livekit-plugins-anthropic 1.0.22__py3-none-any.whl → 1.1.0__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/llm.py +46 -19
- livekit/plugins/anthropic/utils.py +38 -131
- livekit/plugins/anthropic/version.py +1 -1
- {livekit_plugins_anthropic-1.0.22.dist-info → livekit_plugins_anthropic-1.1.0.dist-info}/METADATA +3 -2
- livekit_plugins_anthropic-1.1.0.dist-info/RECORD +10 -0
- livekit_plugins_anthropic-1.0.22.dist-info/RECORD +0 -10
- {livekit_plugins_anthropic-1.0.22.dist-info → livekit_plugins_anthropic-1.1.0.dist-info}/WHEEL +0 -0
livekit/plugins/anthropic/llm.py
CHANGED
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
17
17
|
import os
|
18
18
|
from collections.abc import Awaitable
|
19
19
|
from dataclasses import dataclass
|
20
|
-
from typing import Any, Literal
|
20
|
+
from typing import Any, Literal, cast
|
21
21
|
|
22
22
|
import httpx
|
23
23
|
|
@@ -25,7 +25,7 @@ import anthropic
|
|
25
25
|
from livekit.agents import APIConnectionError, APIStatusError, APITimeoutError, llm
|
26
26
|
from livekit.agents.llm import ToolChoice
|
27
27
|
from livekit.agents.llm.chat_context import ChatContext
|
28
|
-
from livekit.agents.llm.tool_context import FunctionTool
|
28
|
+
from livekit.agents.llm.tool_context import FunctionTool, RawFunctionTool
|
29
29
|
from livekit.agents.types import (
|
30
30
|
DEFAULT_API_CONNECT_OPTIONS,
|
31
31
|
NOT_GIVEN,
|
@@ -35,7 +35,7 @@ from livekit.agents.types import (
|
|
35
35
|
from livekit.agents.utils import is_given
|
36
36
|
|
37
37
|
from .models import ChatModels
|
38
|
-
from .utils import
|
38
|
+
from .utils import CACHE_CONTROL_EPHEMERAL, to_fnc_ctx
|
39
39
|
|
40
40
|
|
41
41
|
@dataclass
|
@@ -118,7 +118,7 @@ class LLM(llm.LLM):
|
|
118
118
|
self,
|
119
119
|
*,
|
120
120
|
chat_ctx: ChatContext,
|
121
|
-
tools: list[FunctionTool] | None = None,
|
121
|
+
tools: list[FunctionTool | RawFunctionTool] | None = None,
|
122
122
|
conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
|
123
123
|
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
|
124
124
|
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
|
@@ -141,8 +141,10 @@ class LLM(llm.LLM):
|
|
141
141
|
extra["max_tokens"] = self._opts.max_tokens if is_given(self._opts.max_tokens) else 1024
|
142
142
|
|
143
143
|
if tools:
|
144
|
-
extra["tools"] = to_fnc_ctx(tools, self._opts.caching)
|
145
|
-
tool_choice =
|
144
|
+
extra["tools"] = to_fnc_ctx(tools, self._opts.caching or None)
|
145
|
+
tool_choice = (
|
146
|
+
cast(ToolChoice, tool_choice) if is_given(tool_choice) else self._opts.tool_choice
|
147
|
+
)
|
146
148
|
if is_given(tool_choice):
|
147
149
|
anthropic_tool_choice: dict[str, Any] | None = {"type": "auto"}
|
148
150
|
if isinstance(tool_choice, dict) and tool_choice.get("type") == "function":
|
@@ -166,15 +168,38 @@ class LLM(llm.LLM):
|
|
166
168
|
anthropic_tool_choice["disable_parallel_tool_use"] = not parallel_tool_calls
|
167
169
|
extra["tool_choice"] = anthropic_tool_choice
|
168
170
|
|
169
|
-
anthropic_ctx,
|
170
|
-
|
171
|
-
if
|
172
|
-
extra["system"] = [
|
171
|
+
anthropic_ctx, extra_data = chat_ctx.to_provider_format(format="anthropic")
|
172
|
+
messages = cast(list[anthropic.types.MessageParam], anthropic_ctx)
|
173
|
+
if extra_data.system_messages:
|
174
|
+
extra["system"] = [
|
175
|
+
anthropic.types.TextBlockParam(text=content, type="text")
|
176
|
+
for content in extra_data.system_messages
|
177
|
+
]
|
178
|
+
|
179
|
+
# add cache control
|
180
|
+
if self._opts.caching == "ephemeral":
|
181
|
+
if extra.get("system"):
|
182
|
+
extra["system"][-1]["cache_control"] = CACHE_CONTROL_EPHEMERAL
|
183
|
+
|
184
|
+
seen_assistant = False
|
185
|
+
for msg in reversed(messages):
|
186
|
+
if (
|
187
|
+
msg["role"] == "assistant"
|
188
|
+
and (content := msg["content"])
|
189
|
+
and not seen_assistant
|
190
|
+
):
|
191
|
+
content[-1]["cache_control"] = CACHE_CONTROL_EPHEMERAL # type: ignore
|
192
|
+
seen_assistant = True
|
193
|
+
|
194
|
+
elif msg["role"] == "user" and (content := msg["content"]) and seen_assistant:
|
195
|
+
content[-1]["cache_control"] = CACHE_CONTROL_EPHEMERAL # type: ignore
|
196
|
+
break
|
173
197
|
|
174
198
|
stream = self._client.messages.create(
|
175
|
-
messages=
|
199
|
+
messages=messages,
|
176
200
|
model=self._opts.model,
|
177
201
|
stream=True,
|
202
|
+
timeout=conn_options.timeout,
|
178
203
|
**extra,
|
179
204
|
)
|
180
205
|
|
@@ -194,7 +219,7 @@ class LLMStream(llm.LLMStream):
|
|
194
219
|
*,
|
195
220
|
anthropic_stream: Awaitable[anthropic.AsyncStream[anthropic.types.RawMessageStreamEvent]],
|
196
221
|
chat_ctx: llm.ChatContext,
|
197
|
-
tools: list[FunctionTool],
|
222
|
+
tools: list[FunctionTool | RawFunctionTool],
|
198
223
|
conn_options: APIConnectOptions,
|
199
224
|
) -> None:
|
200
225
|
super().__init__(llm, chat_ctx=chat_ctx, tools=tools, conn_options=conn_options)
|
@@ -228,18 +253,20 @@ class LLMStream(llm.LLMStream):
|
|
228
253
|
self._event_ch.send_nowait(chat_chunk)
|
229
254
|
retryable = False
|
230
255
|
|
256
|
+
# https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#tracking-cache-performance
|
257
|
+
prompt_token = (
|
258
|
+
self._input_tokens + self._cache_creation_tokens + self._cache_read_tokens
|
259
|
+
)
|
231
260
|
self._event_ch.send_nowait(
|
232
261
|
llm.ChatChunk(
|
233
262
|
id=self._request_id,
|
234
263
|
usage=llm.CompletionUsage(
|
235
264
|
completion_tokens=self._output_tokens,
|
236
|
-
prompt_tokens=
|
237
|
-
total_tokens=self.
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
cache_creation_input_tokens=self._cache_creation_tokens,
|
242
|
-
cache_read_input_tokens=self._cache_read_tokens,
|
265
|
+
prompt_tokens=prompt_token,
|
266
|
+
total_tokens=prompt_token + self._output_tokens,
|
267
|
+
prompt_cached_tokens=self._cache_read_tokens,
|
268
|
+
cache_creation_tokens=self._cache_creation_tokens,
|
269
|
+
cache_read_tokens=self._cache_read_tokens,
|
243
270
|
),
|
244
271
|
)
|
245
272
|
)
|
@@ -1,147 +1,54 @@
|
|
1
|
-
import
|
2
|
-
import json
|
3
|
-
from typing import Any, Literal
|
1
|
+
from typing import Literal, Optional, Union
|
4
2
|
|
5
3
|
import anthropic
|
6
4
|
from livekit.agents import llm
|
7
|
-
from livekit.agents.llm import FunctionTool
|
8
|
-
|
5
|
+
from livekit.agents.llm import FunctionTool, RawFunctionTool
|
6
|
+
from livekit.agents.llm.tool_context import (
|
7
|
+
get_raw_function_info,
|
8
|
+
is_function_tool,
|
9
|
+
is_raw_function_tool,
|
10
|
+
)
|
11
|
+
|
12
|
+
# We can define up to 4 cache breakpoints, we will add them at:
|
13
|
+
# - the last tool definition
|
14
|
+
# - the last system message
|
15
|
+
# - the last assistant message
|
16
|
+
# - the last user message before the last assistant message
|
17
|
+
# https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#structuring-your-prompt
|
9
18
|
CACHE_CONTROL_EPHEMERAL = anthropic.types.CacheControlEphemeralParam(type="ephemeral")
|
10
19
|
|
11
|
-
__all__ = ["to_fnc_ctx", "
|
20
|
+
__all__ = ["to_fnc_ctx", "CACHE_CONTROL_EPHEMERAL"]
|
12
21
|
|
13
22
|
|
14
23
|
def to_fnc_ctx(
|
15
|
-
fncs: list[FunctionTool], caching: Literal["ephemeral"]
|
24
|
+
fncs: list[Union[FunctionTool, RawFunctionTool]], caching: Optional[Literal["ephemeral"]]
|
16
25
|
) -> list[anthropic.types.ToolParam]:
|
17
26
|
tools: list[anthropic.types.ToolParam] = []
|
18
|
-
for
|
19
|
-
|
20
|
-
CACHE_CONTROL_EPHEMERAL if (i == len(fncs) - 1) and caching == "ephemeral" else None
|
21
|
-
)
|
22
|
-
tools.append(_build_anthropic_schema(fnc, cache_ctrl=cache_ctrl))
|
23
|
-
|
24
|
-
return tools
|
25
|
-
|
26
|
-
|
27
|
-
def to_chat_ctx(
|
28
|
-
chat_ctx: llm.ChatContext,
|
29
|
-
cache_key: Any,
|
30
|
-
caching: Literal["ephemeral"] | None,
|
31
|
-
) -> list[anthropic.types.MessageParam]:
|
32
|
-
messages: list[anthropic.types.MessageParam] = []
|
33
|
-
system_message: anthropic.types.TextBlockParam | None = None
|
34
|
-
current_role: str | None = None
|
35
|
-
content: list[anthropic.types.TextBlockParam] = []
|
36
|
-
for i, msg in enumerate(chat_ctx.items):
|
37
|
-
if msg.type == "message" and msg.role == "system":
|
38
|
-
for content in msg.content:
|
39
|
-
if content and isinstance(content, str):
|
40
|
-
system_message = anthropic.types.TextBlockParam(
|
41
|
-
text=content,
|
42
|
-
type="text",
|
43
|
-
cache_control=CACHE_CONTROL_EPHEMERAL if caching == "ephemeral" else None,
|
44
|
-
)
|
45
|
-
continue
|
46
|
-
|
47
|
-
cache_ctrl = (
|
48
|
-
CACHE_CONTROL_EPHEMERAL
|
49
|
-
if (i == len(chat_ctx.items) - 1) and caching == "ephemeral"
|
50
|
-
else None
|
51
|
-
)
|
52
|
-
if msg.type == "message":
|
53
|
-
role = "assistant" if msg.role == "assistant" else "user"
|
54
|
-
elif msg.type == "function_call":
|
55
|
-
role = "assistant"
|
56
|
-
elif msg.type == "function_call_output":
|
57
|
-
role = "user"
|
58
|
-
|
59
|
-
if role != current_role:
|
60
|
-
if current_role is not None and content:
|
61
|
-
messages.append(anthropic.types.MessageParam(role=current_role, content=content))
|
62
|
-
content = []
|
63
|
-
current_role = role
|
27
|
+
for fnc in fncs:
|
28
|
+
tools.append(_build_anthropic_schema(fnc))
|
64
29
|
|
65
|
-
|
66
|
-
|
67
|
-
if c and isinstance(c, str):
|
68
|
-
content.append(
|
69
|
-
anthropic.types.TextBlockParam(
|
70
|
-
text=c, type="text", cache_control=cache_ctrl
|
71
|
-
)
|
72
|
-
)
|
73
|
-
elif isinstance(c, llm.ImageContent):
|
74
|
-
content.append(_to_image_content(c, cache_key, cache_ctrl=cache_ctrl))
|
75
|
-
elif msg.type == "function_call":
|
76
|
-
content.append(
|
77
|
-
anthropic.types.ToolUseBlockParam(
|
78
|
-
id=msg.call_id,
|
79
|
-
type="tool_use",
|
80
|
-
name=msg.name,
|
81
|
-
input=json.loads(msg.arguments or "{}"),
|
82
|
-
cache_control=cache_ctrl,
|
83
|
-
)
|
84
|
-
)
|
85
|
-
elif msg.type == "function_call_output":
|
86
|
-
content.append(
|
87
|
-
anthropic.types.ToolResultBlockParam(
|
88
|
-
tool_use_id=msg.call_id,
|
89
|
-
type="tool_result",
|
90
|
-
content=msg.output,
|
91
|
-
cache_control=cache_ctrl,
|
92
|
-
)
|
93
|
-
)
|
30
|
+
if tools and caching == "ephemeral":
|
31
|
+
tools[-1]["cache_control"] = CACHE_CONTROL_EPHEMERAL
|
94
32
|
|
95
|
-
|
96
|
-
messages.append(anthropic.types.MessageParam(role=current_role, content=content))
|
97
|
-
|
98
|
-
# ensure the messages starts with a "user" message
|
99
|
-
if not messages or messages[0]["role"] != "user":
|
100
|
-
messages.insert(
|
101
|
-
0,
|
102
|
-
anthropic.types.MessageParam(
|
103
|
-
role="user",
|
104
|
-
content=[anthropic.types.TextBlockParam(text="(empty)", type="text")],
|
105
|
-
),
|
106
|
-
)
|
107
|
-
|
108
|
-
return messages, system_message
|
109
|
-
|
110
|
-
|
111
|
-
def _to_image_content(
|
112
|
-
image: llm.ImageContent,
|
113
|
-
cache_key: Any,
|
114
|
-
cache_ctrl: anthropic.types.CacheControlEphemeralParam | None,
|
115
|
-
) -> anthropic.types.ImageBlockParam:
|
116
|
-
img = llm.utils.serialize_image(image)
|
117
|
-
if img.external_url:
|
118
|
-
return {
|
119
|
-
"type": "image",
|
120
|
-
"source": {"type": "url", "url": img.external_url},
|
121
|
-
"cache_control": cache_ctrl,
|
122
|
-
}
|
123
|
-
if cache_key not in image._cache:
|
124
|
-
image._cache[cache_key] = img.data_bytes
|
125
|
-
b64_data = base64.b64encode(image._cache[cache_key]).decode("utf-8")
|
126
|
-
return {
|
127
|
-
"type": "image",
|
128
|
-
"source": {
|
129
|
-
"type": "base64",
|
130
|
-
"data": f"data:{img.mime_type};base64,{b64_data}",
|
131
|
-
"media_type": img.mime_type,
|
132
|
-
},
|
133
|
-
"cache_control": cache_ctrl,
|
134
|
-
}
|
33
|
+
return tools
|
135
34
|
|
136
35
|
|
137
36
|
def _build_anthropic_schema(
|
138
|
-
function_tool: FunctionTool,
|
139
|
-
cache_ctrl: anthropic.types.CacheControlEphemeralParam | None = None,
|
37
|
+
function_tool: Union[FunctionTool, RawFunctionTool],
|
140
38
|
) -> anthropic.types.ToolParam:
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
39
|
+
if is_function_tool(function_tool):
|
40
|
+
fnc = llm.utils.build_legacy_openai_schema(function_tool, internally_tagged=True)
|
41
|
+
return anthropic.types.ToolParam(
|
42
|
+
name=fnc["name"],
|
43
|
+
description=fnc["description"] or "",
|
44
|
+
input_schema=fnc["parameters"],
|
45
|
+
)
|
46
|
+
elif is_raw_function_tool(function_tool):
|
47
|
+
info = get_raw_function_info(function_tool)
|
48
|
+
return anthropic.types.ToolParam(
|
49
|
+
name=info.name,
|
50
|
+
description=info.raw_schema.get("description", ""),
|
51
|
+
input_schema=info.raw_schema.get("parameters", {}),
|
52
|
+
)
|
53
|
+
else:
|
54
|
+
raise ValueError("Invalid function tool")
|
{livekit_plugins_anthropic-1.0.22.dist-info → livekit_plugins_anthropic-1.1.0.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: livekit-plugins-anthropic
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
4
4
|
Summary: Agent Framework plugin for services from Anthropic
|
5
5
|
Project-URL: Documentation, https://docs.livekit.io
|
6
6
|
Project-URL: Website, https://livekit.io/
|
@@ -19,7 +19,8 @@ Classifier: Topic :: Multimedia :: Video
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
20
20
|
Requires-Python: >=3.9.0
|
21
21
|
Requires-Dist: anthropic>=0.41
|
22
|
-
Requires-Dist:
|
22
|
+
Requires-Dist: httpx
|
23
|
+
Requires-Dist: livekit-agents>=1.1.0
|
23
24
|
Description-Content-Type: text/markdown
|
24
25
|
|
25
26
|
# Anthropic plugin for LiveKit Agents
|
@@ -0,0 +1,10 @@
|
|
1
|
+
livekit/plugins/anthropic/__init__.py,sha256=oZc3LY5BrjXy-hYMb0wyvXgqXRW-ikAlo5xGh2gM0Nc,1304
|
2
|
+
livekit/plugins/anthropic/llm.py,sha256=FhzN2ualvDu48iktJIm7MFNR_i9T06QPX8USAtDBBuY,14217
|
3
|
+
livekit/plugins/anthropic/log.py,sha256=fG1pYSY88AnT738gZrmzF9FO4l4BdGENj3VKHMQB3Yo,72
|
4
|
+
livekit/plugins/anthropic/models.py,sha256=wyTr2nl6SL4ylN6s4mHJcqtmgV2mjJysZo89FknWdhI,213
|
5
|
+
livekit/plugins/anthropic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
livekit/plugins/anthropic/utils.py,sha256=CojebB7CWjX6JyObIrM8eRLgli07NGnp84mGDPrLQ1s,1907
|
7
|
+
livekit/plugins/anthropic/version.py,sha256=7SjyflIFTjH0djSotKGIRoRykPCqMpVYetIlvHMFuh0,600
|
8
|
+
livekit_plugins_anthropic-1.1.0.dist-info/METADATA,sha256=u9CAQed356V3xbZxbjRKH-02tAq2UZEMhfHjfr-0HY0,1452
|
9
|
+
livekit_plugins_anthropic-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
livekit_plugins_anthropic-1.1.0.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
livekit/plugins/anthropic/__init__.py,sha256=oZc3LY5BrjXy-hYMb0wyvXgqXRW-ikAlo5xGh2gM0Nc,1304
|
2
|
-
livekit/plugins/anthropic/llm.py,sha256=Q0U5ZIufGtR5abMdfMkZrANz7Ri4RxZLQYocDZOhK1Y,12884
|
3
|
-
livekit/plugins/anthropic/log.py,sha256=fG1pYSY88AnT738gZrmzF9FO4l4BdGENj3VKHMQB3Yo,72
|
4
|
-
livekit/plugins/anthropic/models.py,sha256=wyTr2nl6SL4ylN6s4mHJcqtmgV2mjJysZo89FknWdhI,213
|
5
|
-
livekit/plugins/anthropic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
livekit/plugins/anthropic/utils.py,sha256=Nfl9dGCZGDEJAHj_f-TmePr8bKJrc8IwM6Houjev4DE,5158
|
7
|
-
livekit/plugins/anthropic/version.py,sha256=-8dkOE2vDSF9WN8VoBrSwU2sb5YBGFuwPnSQXQ-uaYM,601
|
8
|
-
livekit_plugins_anthropic-1.0.22.dist-info/METADATA,sha256=k3W_EpA4q1RQSlsDN-3CSa-VcqJ45upY7eV5jhI8xgY,1433
|
9
|
-
livekit_plugins_anthropic-1.0.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
-
livekit_plugins_anthropic-1.0.22.dist-info/RECORD,,
|
{livekit_plugins_anthropic-1.0.22.dist-info → livekit_plugins_anthropic-1.1.0.dist-info}/WHEEL
RENAMED
File without changes
|