feat(ai): assistant prompt, course tools and tool loop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
24cd6329cb
commit
49d1df0f13
5 files changed
+375
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import type { AiProvider, ChatMessage } from "@stc/contracts";
|
||||
import type { ToolSet } from "./tools";
|
||||
|
||||
export type AssistantEvent =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "tool"; name: string }
|
||||
| { type: "error"; message: string }
|
||||
| { type: "done"; text: string };
|
||||
|
||||
/**
|
||||
* Runs the model with tools until it answers without calling a tool, or until maxSteps model calls.
|
||||
* Mutates `messages` by appending the tool round trips, so callers can inspect them.
|
||||
*/
|
||||
export async function* runAssistant(o: {
|
||||
provider: AiProvider;
|
||||
messages: ChatMessage[];
|
||||
tools: ToolSet;
|
||||
signal?: AbortSignal;
|
||||
maxSteps?: number;
|
||||
}): AsyncIterable<AssistantEvent> {
|
||||
const maxSteps = o.maxSteps ?? 6;
|
||||
let answer = "";
|
||||
for (let step = 0; step < maxSteps; step++) {
|
||||
let text = "";
|
||||
const calls: { id: string; name: string; input: unknown }[] = [];
|
||||
try {
|
||||
for await (const ev of o.provider.chat(o.messages, { tools: o.tools.specs, signal: o.signal })) {
|
||||
if (ev.text) { text += ev.text; yield { type: "text", text: ev.text }; }
|
||||
if (ev.toolCall) calls.push(ev.toolCall);
|
||||
}
|
||||
} catch (e) {
|
||||
yield { type: "error", message: (e as Error).message };
|
||||
return;
|
||||
}
|
||||
answer += text;
|
||||
if (!calls.length) {
|
||||
yield { type: "done", text: answer };
|
||||
return;
|
||||
}
|
||||
if (text) o.messages.push({ role: "assistant", content: text });
|
||||
for (const c of calls) o.messages.push({ role: "assistant", content: JSON.stringify(c.input ?? {}), toolCallId: c.id, name: c.name });
|
||||
for (const c of calls) {
|
||||
yield { type: "tool", name: c.name };
|
||||
let result: string;
|
||||
try { result = await o.tools.run(c.name, c.input); } catch (e) { result = `Tool error: ${(e as Error).message}`; }
|
||||
o.messages.push({ role: "tool", content: result, toolCallId: c.id, name: c.name });
|
||||
}
|
||||
}
|
||||
const note = "\n\n(I stopped after several lookups. Ask again to continue.)";
|
||||
yield { type: "text", text: note };
|
||||
yield { type: "done", text: answer + note };
|
||||
}
|
||||
Reference in new issue
Block a user