Spaces:
Running on Zero
Running on Zero
File size: 2,788 Bytes
78cc96f | 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 | // src/agent/runtime.js
// ReAct-style agent loop. Model-agnostic: works with any `llm.chat(...)` that
// streams tokens via onToken and resolves to a string or { text }.
import { DEFAULT_SYSTEM } from "./webllm-agent.js";
import { TOOL_HELP } from "./webllm-agent.js";
export function createAgentRuntime({ llm, tools, onLog, onToken, onState }) {
let running = false;
let aborter = null;
async function runTool(name, args) {
if (!tools[name]) throw new Error(`Unknown tool: ${name}`);
return await tools[name](args || {});
}
async function loop(userText, messages = [], maxIter = 8) {
if (running) return;
running = true;
aborter = new AbortController();
onState?.({ running: true });
const chat = [
{ role: "system", content: `${DEFAULT_SYSTEM}\n\nAvailable tools:\n${TOOL_HELP}` },
...messages,
{ role: "user", content: userText },
];
let last = "";
try {
for (let iter = 0; iter < maxIter; iter++) {
onLog?.(`iter ${iter + 1}/${maxIter}`);
last = "";
const out = await llm.chat({
messages: chat,
signal: aborter.signal,
stream: true,
temperature: 0.4,
max_tokens: 900,
onToken: (t) => {
last += t;
onToken?.(t);
},
});
const text = typeof out === "string" ? out : (out?.text || last);
const actionMatch = text.match(/action\s*:\s*(\{[\s\S]*?\})/i);
if (!actionMatch) {
onState?.({ running: false, final: text });
return text;
}
let action;
try {
action = JSON.parse(actionMatch[1]);
} catch {
chat.push({ role: "assistant", content: text });
chat.push({ role: "user", content: "Observation: action JSON parse error. Re-emit valid JSON." });
continue;
}
onLog?.(`tool: ${action.tool}(${JSON.stringify(stripTool(action))})`);
let result;
try {
result = await runTool(action.tool, action);
} catch (err) {
result = `tool error: ${err?.message || err}`;
}
chat.push({ role: "assistant", content: text });
chat.push({
role: "user",
content: `Observation from ${action.tool}: ${String(result).slice(0, 8000)}`,
});
}
onState?.({ running: false, final: last });
return last;
} finally {
running = false;
aborter = null;
onState?.({ running: false });
}
}
function stop() {
if (aborter) aborter.abort();
running = false;
onState?.({ running: false });
}
return { loop, stop, isRunning: () => running };
}
function stripTool(action) {
const { tool, ...rest } = action;
return rest;
}
|