-
Notifications
You must be signed in to change notification settings - Fork 493
Expand file tree
/
Copy pathserver.ts
More file actions
164 lines (148 loc) · 4.28 KB
/
server.ts
File metadata and controls
164 lines (148 loc) · 4.28 KB
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
/**
* Session Search Example
*
* Demonstrates the SearchProvider with:
* - Searchable knowledge block backed by DO SQLite FTS5
* - The model indexes information via set_context and retrieves via search_context
* - Session memory with context blocks (soul, memory)
*/
import {
Agent,
callable,
routeAgentRequest,
type StreamingResponse
} from "agents";
import {
AgentSearchProvider,
Session
} from "agents/experimental/memory/session";
import {
createCompactFunction,
truncateOlderMessages
} from "agents/experimental/memory/utils";
import type { UIMessage } from "ai";
import {
convertToModelMessages,
generateText,
stepCountIs,
streamText
} from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class SearchAgent extends Agent<Env> {
session = Session.create(this)
.withContext("soul", {
provider: {
get: async () =>
[
"You are a helpful assistant with searchable knowledge.",
"When the user gives you information, use set_context to index it in the knowledge block with a descriptive key.",
"When the user asks a question, use search_context to find relevant information before answering.",
"Use set_context to save important facts to memory."
].join("\n")
}
})
.withContext("memory", {
description: "Learned facts",
maxTokens: 1100
})
.withContext("knowledge", {
description: "Searchable knowledge base",
provider: new AgentSearchProvider(this)
})
.onCompaction(
createCompactFunction({
summarize: (prompt) =>
generateText({
model: createWorkersAI({ binding: this.env.AI })(
"@cf/zai-org/glm-4.7-flash"
),
prompt
}).then((r) => r.text),
tailTokenBudget: 150,
minTailMessages: 1
})
)
.compactAfter(1000)
.withCachedPrompt();
private getAI() {
return createWorkersAI({ binding: this.env.AI })(
"@cf/moonshotai/kimi-k2.5",
{ sessionAffinity: this.sessionAffinity }
);
}
@callable({ streaming: true })
async chat(
stream: StreamingResponse,
message: string,
messageId?: string
): Promise<void> {
await this.session.appendMessage({
id: messageId ?? `user-${crypto.randomUUID()}`,
role: "user",
parts: [{ type: "text", text: message }]
});
const history = this.session.getHistory();
const truncated = truncateOlderMessages(history);
const result = streamText({
model: this.getAI(),
system: await this.session.freezeSystemPrompt(),
messages: await convertToModelMessages(truncated as UIMessage[]),
tools: await this.session.tools(),
stopWhen: stepCountIs(5)
});
for await (const chunk of result.textStream) {
stream.send({ type: "text-delta", text: chunk });
}
const parts: UIMessage["parts"] = [];
const steps = await result.steps;
for (const step of steps) {
for (const tc of step.toolCalls) {
const tr = step.toolResults.find((r) => r.toolCallId === tc.toolCallId);
parts.push({
type: "dynamic-tool",
toolName: tc.toolName,
toolCallId: tc.toolCallId,
state: tr ? "output-available" : "input-available",
input: tc.input,
...(tr ? { output: tr.output } : {})
} as unknown as UIMessage["parts"][number]);
}
}
const text = await result.text;
if (text) {
parts.push({ type: "text", text });
}
const assistantMsg: UIMessage = {
id: `assistant-${crypto.randomUUID()}`,
role: "assistant",
parts
};
await this.session.appendMessage(assistantMsg);
stream.end({ message: assistantMsg });
}
@callable()
getMessages(): UIMessage[] {
return this.session.getHistory() as UIMessage[];
}
@callable()
async compact(): Promise<{ success: boolean }> {
try {
await this.session.compact();
return { success: true };
} catch {
return { success: false };
}
}
@callable()
clearMessages(): void {
this.session.clearMessages();
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
}
};