import { Agent } from "agents";
import { getAgent } from "ayjnt/rpc";
import type { GeneratedEnv } from "@ayjnt/env";
import type IndexAgent from "../index/agent.ts";
import { runWorkersAi } from "../shared.ts";
export default class QAAgent extends Agent<GeneratedEnv, { history: any[]; pending: boolean }> {
override initialState = { history: [], pending: false };
override async onRequest(request: Request): Promise<Response> {
if (request.method !== "POST") return Response.json({ instance: this.name, ...this.state });
const { question } = (await request.json()) as { question: string };
this.setState({ ...this.state, pending: true });
const plan = await this.plan(question);
const index = await getAgent<IndexAgent>(this.env.INDEX_AGENT, "main");
const hits = await Promise.all(plan.map(async (sub) => ({ sub, docs: await index.search(sub, 3) })));
const evidence = hits.flatMap((h) => h.docs.map((d) => d.text))
.filter((t, i, a) => a.indexOf(t) === i).join("\n\n---\n\n");
const answer = await this.compose(question, evidence);
const qa = { id: crypto.randomUUID(), question, plan, hits, answer, at: Date.now() };
this.setState({ history: [...this.state.history, qa], pending: false });
return Response.json({ ok: true, qa });
}
private async plan(question: string): Promise<string[]> {
const r = await runWorkersAi<{ response: string }>(this.env, "@cf/meta/llama-3.1-8b-instruct", {
messages: [
{ role: "system", content: "Return ONLY a JSON array of 2-3 search queries." },
{ role: "user", content: question },
],
});
const m = r.response.match(/\[[^\]]*\]/);
try { return m ? JSON.parse(m[0]) : [question]; } catch { return [question]; }
}
private async compose(question: string, evidence: string) {
const r = await runWorkersAi<{ response: string }>(this.env, "@cf/meta/llama-3.1-8b-instruct", {
messages: [
{ role: "system", content: "Answer using ONLY the context. Be concise. Say so if insufficient." },
{ role: "user", content: `CONTEXT:\n${evidence}\n\nQUESTION:\n${question}` },
],
});
return r.response.trim();
}
}