File size: 11,398 Bytes
817fa59 52fc4fc 817fa59 451f631 817fa59 52fc4fc 8e93b82 52fc4fc 8e93b82 52fc4fc 8e93b82 52fc4fc 817fa59 abf3ab9 8e93b82 abf3ab9 8e93b82 c3f179a abf3ab9 c3f179a abf3ab9 c3f179a abf3ab9 07a505e abf3ab9 3a25c4d abf3ab9 c3f179a abf3ab9 c3f179a d543f86 07a505e abf3ab9 8e93b82 6643734 8e93b82 6643734 8e93b82 6643734 8e93b82 6643734 451f631 8e93b82 c3f179a 6643734 c3f179a 6643734 c3f179a 6643734 8e93b82 6643734 c3f179a 6643734 8e93b82 c3f179a 6643734 8e93b82 c3f179a 8e93b82 c3f179a 8e93b82 c3f179a 8e93b82 6643734 c3f179a 6643734 8e93b82 6643734 c3f179a 6643734 c3f179a 8e93b82 6643734 c3f179a 6643734 8e93b82 6643734 c3f179a 6643734 8e93b82 6643734 8e93b82 6643734 8e93b82 6643734 8e93b82 6643734 c3f179a 8e93b82 6643734 8e93b82 c3f179a 8e93b82 6643734 c3f179a 6643734 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | import json
import os
import subprocess
import sys
import threading
import time
from datetime import datetime, timezone
from pathlib import Path
import gradio as gr
import spaces
import torch
def install_local_wheel(env_name: str, default_path: str, import_name: str) -> None:
"""Install a private package supplied as a wheel in the Space repository."""
try:
__import__(import_name)
return
except ModuleNotFoundError:
pass
wheel_path = Path(os.getenv(env_name, default_path))
if not wheel_path.is_file():
raise RuntimeError(
f"Package '{import_name}' is not installed and its wheel is missing: "
f"{wheel_path}. Add the file to the Space repository or set {env_name}."
)
subprocess.check_call(
[sys.executable, "-m", "pip", "install", str(wheel_path), "--quiet"]
)
# These files were also required by the supplied original demo.
install_local_wheel(
"RXLM_WHEEL",
"/home/user/app/rxlm-0.3.101-py3-none-any.whl",
"rxlm",
)
install_local_wheel(
"RXLM_PRO_WHEEL",
"/home/user/app/rxlm_pro-0.2.71-py3-none-any.whl",
"rxlm_pro",
)
from rxlm.training.tokenizer import load_tokenizer_from_hf_hub
from rxlm_pro.models.rxq import RxQwenDense
MODEL_ID = os.getenv("MODEL_ID", "AdamF92/RxQwen-Micro-2B-Chat")
TOKENIZER_ID = os.getenv("TOKENIZER_ID", "AdamF92/RxQwen-Nano-0.8B")
MODEL_REVISION = os.getenv("MODEL_REVISION") or None
HF_TOKEN = os.getenv("HF_TOKEN")
MEMORY_DIR = Path(os.getenv("MEMORY_DIR", "./memory"))
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = load_tokenizer_from_hf_hub(TOKENIZER_ID, token=HF_TOKEN)
model = RxQwenDense.from_pretrained(
MODEL_ID,
token=HF_TOKEN,
tokenizer=tokenizer,
revision=MODEL_REVISION,
)
model.to(device)
model.init_model(device=device)
model.set_batch_mode(False)
INITIAL_STM = model.export_stm_state().cpu()
MODEL_LOCK = threading.Lock()
STOP_EVENT = threading.Event()
def safe_name(name: str) -> str:
cleaned = "".join(c for c in name.strip() if c.isalnum() or c in "-_")
return cleaned[:64] or "default"
def memory_paths(name: str) -> tuple[Path, Path]:
stem = safe_name(name)
return MEMORY_DIR / f"{stem}.pt", MEMORY_DIR / f"{stem}.jsonl"
def load_memory(name: str) -> torch.Tensor:
state_path, _ = memory_paths(name)
if not state_path.exists():
return INITIAL_STM.clone()
state = torch.load(state_path, map_location="cpu", weights_only=True)
if not isinstance(state, torch.Tensor):
raise ValueError("The memory file does not contain an STM tensor.")
return state
def save_memory(name: str, state: torch.Tensor, event: dict) -> None:
state_path, journal_path = memory_paths(name)
temporary_path = state_path.with_suffix(".tmp")
torch.save(state.cpu(), temporary_path)
os.replace(temporary_path, state_path)
event = {"timestamp": datetime.now(timezone.utc).isoformat(), **event}
with journal_path.open("a", encoding="utf-8") as journal:
journal.write(json.dumps(event, ensure_ascii=False) + "\n")
def stream_generate(prompt: str, stm_state: torch.Tensor):
"""Yield response text, final STM, token delta, and query token count."""
tokenized = model.tokenize_query(prompt, max_seq_len=8192, device=device)
query_tokens = int(tokenized["input_ids"].size(-1))
pieces: list[str] = []
yield "", None, 0, query_tokens
with MODEL_LOCK:
model.load_stm_state(stm_state)
with torch.inference_mode(), torch.amp.autocast(
device_type=device.type,
dtype=torch.bfloat16,
enabled=device.type == "cuda",
):
for token_id in model.interact(
**tokenized,
thinking_mode="extended",
max_seq_len=8192,
temperature=0.4,
top_p=0.9,
):
if token_id == -2:
continue
token = model.stringify_token(
token_id,
show_memory_update=False,
skip_special_tokens=False,
)
if token not in {"[T]", "[A]"}:
pieces.append(token)
yield "".join(pieces), None, 1, query_tokens
new_state = model.export_stm_state().cpu()
yield "".join(pieces).strip(), new_state, 0, query_tokens
def iteration_prompt(task: str, iteration: int) -> str:
# Build the prompt from separate strings so web editors cannot lose a closing
# triple quote while copying the file.
return (
"You are an autonomous agent working on one task.\n\n"
"TASK:\n"
+ task
+ "\n\nThis is iteration "
+ str(iteration)
+ ". Use the information stored in your persistent memory.\n"
"Take the single most valuable next step. Evaluate the result and store "
"important facts, decisions, errors, and the next step in memory. Never "
"claim to have performed actions for which you have no tools. If the task "
"is genuinely complete, end with the exact marker [TASK_COMPLETE]. "
"Otherwise, end with a short description of the next step."
)
@spaces.GPU
def run_agent(
task: str,
memory_name: str,
max_iterations: int,
continuous: bool,
):
state_input_tokens = 0
llm_input_tokens = 0
output_tokens = 0
def result(current_history, current_status):
return (
current_history,
current_status,
state_input_tokens,
llm_input_tokens,
output_tokens,
)
if not task or not task.strip():
yield result([], "Enter a task.")
return
STOP_EVENT.clear()
name = safe_name(memory_name)
history = []
try:
state = load_memory(name)
except Exception as exc:
yield result(history, f"Could not load memory: {exc}")
return
iteration = 1
while continuous or iteration <= int(max_iterations):
if STOP_EVENT.is_set():
yield result(
history,
f"Stopped after {iteration - 1} iterations. Memory was saved.",
)
return
limit_label = "continuous" if continuous else str(int(max_iterations))
yield result(history, f"Iteration {iteration}/{limit_label}...")
try:
response = ""
final_state = None
counted_query = False
for partial_response, possible_state, token_delta, query_tokens in stream_generate(
iteration_prompt(task.strip(), iteration), state
):
if not counted_query:
# State models read only the new query. A conventional LLM must
# read the accumulated context again on every turn.
previous_state_inputs = state_input_tokens
state_input_tokens += query_tokens
llm_input_tokens += (
previous_state_inputs + output_tokens + query_tokens
)
counted_query = True
output_tokens += token_delta
response = partial_response
if possible_state is not None:
final_state = possible_state
live_history = history + [
{"role": "user", "content": f"Iteration {iteration}"},
{"role": "assistant", "content": response},
]
yield result(
live_history,
f"Iteration {iteration}/{limit_label}: reasoning live...",
)
if final_state is None:
raise RuntimeError("The model did not return an updated STM state.")
state = final_state
save_memory(
name,
state,
{"iteration": iteration, "task": task.strip(), "response": response},
)
except Exception as exc:
yield result(history, f"Error in iteration {iteration}: {exc}")
return
history = history + [
{"role": "user", "content": f"Iteration {iteration}"},
{"role": "assistant", "content": response},
]
if "[TASK_COMPLETE]" in response:
yield result(
history,
f"Task completed in iteration {iteration}. Memory: {name}",
)
return
yield result(history, f"Iteration {iteration} saved; continuing...")
iteration += 1
time.sleep(0.1)
yield result(
history,
f"Reached the limit of {max_iterations} iterations. Memory was saved.",
)
def stop_agent():
STOP_EVENT.set()
return "Stop requested..."
def reset_memory(memory_name: str):
state_path, journal_path = memory_paths(memory_name)
for path in (state_path, journal_path):
path.unlink(missing_ok=True)
return [], f"Deleted memory: {safe_name(memory_name)}", 0, 0, 0
with gr.Blocks(title="Persistent Agent Loop") as demo:
gr.Markdown(
"# Live Persistent Agent Loop\n"
"Watch the model reason token by token while it repeatedly works on one task."
)
task = gr.Textbox(
label="Task",
lines=5,
value=(
"Create a practical five-step plan for launching a small AI-powered "
"study assistant. Refine the plan on every turn, identify risks, and "
"finish with a concise implementation checklist."
),
)
with gr.Row():
memory_name = gr.Textbox(label="Memory name", value="default")
max_iterations = gr.Slider(1, 50, value=5, step=1, label="Turns")
continuous = gr.Checkbox(
value=False,
label="Continuous loop (runs until Stop or TASK_COMPLETE)",
)
with gr.Row():
start = gr.Button("Run", variant="primary")
stop = gr.Button("Stop")
reset = gr.Button("Clear memory")
status = gr.Textbox(label="Status", interactive=False)
with gr.Row():
state_token_metric = gr.Number(
value=0,
label="State Model Input Tokens",
interactive=False,
)
llm_token_metric = gr.Number(
value=0,
label="Conventional LLM Input Tokens",
interactive=False,
)
output_token_metric = gr.Number(
value=0,
label="Output Tokens",
interactive=False,
)
# The Gradio version used by this Space does not support the `type` parameter.
log = gr.Chatbot(label="Progress", height=500)
start.click(
run_agent,
[task, memory_name, max_iterations, continuous],
[
log,
status,
state_token_metric,
llm_token_metric,
output_token_metric,
],
)
stop.click(stop_agent, outputs=status, queue=False)
reset.click(
reset_memory,
memory_name,
[
log,
status,
state_token_metric,
llm_token_metric,
output_token_metric,
],
)
if __name__ == "__main__":
demo.queue(default_concurrency_limit=1).launch()
|