@livekit/agents-plugin-anthropic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +21 -0
- package/dist/index.cjs +45 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.cjs +357 -0
- package/dist/llm.cjs.map +1 -0
- package/dist/llm.d.cts +92 -0
- package/dist/llm.d.ts +92 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +328 -0
- package/dist/llm.js.map +1 -0
- package/dist/llm.test.cjs +259 -0
- package/dist/llm.test.cjs.map +1 -0
- package/dist/llm.test.d.cts +2 -0
- package/dist/llm.test.d.ts +2 -0
- package/dist/llm.test.d.ts.map +1 -0
- package/dist/llm.test.js +258 -0
- package/dist/llm.test.js.map +1 -0
- package/dist/models.cjs +17 -0
- package/dist/models.cjs.map +1 -0
- package/dist/models.d.cts +8 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +1 -0
- package/dist/models.js.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +19 -0
- package/src/llm.test.ts +317 -0
- package/src/llm.ts +447 -0
- package/src/models.ts +21 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_agents = require("@livekit/agents");
|
|
3
|
+
var import_vitest = require("vitest");
|
|
4
|
+
var import_zod = require("zod");
|
|
5
|
+
var import_llm = require("./llm.cjs");
|
|
6
|
+
function messageStartEvent() {
|
|
7
|
+
return {
|
|
8
|
+
type: "message_start",
|
|
9
|
+
message: {
|
|
10
|
+
id: "msg_123",
|
|
11
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function textDeltaEvent(text) {
|
|
16
|
+
return {
|
|
17
|
+
type: "content_block_delta",
|
|
18
|
+
delta: { type: "text_delta", text }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function collectTextFromEvents(events, toolCtx) {
|
|
22
|
+
var _a;
|
|
23
|
+
const client = {
|
|
24
|
+
messages: {
|
|
25
|
+
create: async () => async function* () {
|
|
26
|
+
yield* events;
|
|
27
|
+
}()
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const anthropicLlm = new import_llm.LLM({
|
|
31
|
+
apiKey: "dummy",
|
|
32
|
+
client,
|
|
33
|
+
model: "claude-3-5-sonnet-20241022"
|
|
34
|
+
});
|
|
35
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
36
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
37
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx });
|
|
38
|
+
const textChunks = [];
|
|
39
|
+
for await (const chunk of stream) {
|
|
40
|
+
if ((_a = chunk.delta) == null ? void 0 : _a.content) {
|
|
41
|
+
textChunks.push(chunk.delta.content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return textChunks.join("");
|
|
45
|
+
}
|
|
46
|
+
(0, import_vitest.describe)("Anthropic LLM", () => {
|
|
47
|
+
(0, import_vitest.it)("correctly maps ChatContext to Anthropic system and messages arrays", () => {
|
|
48
|
+
const anthropicLlm = new import_llm.LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
49
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
50
|
+
chatCtx.addMessage({
|
|
51
|
+
role: "system",
|
|
52
|
+
content: "You are a mock agent."
|
|
53
|
+
});
|
|
54
|
+
chatCtx.addMessage({
|
|
55
|
+
role: "user",
|
|
56
|
+
content: "Hello, world!"
|
|
57
|
+
});
|
|
58
|
+
const { system, messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
59
|
+
(0, import_vitest.expect)(system).toHaveLength(1);
|
|
60
|
+
(0, import_vitest.expect)(system[0].text).toBe("You are a mock agent.");
|
|
61
|
+
(0, import_vitest.expect)(messages).toHaveLength(1);
|
|
62
|
+
(0, import_vitest.expect)(messages[0].role).toBe("user");
|
|
63
|
+
(0, import_vitest.expect)(messages[0].content).toBe("Hello, world!");
|
|
64
|
+
});
|
|
65
|
+
(0, import_vitest.it)("merges consecutive same-role messages", () => {
|
|
66
|
+
const anthropicLlm = new import_llm.LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
67
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
68
|
+
chatCtx.addMessage({ role: "user", content: "First message" });
|
|
69
|
+
chatCtx.addMessage({ role: "user", content: "Second message" });
|
|
70
|
+
chatCtx.addMessage({ role: "assistant", content: "Reply" });
|
|
71
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
72
|
+
(0, import_vitest.expect)(messages).toHaveLength(3);
|
|
73
|
+
(0, import_vitest.expect)(messages[0].role).toBe("user");
|
|
74
|
+
(0, import_vitest.expect)(Array.isArray(messages[0].content)).toBe(true);
|
|
75
|
+
(0, import_vitest.expect)(messages[0].content).toHaveLength(2);
|
|
76
|
+
(0, import_vitest.expect)(messages[1].role).toBe("assistant");
|
|
77
|
+
(0, import_vitest.expect)(messages[2].role).toBe("user");
|
|
78
|
+
(0, import_vitest.expect)(messages[2].content).toEqual([{ type: "text", text: "." }]);
|
|
79
|
+
});
|
|
80
|
+
(0, import_vitest.it)("injects a dummy user message if conversation starts with assistant", () => {
|
|
81
|
+
const anthropicLlm = new import_llm.LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
82
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
83
|
+
chatCtx.addMessage({ role: "system", content: "System prompt" });
|
|
84
|
+
chatCtx.addMessage({ role: "assistant", content: "I start talking" });
|
|
85
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
86
|
+
(0, import_vitest.expect)(messages[0].role).toBe("user");
|
|
87
|
+
(0, import_vitest.expect)(messages[0].content).toBe("(empty)");
|
|
88
|
+
(0, import_vitest.expect)(messages[1].role).toBe("assistant");
|
|
89
|
+
});
|
|
90
|
+
(0, import_vitest.it)("handles function_call and function_call_output items", () => {
|
|
91
|
+
const anthropicLlm = new import_llm.LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
92
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
93
|
+
chatCtx.addMessage({ role: "user", content: "What is the weather?" });
|
|
94
|
+
chatCtx.items.push(
|
|
95
|
+
new import_agents.llm.FunctionCall({
|
|
96
|
+
callId: "call_123",
|
|
97
|
+
name: "get_weather",
|
|
98
|
+
args: '{"city":"London"}'
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
chatCtx.items.push(
|
|
102
|
+
new import_agents.llm.FunctionCallOutput({
|
|
103
|
+
callId: "call_123",
|
|
104
|
+
name: "get_weather",
|
|
105
|
+
output: '{"temp": 20}',
|
|
106
|
+
isError: false
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
110
|
+
(0, import_vitest.expect)(messages).toHaveLength(3);
|
|
111
|
+
(0, import_vitest.expect)(messages[0].role).toBe("user");
|
|
112
|
+
(0, import_vitest.expect)(messages[1].role).toBe("assistant");
|
|
113
|
+
(0, import_vitest.expect)(Array.isArray(messages[1].content)).toBe(true);
|
|
114
|
+
(0, import_vitest.expect)(messages[1].content[0].type).toBe("tool_use");
|
|
115
|
+
(0, import_vitest.expect)(messages[1].content[0].id).toBe("call_123");
|
|
116
|
+
(0, import_vitest.expect)(messages[2].role).toBe("user");
|
|
117
|
+
(0, import_vitest.expect)(Array.isArray(messages[2].content)).toBe(true);
|
|
118
|
+
(0, import_vitest.expect)(messages[2].content[0].type).toBe("tool_result");
|
|
119
|
+
});
|
|
120
|
+
(0, import_vitest.it)("creates a fresh stream on retry", async () => {
|
|
121
|
+
var _a;
|
|
122
|
+
let calls = 0;
|
|
123
|
+
const client = {
|
|
124
|
+
messages: {
|
|
125
|
+
create: async () => {
|
|
126
|
+
calls += 1;
|
|
127
|
+
if (calls === 1) {
|
|
128
|
+
throw new Error("transient connect failure");
|
|
129
|
+
}
|
|
130
|
+
return async function* () {
|
|
131
|
+
}();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const anthropicLlm = new import_llm.LLM({
|
|
136
|
+
apiKey: "dummy",
|
|
137
|
+
client,
|
|
138
|
+
model: "claude-3-5-sonnet-20241022"
|
|
139
|
+
});
|
|
140
|
+
anthropicLlm.on("error", () => {
|
|
141
|
+
});
|
|
142
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
143
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
144
|
+
const stream = anthropicLlm.chat({
|
|
145
|
+
chatCtx,
|
|
146
|
+
connOptions: { maxRetry: 1, retryIntervalMs: 0, timeoutMs: 1e3 }
|
|
147
|
+
});
|
|
148
|
+
const chunks = [];
|
|
149
|
+
for await (const chunk of stream) {
|
|
150
|
+
chunks.push(chunk);
|
|
151
|
+
}
|
|
152
|
+
(0, import_vitest.expect)(calls).toBe(2);
|
|
153
|
+
(0, import_vitest.expect)((_a = chunks.at(-1)) == null ? void 0 : _a.usage).toEqual({
|
|
154
|
+
completionTokens: 0,
|
|
155
|
+
promptTokens: 0,
|
|
156
|
+
promptCachedTokens: 0,
|
|
157
|
+
totalTokens: 0
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
(0, import_vitest.it)("sends function tool schemas from a real ToolContext", async () => {
|
|
161
|
+
let capturedParams;
|
|
162
|
+
const client = {
|
|
163
|
+
messages: {
|
|
164
|
+
create: async (params) => {
|
|
165
|
+
capturedParams = params;
|
|
166
|
+
return async function* () {
|
|
167
|
+
yield messageStartEvent();
|
|
168
|
+
}();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const anthropicLlm = new import_llm.LLM({
|
|
173
|
+
apiKey: "dummy",
|
|
174
|
+
client,
|
|
175
|
+
model: "claude-3-5-sonnet-20241022"
|
|
176
|
+
});
|
|
177
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
178
|
+
chatCtx.addMessage({ role: "user", content: "What is the weather in Tokyo?" });
|
|
179
|
+
const toolCtx = import_agents.llm.toToolContext({
|
|
180
|
+
getWeather: import_agents.llm.tool({
|
|
181
|
+
description: "Get the weather for a given location.",
|
|
182
|
+
parameters: import_zod.z.object({ location: import_zod.z.string() }),
|
|
183
|
+
execute: async () => "sunny"
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx });
|
|
187
|
+
for await (const _ of stream) {
|
|
188
|
+
}
|
|
189
|
+
(0, import_vitest.expect)(capturedParams == null ? void 0 : capturedParams.tools).toHaveLength(1);
|
|
190
|
+
const toolSchema = capturedParams.tools[0];
|
|
191
|
+
(0, import_vitest.expect)(toolSchema.name).toBe("getWeather");
|
|
192
|
+
(0, import_vitest.expect)(toolSchema.description).toBe("Get the weather for a given location.");
|
|
193
|
+
(0, import_vitest.expect)(toolSchema.input_schema.type).toBe("object");
|
|
194
|
+
(0, import_vitest.expect)(Object.keys(toolSchema.input_schema.properties ?? {})).toEqual(["location"]);
|
|
195
|
+
});
|
|
196
|
+
(0, import_vitest.it)("does not emit an empty delta for a fully-filtered thinking block", async () => {
|
|
197
|
+
var _a;
|
|
198
|
+
const client = {
|
|
199
|
+
messages: {
|
|
200
|
+
create: async () => async function* () {
|
|
201
|
+
yield messageStartEvent();
|
|
202
|
+
yield textDeltaEvent("<thinking>only reasoning</thinking>");
|
|
203
|
+
}()
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const anthropicLlm = new import_llm.LLM({
|
|
207
|
+
apiKey: "dummy",
|
|
208
|
+
client,
|
|
209
|
+
model: "claude-3-5-sonnet-20241022"
|
|
210
|
+
});
|
|
211
|
+
const chatCtx = new import_agents.llm.ChatContext();
|
|
212
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
213
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx: import_agents.llm.ToolContext.empty() });
|
|
214
|
+
const contentChunks = [];
|
|
215
|
+
for await (const chunk of stream) {
|
|
216
|
+
if (((_a = chunk.delta) == null ? void 0 : _a.content) !== void 0) {
|
|
217
|
+
contentChunks.push(chunk.delta.content);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
(0, import_vitest.expect)(contentChunks).toEqual([]);
|
|
221
|
+
});
|
|
222
|
+
(0, import_vitest.it)("filters thinking blocks when tools are active", async () => {
|
|
223
|
+
const text = await collectTextFromEvents(
|
|
224
|
+
[
|
|
225
|
+
messageStartEvent(),
|
|
226
|
+
textDeltaEvent("<thinking>hidden"),
|
|
227
|
+
textDeltaEvent("still hidden</thinking>visible")
|
|
228
|
+
],
|
|
229
|
+
import_agents.llm.ToolContext.empty()
|
|
230
|
+
);
|
|
231
|
+
(0, import_vitest.expect)(text).toBe("visible");
|
|
232
|
+
});
|
|
233
|
+
(0, import_vitest.it)("does not filter thinking blocks without tools", async () => {
|
|
234
|
+
const text = await collectTextFromEvents([
|
|
235
|
+
messageStartEvent(),
|
|
236
|
+
textDeltaEvent("<thinking>visible without tools</thinking>")
|
|
237
|
+
]);
|
|
238
|
+
(0, import_vitest.expect)(text).toBe("<thinking>visible without tools</thinking>");
|
|
239
|
+
});
|
|
240
|
+
(0, import_vitest.it)("preserves text around same-delta thinking blocks", async () => {
|
|
241
|
+
const text = await collectTextFromEvents(
|
|
242
|
+
[messageStartEvent(), textDeltaEvent("before <thinking>hidden</thinking> after")],
|
|
243
|
+
import_agents.llm.ToolContext.empty()
|
|
244
|
+
);
|
|
245
|
+
(0, import_vitest.expect)(text).toBe("before after");
|
|
246
|
+
});
|
|
247
|
+
(0, import_vitest.it)("preserves text before split thinking blocks", async () => {
|
|
248
|
+
const text = await collectTextFromEvents(
|
|
249
|
+
[
|
|
250
|
+
messageStartEvent(),
|
|
251
|
+
textDeltaEvent("before <thinking>hidden"),
|
|
252
|
+
textDeltaEvent("still hidden</thinking> after")
|
|
253
|
+
],
|
|
254
|
+
import_agents.llm.ToolContext.empty()
|
|
255
|
+
);
|
|
256
|
+
(0, import_vitest.expect)(text).toBe("before after");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
//# sourceMappingURL=llm.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { llm } from '@livekit/agents';\nimport { describe, expect, it } from 'vitest';\nimport { z } from 'zod';\nimport { LLM } from './llm.js';\n\nfunction messageStartEvent(): Anthropic.MessageStreamEvent {\n return {\n type: 'message_start',\n message: {\n id: 'msg_123',\n usage: { input_tokens: 0, output_tokens: 0 },\n },\n } as Anthropic.MessageStreamEvent;\n}\n\nfunction textDeltaEvent(text: string): Anthropic.MessageStreamEvent {\n return {\n type: 'content_block_delta',\n delta: { type: 'text_delta', text },\n } as Anthropic.MessageStreamEvent;\n}\n\nasync function collectTextFromEvents(\n events: Anthropic.MessageStreamEvent[],\n toolCtx?: llm.ToolContextLike,\n): Promise<string> {\n const client = {\n messages: {\n create: async () =>\n (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield* events;\n })(),\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx });\n const textChunks: string[] = [];\n for await (const chunk of stream) {\n if (chunk.delta?.content) {\n textChunks.push(chunk.delta.content);\n }\n }\n return textChunks.join('');\n}\n\ndescribe('Anthropic LLM', () => {\n it('correctly maps ChatContext to Anthropic system and messages arrays', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({\n role: 'system',\n content: 'You are a mock agent.',\n });\n chatCtx.addMessage({\n role: 'user',\n content: 'Hello, world!',\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { system, messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Assert that system prompts were correctly isolated\n expect(system).toHaveLength(1);\n expect(system[0].text).toBe('You are a mock agent.');\n\n // Assert that strictly user messages ended up in the messages payload\n expect(messages).toHaveLength(1);\n expect(messages[0].role).toBe('user');\n expect(messages[0].content).toBe('Hello, world!');\n });\n\n it('merges consecutive same-role messages', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'First message' });\n chatCtx.addMessage({ role: 'user', content: 'Second message' });\n chatCtx.addMessage({ role: 'assistant', content: 'Reply' });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Two user messages should be merged into one with array content, followed by a\n // trailing dummy user turn for Claude 4.6+.\n expect(messages).toHaveLength(3);\n expect(messages[0].role).toBe('user');\n expect(Array.isArray(messages[0].content)).toBe(true);\n expect(messages[0].content).toHaveLength(2);\n expect(messages[1].role).toBe('assistant');\n expect(messages[2].role).toBe('user');\n expect(messages[2].content).toEqual([{ type: 'text', text: '.' }]);\n });\n\n it('injects a dummy user message if conversation starts with assistant', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'system', content: 'System prompt' });\n chatCtx.addMessage({ role: 'assistant', content: 'I start talking' });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Should have injected a dummy user message at the start\n expect(messages[0].role).toBe('user');\n expect(messages[0].content).toBe('(empty)');\n expect(messages[1].role).toBe('assistant');\n });\n\n it('handles function_call and function_call_output items', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'What is the weather?' });\n\n // Simulate a function call from the assistant\n chatCtx.items.push(\n new llm.FunctionCall({\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"city\":\"London\"}',\n }),\n );\n\n // Simulate the tool result\n chatCtx.items.push(\n new llm.FunctionCallOutput({\n callId: 'call_123',\n name: 'get_weather',\n output: '{\"temp\": 20}',\n isError: false,\n }),\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // user message, then assistant tool_use, then user tool_result\n expect(messages).toHaveLength(3);\n expect(messages[0].role).toBe('user');\n expect(messages[1].role).toBe('assistant');\n expect(Array.isArray(messages[1].content)).toBe(true);\n expect(messages[1].content[0].type).toBe('tool_use');\n expect(messages[1].content[0].id).toBe('call_123');\n expect(messages[2].role).toBe('user');\n expect(Array.isArray(messages[2].content)).toBe(true);\n expect(messages[2].content[0].type).toBe('tool_result');\n });\n\n it('creates a fresh stream on retry', async () => {\n let calls = 0;\n const client = {\n messages: {\n create: async () => {\n calls += 1;\n if (calls === 1) {\n throw new Error('transient connect failure');\n }\n return (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {})();\n },\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n anthropicLlm.on('error', () => {});\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({\n chatCtx,\n connOptions: { maxRetry: 1, retryIntervalMs: 0, timeoutMs: 1000 },\n });\n const chunks: llm.ChatChunk[] = [];\n for await (const chunk of stream) {\n chunks.push(chunk);\n }\n\n expect(calls).toBe(2);\n expect(chunks.at(-1)?.usage).toEqual({\n completionTokens: 0,\n promptTokens: 0,\n promptCachedTokens: 0,\n totalTokens: 0,\n });\n });\n\n it('sends function tool schemas from a real ToolContext', async () => {\n let capturedParams: Anthropic.MessageCreateParamsStreaming | undefined;\n const client = {\n messages: {\n create: async (params: Anthropic.MessageCreateParamsStreaming) => {\n capturedParams = params;\n return (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield messageStartEvent();\n })();\n },\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'What is the weather in Tokyo?' });\n\n const toolCtx = llm.toToolContext({\n getWeather: llm.tool({\n description: 'Get the weather for a given location.',\n parameters: z.object({ location: z.string() }),\n execute: async () => 'sunny',\n }),\n });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for await (const _ of stream) {\n // drain\n }\n\n expect(capturedParams?.tools).toHaveLength(1);\n const toolSchema = capturedParams!.tools![0] as Anthropic.Tool;\n expect(toolSchema.name).toBe('getWeather');\n expect(toolSchema.description).toBe('Get the weather for a given location.');\n expect(toolSchema.input_schema.type).toBe('object');\n expect(Object.keys(toolSchema.input_schema.properties ?? {})).toEqual(['location']);\n });\n\n it('does not emit an empty delta for a fully-filtered thinking block', async () => {\n const client = {\n messages: {\n create: async () =>\n (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield messageStartEvent();\n yield textDeltaEvent('<thinking>only reasoning</thinking>');\n })(),\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx: llm.ToolContext.empty() });\n const contentChunks: string[] = [];\n for await (const chunk of stream) {\n if (chunk.delta?.content !== undefined) {\n contentChunks.push(chunk.delta.content);\n }\n }\n\n // The whole delta was a thinking block: nothing (not even '') is emitted.\n expect(contentChunks).toEqual([]);\n });\n\n it('filters thinking blocks when tools are active', async () => {\n const text = await collectTextFromEvents(\n [\n messageStartEvent(),\n textDeltaEvent('<thinking>hidden'),\n textDeltaEvent('still hidden</thinking>visible'),\n ],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('visible');\n });\n\n it('does not filter thinking blocks without tools', async () => {\n const text = await collectTextFromEvents([\n messageStartEvent(),\n textDeltaEvent('<thinking>visible without tools</thinking>'),\n ]);\n\n expect(text).toBe('<thinking>visible without tools</thinking>');\n });\n\n it('preserves text around same-delta thinking blocks', async () => {\n const text = await collectTextFromEvents(\n [messageStartEvent(), textDeltaEvent('before <thinking>hidden</thinking> after')],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('before after');\n });\n\n it('preserves text before split thinking blocks', async () => {\n const text = await collectTextFromEvents(\n [\n messageStartEvent(),\n textDeltaEvent('before <thinking>hidden'),\n textDeltaEvent('still hidden</thinking> after'),\n ],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('before after');\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.test.d.ts","sourceRoot":"","sources":["../src/llm.test.ts"],"names":[],"mappings":""}
|
package/dist/llm.test.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { llm } from "@livekit/agents";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { LLM } from "./llm.js";
|
|
5
|
+
function messageStartEvent() {
|
|
6
|
+
return {
|
|
7
|
+
type: "message_start",
|
|
8
|
+
message: {
|
|
9
|
+
id: "msg_123",
|
|
10
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function textDeltaEvent(text) {
|
|
15
|
+
return {
|
|
16
|
+
type: "content_block_delta",
|
|
17
|
+
delta: { type: "text_delta", text }
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function collectTextFromEvents(events, toolCtx) {
|
|
21
|
+
var _a;
|
|
22
|
+
const client = {
|
|
23
|
+
messages: {
|
|
24
|
+
create: async () => async function* () {
|
|
25
|
+
yield* events;
|
|
26
|
+
}()
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const anthropicLlm = new LLM({
|
|
30
|
+
apiKey: "dummy",
|
|
31
|
+
client,
|
|
32
|
+
model: "claude-3-5-sonnet-20241022"
|
|
33
|
+
});
|
|
34
|
+
const chatCtx = new llm.ChatContext();
|
|
35
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
36
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx });
|
|
37
|
+
const textChunks = [];
|
|
38
|
+
for await (const chunk of stream) {
|
|
39
|
+
if ((_a = chunk.delta) == null ? void 0 : _a.content) {
|
|
40
|
+
textChunks.push(chunk.delta.content);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return textChunks.join("");
|
|
44
|
+
}
|
|
45
|
+
describe("Anthropic LLM", () => {
|
|
46
|
+
it("correctly maps ChatContext to Anthropic system and messages arrays", () => {
|
|
47
|
+
const anthropicLlm = new LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
48
|
+
const chatCtx = new llm.ChatContext();
|
|
49
|
+
chatCtx.addMessage({
|
|
50
|
+
role: "system",
|
|
51
|
+
content: "You are a mock agent."
|
|
52
|
+
});
|
|
53
|
+
chatCtx.addMessage({
|
|
54
|
+
role: "user",
|
|
55
|
+
content: "Hello, world!"
|
|
56
|
+
});
|
|
57
|
+
const { system, messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
58
|
+
expect(system).toHaveLength(1);
|
|
59
|
+
expect(system[0].text).toBe("You are a mock agent.");
|
|
60
|
+
expect(messages).toHaveLength(1);
|
|
61
|
+
expect(messages[0].role).toBe("user");
|
|
62
|
+
expect(messages[0].content).toBe("Hello, world!");
|
|
63
|
+
});
|
|
64
|
+
it("merges consecutive same-role messages", () => {
|
|
65
|
+
const anthropicLlm = new LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
66
|
+
const chatCtx = new llm.ChatContext();
|
|
67
|
+
chatCtx.addMessage({ role: "user", content: "First message" });
|
|
68
|
+
chatCtx.addMessage({ role: "user", content: "Second message" });
|
|
69
|
+
chatCtx.addMessage({ role: "assistant", content: "Reply" });
|
|
70
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
71
|
+
expect(messages).toHaveLength(3);
|
|
72
|
+
expect(messages[0].role).toBe("user");
|
|
73
|
+
expect(Array.isArray(messages[0].content)).toBe(true);
|
|
74
|
+
expect(messages[0].content).toHaveLength(2);
|
|
75
|
+
expect(messages[1].role).toBe("assistant");
|
|
76
|
+
expect(messages[2].role).toBe("user");
|
|
77
|
+
expect(messages[2].content).toEqual([{ type: "text", text: "." }]);
|
|
78
|
+
});
|
|
79
|
+
it("injects a dummy user message if conversation starts with assistant", () => {
|
|
80
|
+
const anthropicLlm = new LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
81
|
+
const chatCtx = new llm.ChatContext();
|
|
82
|
+
chatCtx.addMessage({ role: "system", content: "System prompt" });
|
|
83
|
+
chatCtx.addMessage({ role: "assistant", content: "I start talking" });
|
|
84
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
85
|
+
expect(messages[0].role).toBe("user");
|
|
86
|
+
expect(messages[0].content).toBe("(empty)");
|
|
87
|
+
expect(messages[1].role).toBe("assistant");
|
|
88
|
+
});
|
|
89
|
+
it("handles function_call and function_call_output items", () => {
|
|
90
|
+
const anthropicLlm = new LLM({ apiKey: "dummy", model: "claude-3-5-sonnet-20241022" });
|
|
91
|
+
const chatCtx = new llm.ChatContext();
|
|
92
|
+
chatCtx.addMessage({ role: "user", content: "What is the weather?" });
|
|
93
|
+
chatCtx.items.push(
|
|
94
|
+
new llm.FunctionCall({
|
|
95
|
+
callId: "call_123",
|
|
96
|
+
name: "get_weather",
|
|
97
|
+
args: '{"city":"London"}'
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
chatCtx.items.push(
|
|
101
|
+
new llm.FunctionCallOutput({
|
|
102
|
+
callId: "call_123",
|
|
103
|
+
name: "get_weather",
|
|
104
|
+
output: '{"temp": 20}',
|
|
105
|
+
isError: false
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
const { messages } = anthropicLlm._buildAnthropicContext(chatCtx);
|
|
109
|
+
expect(messages).toHaveLength(3);
|
|
110
|
+
expect(messages[0].role).toBe("user");
|
|
111
|
+
expect(messages[1].role).toBe("assistant");
|
|
112
|
+
expect(Array.isArray(messages[1].content)).toBe(true);
|
|
113
|
+
expect(messages[1].content[0].type).toBe("tool_use");
|
|
114
|
+
expect(messages[1].content[0].id).toBe("call_123");
|
|
115
|
+
expect(messages[2].role).toBe("user");
|
|
116
|
+
expect(Array.isArray(messages[2].content)).toBe(true);
|
|
117
|
+
expect(messages[2].content[0].type).toBe("tool_result");
|
|
118
|
+
});
|
|
119
|
+
it("creates a fresh stream on retry", async () => {
|
|
120
|
+
var _a;
|
|
121
|
+
let calls = 0;
|
|
122
|
+
const client = {
|
|
123
|
+
messages: {
|
|
124
|
+
create: async () => {
|
|
125
|
+
calls += 1;
|
|
126
|
+
if (calls === 1) {
|
|
127
|
+
throw new Error("transient connect failure");
|
|
128
|
+
}
|
|
129
|
+
return async function* () {
|
|
130
|
+
}();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const anthropicLlm = new LLM({
|
|
135
|
+
apiKey: "dummy",
|
|
136
|
+
client,
|
|
137
|
+
model: "claude-3-5-sonnet-20241022"
|
|
138
|
+
});
|
|
139
|
+
anthropicLlm.on("error", () => {
|
|
140
|
+
});
|
|
141
|
+
const chatCtx = new llm.ChatContext();
|
|
142
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
143
|
+
const stream = anthropicLlm.chat({
|
|
144
|
+
chatCtx,
|
|
145
|
+
connOptions: { maxRetry: 1, retryIntervalMs: 0, timeoutMs: 1e3 }
|
|
146
|
+
});
|
|
147
|
+
const chunks = [];
|
|
148
|
+
for await (const chunk of stream) {
|
|
149
|
+
chunks.push(chunk);
|
|
150
|
+
}
|
|
151
|
+
expect(calls).toBe(2);
|
|
152
|
+
expect((_a = chunks.at(-1)) == null ? void 0 : _a.usage).toEqual({
|
|
153
|
+
completionTokens: 0,
|
|
154
|
+
promptTokens: 0,
|
|
155
|
+
promptCachedTokens: 0,
|
|
156
|
+
totalTokens: 0
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it("sends function tool schemas from a real ToolContext", async () => {
|
|
160
|
+
let capturedParams;
|
|
161
|
+
const client = {
|
|
162
|
+
messages: {
|
|
163
|
+
create: async (params) => {
|
|
164
|
+
capturedParams = params;
|
|
165
|
+
return async function* () {
|
|
166
|
+
yield messageStartEvent();
|
|
167
|
+
}();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const anthropicLlm = new LLM({
|
|
172
|
+
apiKey: "dummy",
|
|
173
|
+
client,
|
|
174
|
+
model: "claude-3-5-sonnet-20241022"
|
|
175
|
+
});
|
|
176
|
+
const chatCtx = new llm.ChatContext();
|
|
177
|
+
chatCtx.addMessage({ role: "user", content: "What is the weather in Tokyo?" });
|
|
178
|
+
const toolCtx = llm.toToolContext({
|
|
179
|
+
getWeather: llm.tool({
|
|
180
|
+
description: "Get the weather for a given location.",
|
|
181
|
+
parameters: z.object({ location: z.string() }),
|
|
182
|
+
execute: async () => "sunny"
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx });
|
|
186
|
+
for await (const _ of stream) {
|
|
187
|
+
}
|
|
188
|
+
expect(capturedParams == null ? void 0 : capturedParams.tools).toHaveLength(1);
|
|
189
|
+
const toolSchema = capturedParams.tools[0];
|
|
190
|
+
expect(toolSchema.name).toBe("getWeather");
|
|
191
|
+
expect(toolSchema.description).toBe("Get the weather for a given location.");
|
|
192
|
+
expect(toolSchema.input_schema.type).toBe("object");
|
|
193
|
+
expect(Object.keys(toolSchema.input_schema.properties ?? {})).toEqual(["location"]);
|
|
194
|
+
});
|
|
195
|
+
it("does not emit an empty delta for a fully-filtered thinking block", async () => {
|
|
196
|
+
var _a;
|
|
197
|
+
const client = {
|
|
198
|
+
messages: {
|
|
199
|
+
create: async () => async function* () {
|
|
200
|
+
yield messageStartEvent();
|
|
201
|
+
yield textDeltaEvent("<thinking>only reasoning</thinking>");
|
|
202
|
+
}()
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const anthropicLlm = new LLM({
|
|
206
|
+
apiKey: "dummy",
|
|
207
|
+
client,
|
|
208
|
+
model: "claude-3-5-sonnet-20241022"
|
|
209
|
+
});
|
|
210
|
+
const chatCtx = new llm.ChatContext();
|
|
211
|
+
chatCtx.addMessage({ role: "user", content: "Hello, world!" });
|
|
212
|
+
const stream = anthropicLlm.chat({ chatCtx, toolCtx: llm.ToolContext.empty() });
|
|
213
|
+
const contentChunks = [];
|
|
214
|
+
for await (const chunk of stream) {
|
|
215
|
+
if (((_a = chunk.delta) == null ? void 0 : _a.content) !== void 0) {
|
|
216
|
+
contentChunks.push(chunk.delta.content);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
expect(contentChunks).toEqual([]);
|
|
220
|
+
});
|
|
221
|
+
it("filters thinking blocks when tools are active", async () => {
|
|
222
|
+
const text = await collectTextFromEvents(
|
|
223
|
+
[
|
|
224
|
+
messageStartEvent(),
|
|
225
|
+
textDeltaEvent("<thinking>hidden"),
|
|
226
|
+
textDeltaEvent("still hidden</thinking>visible")
|
|
227
|
+
],
|
|
228
|
+
llm.ToolContext.empty()
|
|
229
|
+
);
|
|
230
|
+
expect(text).toBe("visible");
|
|
231
|
+
});
|
|
232
|
+
it("does not filter thinking blocks without tools", async () => {
|
|
233
|
+
const text = await collectTextFromEvents([
|
|
234
|
+
messageStartEvent(),
|
|
235
|
+
textDeltaEvent("<thinking>visible without tools</thinking>")
|
|
236
|
+
]);
|
|
237
|
+
expect(text).toBe("<thinking>visible without tools</thinking>");
|
|
238
|
+
});
|
|
239
|
+
it("preserves text around same-delta thinking blocks", async () => {
|
|
240
|
+
const text = await collectTextFromEvents(
|
|
241
|
+
[messageStartEvent(), textDeltaEvent("before <thinking>hidden</thinking> after")],
|
|
242
|
+
llm.ToolContext.empty()
|
|
243
|
+
);
|
|
244
|
+
expect(text).toBe("before after");
|
|
245
|
+
});
|
|
246
|
+
it("preserves text before split thinking blocks", async () => {
|
|
247
|
+
const text = await collectTextFromEvents(
|
|
248
|
+
[
|
|
249
|
+
messageStartEvent(),
|
|
250
|
+
textDeltaEvent("before <thinking>hidden"),
|
|
251
|
+
textDeltaEvent("still hidden</thinking> after")
|
|
252
|
+
],
|
|
253
|
+
llm.ToolContext.empty()
|
|
254
|
+
);
|
|
255
|
+
expect(text).toBe("before after");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
//# sourceMappingURL=llm.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { llm } from '@livekit/agents';\nimport { describe, expect, it } from 'vitest';\nimport { z } from 'zod';\nimport { LLM } from './llm.js';\n\nfunction messageStartEvent(): Anthropic.MessageStreamEvent {\n return {\n type: 'message_start',\n message: {\n id: 'msg_123',\n usage: { input_tokens: 0, output_tokens: 0 },\n },\n } as Anthropic.MessageStreamEvent;\n}\n\nfunction textDeltaEvent(text: string): Anthropic.MessageStreamEvent {\n return {\n type: 'content_block_delta',\n delta: { type: 'text_delta', text },\n } as Anthropic.MessageStreamEvent;\n}\n\nasync function collectTextFromEvents(\n events: Anthropic.MessageStreamEvent[],\n toolCtx?: llm.ToolContextLike,\n): Promise<string> {\n const client = {\n messages: {\n create: async () =>\n (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield* events;\n })(),\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx });\n const textChunks: string[] = [];\n for await (const chunk of stream) {\n if (chunk.delta?.content) {\n textChunks.push(chunk.delta.content);\n }\n }\n return textChunks.join('');\n}\n\ndescribe('Anthropic LLM', () => {\n it('correctly maps ChatContext to Anthropic system and messages arrays', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({\n role: 'system',\n content: 'You are a mock agent.',\n });\n chatCtx.addMessage({\n role: 'user',\n content: 'Hello, world!',\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { system, messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Assert that system prompts were correctly isolated\n expect(system).toHaveLength(1);\n expect(system[0].text).toBe('You are a mock agent.');\n\n // Assert that strictly user messages ended up in the messages payload\n expect(messages).toHaveLength(1);\n expect(messages[0].role).toBe('user');\n expect(messages[0].content).toBe('Hello, world!');\n });\n\n it('merges consecutive same-role messages', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'First message' });\n chatCtx.addMessage({ role: 'user', content: 'Second message' });\n chatCtx.addMessage({ role: 'assistant', content: 'Reply' });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Two user messages should be merged into one with array content, followed by a\n // trailing dummy user turn for Claude 4.6+.\n expect(messages).toHaveLength(3);\n expect(messages[0].role).toBe('user');\n expect(Array.isArray(messages[0].content)).toBe(true);\n expect(messages[0].content).toHaveLength(2);\n expect(messages[1].role).toBe('assistant');\n expect(messages[2].role).toBe('user');\n expect(messages[2].content).toEqual([{ type: 'text', text: '.' }]);\n });\n\n it('injects a dummy user message if conversation starts with assistant', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'system', content: 'System prompt' });\n chatCtx.addMessage({ role: 'assistant', content: 'I start talking' });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // Should have injected a dummy user message at the start\n expect(messages[0].role).toBe('user');\n expect(messages[0].content).toBe('(empty)');\n expect(messages[1].role).toBe('assistant');\n });\n\n it('handles function_call and function_call_output items', () => {\n const anthropicLlm = new LLM({ apiKey: 'dummy', model: 'claude-3-5-sonnet-20241022' });\n\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'What is the weather?' });\n\n // Simulate a function call from the assistant\n chatCtx.items.push(\n new llm.FunctionCall({\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"city\":\"London\"}',\n }),\n );\n\n // Simulate the tool result\n chatCtx.items.push(\n new llm.FunctionCallOutput({\n callId: 'call_123',\n name: 'get_weather',\n output: '{\"temp\": 20}',\n isError: false,\n }),\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { messages } = (anthropicLlm as any)._buildAnthropicContext(chatCtx);\n\n // user message, then assistant tool_use, then user tool_result\n expect(messages).toHaveLength(3);\n expect(messages[0].role).toBe('user');\n expect(messages[1].role).toBe('assistant');\n expect(Array.isArray(messages[1].content)).toBe(true);\n expect(messages[1].content[0].type).toBe('tool_use');\n expect(messages[1].content[0].id).toBe('call_123');\n expect(messages[2].role).toBe('user');\n expect(Array.isArray(messages[2].content)).toBe(true);\n expect(messages[2].content[0].type).toBe('tool_result');\n });\n\n it('creates a fresh stream on retry', async () => {\n let calls = 0;\n const client = {\n messages: {\n create: async () => {\n calls += 1;\n if (calls === 1) {\n throw new Error('transient connect failure');\n }\n return (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {})();\n },\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n anthropicLlm.on('error', () => {});\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({\n chatCtx,\n connOptions: { maxRetry: 1, retryIntervalMs: 0, timeoutMs: 1000 },\n });\n const chunks: llm.ChatChunk[] = [];\n for await (const chunk of stream) {\n chunks.push(chunk);\n }\n\n expect(calls).toBe(2);\n expect(chunks.at(-1)?.usage).toEqual({\n completionTokens: 0,\n promptTokens: 0,\n promptCachedTokens: 0,\n totalTokens: 0,\n });\n });\n\n it('sends function tool schemas from a real ToolContext', async () => {\n let capturedParams: Anthropic.MessageCreateParamsStreaming | undefined;\n const client = {\n messages: {\n create: async (params: Anthropic.MessageCreateParamsStreaming) => {\n capturedParams = params;\n return (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield messageStartEvent();\n })();\n },\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'What is the weather in Tokyo?' });\n\n const toolCtx = llm.toToolContext({\n getWeather: llm.tool({\n description: 'Get the weather for a given location.',\n parameters: z.object({ location: z.string() }),\n execute: async () => 'sunny',\n }),\n });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for await (const _ of stream) {\n // drain\n }\n\n expect(capturedParams?.tools).toHaveLength(1);\n const toolSchema = capturedParams!.tools![0] as Anthropic.Tool;\n expect(toolSchema.name).toBe('getWeather');\n expect(toolSchema.description).toBe('Get the weather for a given location.');\n expect(toolSchema.input_schema.type).toBe('object');\n expect(Object.keys(toolSchema.input_schema.properties ?? {})).toEqual(['location']);\n });\n\n it('does not emit an empty delta for a fully-filtered thinking block', async () => {\n const client = {\n messages: {\n create: async () =>\n (async function* (): AsyncGenerator<Anthropic.MessageStreamEvent> {\n yield messageStartEvent();\n yield textDeltaEvent('<thinking>only reasoning</thinking>');\n })(),\n },\n } as unknown as Anthropic;\n const anthropicLlm = new LLM({\n apiKey: 'dummy',\n client,\n model: 'claude-3-5-sonnet-20241022',\n });\n const chatCtx = new llm.ChatContext();\n chatCtx.addMessage({ role: 'user', content: 'Hello, world!' });\n\n const stream = anthropicLlm.chat({ chatCtx, toolCtx: llm.ToolContext.empty() });\n const contentChunks: string[] = [];\n for await (const chunk of stream) {\n if (chunk.delta?.content !== undefined) {\n contentChunks.push(chunk.delta.content);\n }\n }\n\n // The whole delta was a thinking block: nothing (not even '') is emitted.\n expect(contentChunks).toEqual([]);\n });\n\n it('filters thinking blocks when tools are active', async () => {\n const text = await collectTextFromEvents(\n [\n messageStartEvent(),\n textDeltaEvent('<thinking>hidden'),\n textDeltaEvent('still hidden</thinking>visible'),\n ],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('visible');\n });\n\n it('does not filter thinking blocks without tools', async () => {\n const text = await collectTextFromEvents([\n messageStartEvent(),\n textDeltaEvent('<thinking>visible without tools</thinking>'),\n ]);\n\n expect(text).toBe('<thinking>visible without tools</thinking>');\n });\n\n it('preserves text around same-delta thinking blocks', async () => {\n const text = await collectTextFromEvents(\n [messageStartEvent(), textDeltaEvent('before <thinking>hidden</thinking> after')],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('before after');\n });\n\n it('preserves text before split thinking blocks', async () => {\n const text = await collectTextFromEvents(\n [\n messageStartEvent(),\n textDeltaEvent('before <thinking>hidden'),\n textDeltaEvent('still hidden</thinking> after'),\n ],\n llm.ToolContext.empty(),\n );\n\n expect(text).toBe('before after');\n });\n});\n"],"mappings":"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","names":[]}
|
package/dist/models.cjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var models_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(models_exports);
|
|
17
|
+
//# sourceMappingURL=models.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/models.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Supported Anthropic Chat Models.\n *\n * @remarks\n * Based on https://docs.anthropic.com/en/docs/about-claude/model-deprecations\n */\nexport type ChatModels =\n | 'claude-3-5-sonnet-20241022'\n | 'claude-3-5-haiku-20241022'\n | 'claude-3-haiku-20240307'\n | 'claude-3-7-sonnet-20250219'\n | 'claude-sonnet-4-20250514'\n | 'claude-sonnet-4-6'\n | 'claude-opus-4-20250514'\n | 'claude-opus-4-1-20250805'\n | 'claude-opus-4-6'\n | (string & Record<never, never>);\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported Anthropic Chat Models.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Based on https://docs.anthropic.com/en/docs/about-claude/model-deprecations
|
|
6
|
+
*/
|
|
7
|
+
export type ChatModels = 'claude-3-5-sonnet-20241022' | 'claude-3-5-haiku-20241022' | 'claude-3-haiku-20240307' | 'claude-3-7-sonnet-20250219' | 'claude-sonnet-4-20250514' | 'claude-sonnet-4-6' | 'claude-opus-4-20250514' | 'claude-opus-4-1-20250805' | 'claude-opus-4-6' | (string & Record<never, never>);
|
|
8
|
+
//# sourceMappingURL=models.d.ts.map
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported Anthropic Chat Models.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Based on https://docs.anthropic.com/en/docs/about-claude/model-deprecations
|
|
6
|
+
*/
|
|
7
|
+
export type ChatModels = 'claude-3-5-sonnet-20241022' | 'claude-3-5-haiku-20241022' | 'claude-3-haiku-20240307' | 'claude-3-7-sonnet-20250219' | 'claude-sonnet-4-20250514' | 'claude-sonnet-4-6' | 'claude-opus-4-20250514' | 'claude-opus-4-1-20250805' | 'claude-opus-4-6' | (string & Record<never, never>);
|
|
8
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAClB,4BAA4B,GAC5B,2BAA2B,GAC3B,yBAAyB,GACzB,4BAA4B,GAC5B,0BAA0B,GAC1B,mBAAmB,GACnB,wBAAwB,GACxB,0BAA0B,GAC1B,iBAAiB,GACjB,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC"}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=models.js.map
|