livekit-plugins-anthropic 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
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.
- livekit/plugins/anthropic/__init__.py +9 -0
- livekit/plugins/anthropic/llm.py +152 -95
- livekit/plugins/anthropic/version.py +1 -1
- {livekit_plugins_anthropic-0.2.0.dist-info → livekit_plugins_anthropic-0.2.2.dist-info}/METADATA +3 -3
- livekit_plugins_anthropic-0.2.2.dist-info/RECORD +10 -0
- {livekit_plugins_anthropic-0.2.0.dist-info → livekit_plugins_anthropic-0.2.2.dist-info}/WHEEL +1 -1
- livekit_plugins_anthropic-0.2.0.dist-info/RECORD +0 -10
- {livekit_plugins_anthropic-0.2.0.dist-info → livekit_plugins_anthropic-0.2.2.dist-info}/top_level.txt +0 -0
@@ -35,3 +35,12 @@ class AnthropicPlugin(Plugin):
|
|
35
35
|
|
36
36
|
|
37
37
|
Plugin.register_plugin(AnthropicPlugin())
|
38
|
+
|
39
|
+
# Cleanup docs of unexported modules
|
40
|
+
_module = dir()
|
41
|
+
NOT_IN_ALL = [m for m in _module if m not in __all__]
|
42
|
+
|
43
|
+
__pdoc__ = {}
|
44
|
+
|
45
|
+
for n in NOT_IN_ALL:
|
46
|
+
__pdoc__[n] = False
|
livekit/plugins/anthropic/llm.py
CHANGED
@@ -23,7 +23,13 @@ from typing import Any, Awaitable, List, Tuple, get_args, get_origin
|
|
23
23
|
|
24
24
|
import httpx
|
25
25
|
from livekit import rtc
|
26
|
-
from livekit.agents import
|
26
|
+
from livekit.agents import (
|
27
|
+
APIConnectionError,
|
28
|
+
APIStatusError,
|
29
|
+
APITimeoutError,
|
30
|
+
llm,
|
31
|
+
utils,
|
32
|
+
)
|
27
33
|
|
28
34
|
import anthropic
|
29
35
|
|
@@ -37,17 +43,19 @@ from .models import (
|
|
37
43
|
class LLMOptions:
|
38
44
|
model: str | ChatModels
|
39
45
|
user: str | None
|
46
|
+
temperature: float | None
|
40
47
|
|
41
48
|
|
42
49
|
class LLM(llm.LLM):
|
43
50
|
def __init__(
|
44
51
|
self,
|
45
52
|
*,
|
46
|
-
model: str | ChatModels = "claude-3-
|
53
|
+
model: str | ChatModels = "claude-3-haiku-20240307",
|
47
54
|
api_key: str | None = None,
|
48
55
|
base_url: str | None = None,
|
49
56
|
user: str | None = None,
|
50
57
|
client: anthropic.AsyncClient | None = None,
|
58
|
+
temperature: float | None = None,
|
51
59
|
) -> None:
|
52
60
|
"""
|
53
61
|
Create a new instance of Anthropic LLM.
|
@@ -55,13 +63,14 @@ class LLM(llm.LLM):
|
|
55
63
|
``api_key`` must be set to your Anthropic API key, either using the argument or by setting
|
56
64
|
the ``ANTHROPIC_API_KEY`` environmental variable.
|
57
65
|
"""
|
66
|
+
super().__init__()
|
58
67
|
|
59
68
|
# throw an error on our end
|
60
69
|
api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
|
61
70
|
if api_key is None:
|
62
71
|
raise ValueError("Anthropic API key is required")
|
63
72
|
|
64
|
-
self._opts = LLMOptions(model=model, user=user)
|
73
|
+
self._opts = LLMOptions(model=model, user=user, temperature=temperature)
|
65
74
|
self._client = client or anthropic.AsyncClient(
|
66
75
|
api_key=api_key,
|
67
76
|
base_url=base_url,
|
@@ -85,6 +94,9 @@ class LLM(llm.LLM):
|
|
85
94
|
n: int | None = 1,
|
86
95
|
parallel_tool_calls: bool | None = None,
|
87
96
|
) -> "LLMStream":
|
97
|
+
if temperature is None:
|
98
|
+
temperature = self._opts.temperature
|
99
|
+
|
88
100
|
opts: dict[str, Any] = dict()
|
89
101
|
if fnc_ctx and len(fnc_ctx.ai_functions) > 0:
|
90
102
|
fncs_desc: list[anthropic.types.ToolParam] = []
|
@@ -100,7 +112,7 @@ class LLM(llm.LLM):
|
|
100
112
|
anthropic_ctx = _build_anthropic_context(chat_ctx.messages, id(self))
|
101
113
|
collaped_anthropic_ctx = _merge_messages(anthropic_ctx)
|
102
114
|
stream = self._client.messages.create(
|
103
|
-
max_tokens=opts.get("max_tokens",
|
115
|
+
max_tokens=opts.get("max_tokens", 1024),
|
104
116
|
system=latest_system_message,
|
105
117
|
messages=collaped_anthropic_ctx,
|
106
118
|
model=self._opts.model,
|
@@ -110,12 +122,15 @@ class LLM(llm.LLM):
|
|
110
122
|
**opts,
|
111
123
|
)
|
112
124
|
|
113
|
-
return LLMStream(
|
125
|
+
return LLMStream(
|
126
|
+
self, anthropic_stream=stream, chat_ctx=chat_ctx, fnc_ctx=fnc_ctx
|
127
|
+
)
|
114
128
|
|
115
129
|
|
116
130
|
class LLMStream(llm.LLMStream):
|
117
131
|
def __init__(
|
118
132
|
self,
|
133
|
+
llm: LLM,
|
119
134
|
*,
|
120
135
|
anthropic_stream: Awaitable[
|
121
136
|
anthropic.AsyncStream[anthropic.types.RawMessageStreamEvent]
|
@@ -123,7 +138,7 @@ class LLMStream(llm.LLMStream):
|
|
123
138
|
chat_ctx: llm.ChatContext,
|
124
139
|
fnc_ctx: llm.FunctionContext | None,
|
125
140
|
) -> None:
|
126
|
-
super().__init__(chat_ctx=chat_ctx, fnc_ctx=fnc_ctx)
|
141
|
+
super().__init__(llm, chat_ctx=chat_ctx, fnc_ctx=fnc_ctx)
|
127
142
|
self._awaitable_anthropic_stream = anthropic_stream
|
128
143
|
self._anthropic_stream: (
|
129
144
|
anthropic.AsyncStream[anthropic.types.RawMessageStreamEvent] | None
|
@@ -134,70 +149,113 @@ class LLMStream(llm.LLMStream):
|
|
134
149
|
self._fnc_name: str | None = None
|
135
150
|
self._fnc_raw_arguments: str | None = None
|
136
151
|
|
137
|
-
|
138
|
-
|
139
|
-
|
152
|
+
self._request_id: str = ""
|
153
|
+
self._ignoring_cot = False # ignore chain of thought
|
154
|
+
self._input_tokens = 0
|
155
|
+
self._output_tokens = 0
|
140
156
|
|
141
|
-
|
142
|
-
|
143
|
-
async def __anext__(self):
|
157
|
+
async def _main_task(self) -> None:
|
144
158
|
if not self._anthropic_stream:
|
145
159
|
self._anthropic_stream = await self._awaitable_anthropic_stream
|
146
160
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
return llm.ChatChunk(
|
163
|
-
choices=[
|
164
|
-
llm.Choice(
|
165
|
-
delta=llm.ChoiceDelta(
|
166
|
-
content=delta.text, role="assistant"
|
167
|
-
)
|
168
|
-
)
|
169
|
-
]
|
170
|
-
)
|
171
|
-
elif delta.type == "input_json_delta":
|
172
|
-
assert self._fnc_raw_arguments is not None
|
173
|
-
self._fnc_raw_arguments += delta.partial_json
|
174
|
-
elif event.type == "content_block_stop":
|
175
|
-
if self._tool_call_id is not None and self._fnc_ctx:
|
176
|
-
assert self._fnc_name is not None
|
177
|
-
assert self._fnc_raw_arguments is not None
|
178
|
-
fnc_info = _create_ai_function_info(
|
179
|
-
self._fnc_ctx,
|
180
|
-
self._tool_call_id,
|
181
|
-
self._fnc_name,
|
182
|
-
self._fnc_raw_arguments,
|
183
|
-
)
|
184
|
-
self._function_calls_info.append(fnc_info)
|
185
|
-
chunk = llm.ChatChunk(
|
186
|
-
choices=[
|
187
|
-
llm.Choice(
|
188
|
-
delta=llm.ChoiceDelta(
|
189
|
-
role="assistant", tool_calls=[fnc_info]
|
190
|
-
),
|
191
|
-
index=0,
|
192
|
-
)
|
193
|
-
]
|
161
|
+
try:
|
162
|
+
async with self._anthropic_stream as stream:
|
163
|
+
async for event in stream:
|
164
|
+
chat_chunk = self._parse_event(event)
|
165
|
+
if chat_chunk is not None:
|
166
|
+
self._event_ch.send_nowait(chat_chunk)
|
167
|
+
|
168
|
+
self._event_ch.send_nowait(
|
169
|
+
llm.ChatChunk(
|
170
|
+
request_id=self._request_id,
|
171
|
+
usage=llm.CompletionUsage(
|
172
|
+
completion_tokens=self._output_tokens,
|
173
|
+
prompt_tokens=self._input_tokens,
|
174
|
+
total_tokens=self._input_tokens + self._output_tokens,
|
175
|
+
),
|
194
176
|
)
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
177
|
+
)
|
178
|
+
except anthropic.APITimeoutError:
|
179
|
+
raise APITimeoutError()
|
180
|
+
except anthropic.APIStatusError as e:
|
181
|
+
raise APIStatusError(
|
182
|
+
e.message,
|
183
|
+
status_code=e.status_code,
|
184
|
+
request_id=e.request_id,
|
185
|
+
body=e.body,
|
186
|
+
)
|
187
|
+
except Exception as e:
|
188
|
+
raise APIConnectionError() from e
|
189
|
+
|
190
|
+
def _parse_event(
|
191
|
+
self, event: anthropic.types.RawMessageStreamEvent
|
192
|
+
) -> llm.ChatChunk | None:
|
193
|
+
if event.type == "message_start":
|
194
|
+
self._request_id = event.message.id
|
195
|
+
self._input_tokens = event.message.usage.input_tokens
|
196
|
+
self._output_tokens = event.message.usage.output_tokens
|
197
|
+
elif event.type == "message_delta":
|
198
|
+
self._output_tokens += event.usage.output_tokens
|
199
|
+
elif event.type == "content_block_start":
|
200
|
+
if event.content_block.type == "tool_use":
|
201
|
+
self._tool_call_id = event.content_block.id
|
202
|
+
self._fnc_name = event.content_block.name
|
203
|
+
self._fnc_raw_arguments = ""
|
204
|
+
elif event.type == "content_block_delta":
|
205
|
+
delta = event.delta
|
206
|
+
if delta.type == "text_delta":
|
207
|
+
text = delta.text
|
208
|
+
|
209
|
+
if self._fnc_ctx is not None:
|
210
|
+
# anthropic may inject COC when using functions
|
211
|
+
if text.startswith("<thinking>"):
|
212
|
+
self._ignoring_cot = True
|
213
|
+
elif self._ignoring_cot and "</thinking>" in text:
|
214
|
+
text = text.split("</thinking>")[-1]
|
215
|
+
self._ignoring_cot = False
|
216
|
+
|
217
|
+
if self._ignoring_cot:
|
218
|
+
return None
|
219
|
+
|
220
|
+
return llm.ChatChunk(
|
221
|
+
request_id=self._request_id,
|
222
|
+
choices=[
|
223
|
+
llm.Choice(
|
224
|
+
delta=llm.ChoiceDelta(content=text, role="assistant")
|
225
|
+
)
|
226
|
+
],
|
227
|
+
)
|
228
|
+
elif delta.type == "input_json_delta":
|
229
|
+
assert self._fnc_raw_arguments is not None
|
230
|
+
self._fnc_raw_arguments += delta.partial_json
|
231
|
+
|
232
|
+
elif event.type == "content_block_stop":
|
233
|
+
if self._tool_call_id is not None and self._fnc_ctx:
|
234
|
+
assert self._fnc_name is not None
|
235
|
+
assert self._fnc_raw_arguments is not None
|
236
|
+
|
237
|
+
fnc_info = _create_ai_function_info(
|
238
|
+
self._fnc_ctx,
|
239
|
+
self._tool_call_id,
|
240
|
+
self._fnc_name,
|
241
|
+
self._fnc_raw_arguments,
|
242
|
+
)
|
243
|
+
self._function_calls_info.append(fnc_info)
|
244
|
+
|
245
|
+
chat_chunk = llm.ChatChunk(
|
246
|
+
request_id=self._request_id,
|
247
|
+
choices=[
|
248
|
+
llm.Choice(
|
249
|
+
delta=llm.ChoiceDelta(
|
250
|
+
role="assistant", tool_calls=[fnc_info]
|
251
|
+
),
|
252
|
+
)
|
253
|
+
],
|
254
|
+
)
|
255
|
+
self._tool_call_id = self._fnc_raw_arguments = self._fnc_name = None
|
256
|
+
return chat_chunk
|
199
257
|
|
200
|
-
|
258
|
+
return None
|
201
259
|
|
202
260
|
|
203
261
|
def _latest_system_message(chat_ctx: llm.ChatContext) -> str:
|
@@ -249,13 +307,15 @@ def _build_anthropic_context(
|
|
249
307
|
) -> List[anthropic.types.MessageParam]:
|
250
308
|
result: List[anthropic.types.MessageParam] = []
|
251
309
|
for msg in chat_ctx:
|
252
|
-
a_msg = _build_anthropic_message(msg, cache_key)
|
310
|
+
a_msg = _build_anthropic_message(msg, cache_key, chat_ctx)
|
253
311
|
if a_msg:
|
254
312
|
result.append(a_msg)
|
255
313
|
return result
|
256
314
|
|
257
315
|
|
258
|
-
def _build_anthropic_message(
|
316
|
+
def _build_anthropic_message(
|
317
|
+
msg: llm.ChatMessage, cache_key: Any, chat_ctx: List[llm.ChatMessage]
|
318
|
+
) -> anthropic.types.MessageParam | None:
|
259
319
|
if msg.role == "user" or msg.role == "assistant":
|
260
320
|
a_msg: anthropic.types.MessageParam = {
|
261
321
|
"role": msg.role,
|
@@ -282,38 +342,35 @@ def _build_anthropic_message(msg: llm.ChatMessage, cache_key: Any):
|
|
282
342
|
a_content.append(content)
|
283
343
|
elif isinstance(cnt, llm.ChatImage):
|
284
344
|
a_content.append(_build_anthropic_image_content(cnt, cache_key))
|
285
|
-
|
286
|
-
elif msg.role == "tool":
|
287
|
-
ant_msg: anthropic.types.MessageParam = {
|
288
|
-
"role": "assistant",
|
289
|
-
"content": [],
|
290
|
-
}
|
291
|
-
assert isinstance(ant_msg["content"], list)
|
292
|
-
# make sure to provide when function has been called inside the context
|
293
|
-
# (+ raw_arguments)
|
345
|
+
|
294
346
|
if msg.tool_calls is not None:
|
295
347
|
for fnc in msg.tool_calls:
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
"name": fnc.function_info.name,
|
302
|
-
}
|
348
|
+
tool_use = anthropic.types.ToolUseBlockParam(
|
349
|
+
id=fnc.tool_call_id,
|
350
|
+
type="tool_use",
|
351
|
+
name=fnc.function_info.name,
|
352
|
+
input=fnc.arguments,
|
303
353
|
)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
354
|
+
a_content.append(tool_use)
|
355
|
+
|
356
|
+
return a_msg
|
357
|
+
elif msg.role == "tool":
|
358
|
+
if not isinstance(msg.content, str):
|
359
|
+
logger.warning("tool message content is not a string")
|
360
|
+
return None
|
361
|
+
if not msg.tool_call_id:
|
362
|
+
return None
|
363
|
+
|
364
|
+
u_content = anthropic.types.ToolResultBlockParam(
|
365
|
+
tool_use_id=msg.tool_call_id,
|
366
|
+
type="tool_result",
|
367
|
+
content=msg.content,
|
368
|
+
is_error=msg.tool_exception is not None,
|
369
|
+
)
|
370
|
+
return {
|
371
|
+
"role": "user",
|
372
|
+
"content": [u_content],
|
373
|
+
}
|
317
374
|
|
318
375
|
return None
|
319
376
|
|
{livekit_plugins_anthropic-0.2.0.dist-info → livekit_plugins_anthropic-0.2.2.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: livekit-plugins-anthropic
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: Agent Framework plugin for services from Anthropic
|
5
5
|
Home-page: https://github.com/livekit/agents
|
6
6
|
License: Apache-2.0
|
@@ -19,8 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
20
20
|
Requires-Python: >=3.9.0
|
21
21
|
Description-Content-Type: text/markdown
|
22
|
-
Requires-Dist: livekit-agents
|
23
|
-
Requires-Dist: anthropic
|
22
|
+
Requires-Dist: livekit-agents >=0.11
|
23
|
+
Requires-Dist: anthropic >=0.34
|
24
24
|
|
25
25
|
# LiveKit Plugins Anthropic
|
26
26
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
livekit/plugins/anthropic/__init__.py,sha256=1WCyNEaR6qBsX54qJQM0SeY-QHIucww16PLXcSnMqRo,1175
|
2
|
+
livekit/plugins/anthropic/llm.py,sha256=mcTBYT3_ZVAWx9ZnCUj_96NM44dF6SF1R0ZLMUQt79Y,18888
|
3
|
+
livekit/plugins/anthropic/log.py,sha256=fG1pYSY88AnT738gZrmzF9FO4l4BdGENj3VKHMQB3Yo,72
|
4
|
+
livekit/plugins/anthropic/models.py,sha256=AVEhrEtKfWxsd-R03u7R74hcKjJq4oDVSTukvoPQGb0,179
|
5
|
+
livekit/plugins/anthropic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
livekit/plugins/anthropic/version.py,sha256=vseqf_ctDD3TBGPSArXT_dFZvNHkuJc4_8GgQSvrKrM,600
|
7
|
+
livekit_plugins_anthropic-0.2.2.dist-info/METADATA,sha256=KWqm8V_Ooo_sGKJ2JNAVrfOCu1sfuSLLBHkOC4tZHfQ,1265
|
8
|
+
livekit_plugins_anthropic-0.2.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
9
|
+
livekit_plugins_anthropic-0.2.2.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
+
livekit_plugins_anthropic-0.2.2.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
livekit/plugins/anthropic/__init__.py,sha256=g6KUqOfZo9DIBwBD98u6QOWY7pr8ZYJJ61fk3AWpoa4,1006
|
2
|
-
livekit/plugins/anthropic/llm.py,sha256=SJo_opc9_2rKYvcDW8-ltuOD-p7QUc0oROGDHu04htY,17162
|
3
|
-
livekit/plugins/anthropic/log.py,sha256=fG1pYSY88AnT738gZrmzF9FO4l4BdGENj3VKHMQB3Yo,72
|
4
|
-
livekit/plugins/anthropic/models.py,sha256=AVEhrEtKfWxsd-R03u7R74hcKjJq4oDVSTukvoPQGb0,179
|
5
|
-
livekit/plugins/anthropic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
livekit/plugins/anthropic/version.py,sha256=cLFCdnm5S21CiJ5UJBcqfRvvFkCQ8p6M5fFUJVJkEiM,600
|
7
|
-
livekit_plugins_anthropic-0.2.0.dist-info/METADATA,sha256=1VWzsOFCxwtoB2m-NVZgKPoPI8xwsZctTbZJO8FYxbI,1264
|
8
|
-
livekit_plugins_anthropic-0.2.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
9
|
-
livekit_plugins_anthropic-0.2.0.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
-
livekit_plugins_anthropic-0.2.0.dist-info/RECORD,,
|
File without changes
|