perf(agent): cap recall + 2s deadline so LLM fallback always fires
Trace showed every Assistant submit triggered 60+ sequential
GET /api/ea2/flow/{key} requests via agentMemory.recall before
llmClient.chat ran. With a slow EA2 demo, recall took longer
than the test wait so the LLM fetch never started, the agent
reply never rendered, and [llm] telemetry never logged.
Two fixes:
1. agentMemory.listAll(cap) - default 200, recall uses 30.
Edges are returned newest-first by EA2 so the 30-cap keeps the
most recent vault entries and drops the long-tail history that
was pinning the network.
2. routeAgentInput wraps recall in a 2s AbortController. If recall
doesn't finish in 2s, hints[] stays empty and llmFallback runs
immediately. Recall is best-effort context, never a blocker.
This commit is contained in:
@@ -97,7 +97,7 @@ export const agentMemory = {
|
|||||||
return mem._key;
|
return mem._key;
|
||||||
},
|
},
|
||||||
|
|
||||||
async listAll(email: string, signal?: AbortSignal): Promise<Memory[]> {
|
async listAll(email: string, signal?: AbortSignal, cap = 200): Promise<Memory[]> {
|
||||||
const vaultKey = vaultKeyFor(email);
|
const vaultKey = vaultKeyFor(email);
|
||||||
const ok = await ensureVault(vaultKey, email, signal);
|
const ok = await ensureVault(vaultKey, email, signal);
|
||||||
if (!ok) return [];
|
if (!ok) return [];
|
||||||
@@ -107,7 +107,8 @@ export const agentMemory = {
|
|||||||
const memKeys = (edges as any[])
|
const memKeys = (edges as any[])
|
||||||
.filter((e) => e?.role === "presentation")
|
.filter((e) => e?.role === "presentation")
|
||||||
.map((e) => (e._to || "").split("/").pop())
|
.map((e) => (e._to || "").split("/").pop())
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean)
|
||||||
|
.slice(0, cap) as string[];
|
||||||
const memos = await Promise.all(
|
const memos = await Promise.all(
|
||||||
memKeys.map(async (k) => {
|
memKeys.map(async (k) => {
|
||||||
try {
|
try {
|
||||||
@@ -132,7 +133,7 @@ export const agentMemory = {
|
|||||||
* Score = count of unique stem matches. Ties broken by recency.
|
* Score = count of unique stem matches. Ties broken by recency.
|
||||||
*/
|
*/
|
||||||
async recall(email: string, query: string, limit = 5, signal?: AbortSignal): Promise<Memory[]> {
|
async recall(email: string, query: string, limit = 5, signal?: AbortSignal): Promise<Memory[]> {
|
||||||
const all = await agentMemory.listAll(email, signal);
|
const all = await agentMemory.listAll(email, signal, 30);
|
||||||
if (!query.trim() || all.length === 0) return all.slice(0, limit);
|
if (!query.trim() || all.length === 0) return all.slice(0, limit);
|
||||||
const stems = new Set(
|
const stems = new Set(
|
||||||
query.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
|
query.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
|
||||||
|
|||||||
@@ -240,7 +240,12 @@ export async function routeAgentInput(text: string, ctx: ToolContext): Promise<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let hints: Memory[] = [];
|
let hints: Memory[] = [];
|
||||||
try { hints = await agentMemory.recall(ctx.userEmail, trimmed, 3); } catch { /* ignore */ }
|
try {
|
||||||
|
const recallDeadline = new AbortController();
|
||||||
|
const recallTimer = setTimeout(() => recallDeadline.abort(), 2000);
|
||||||
|
hints = await agentMemory.recall(ctx.userEmail, trimmed, 3, recallDeadline.signal);
|
||||||
|
clearTimeout(recallTimer);
|
||||||
|
} catch { /* recall is best-effort; never blocks the user */ }
|
||||||
const llmReply = await llmFallback(trimmed, ctx, hints);
|
const llmReply = await llmFallback(trimmed, ctx, hints);
|
||||||
if (llmReply) return llmReply;
|
if (llmReply) return llmReply;
|
||||||
if (hints.length > 0) {
|
if (hints.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user