livekit-plugins-anthropic 0.2.1__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.
@@ -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
@@ -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 llm, utils
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,6 +43,7 @@ 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):
@@ -48,6 +55,7 @@ class LLM(llm.LLM):
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", 1000),
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(anthropic_stream=stream, chat_ctx=chat_ctx, fnc_ctx=fnc_ctx)
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,89 +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
- async def aclose(self) -> None:
138
- if self._anthropic_stream:
139
- await self._anthropic_stream.close()
140
-
141
- return await super().aclose()
152
+ self._request_id: str = ""
153
+ self._ignoring_cot = False # ignore chain of thought
154
+ self._input_tokens = 0
155
+ self._output_tokens = 0
142
156
 
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
- fn_calling_enabled = self._fnc_ctx is not None
148
- ignore = False
149
-
150
- async for event in self._anthropic_stream:
151
- if event.type == "message_start":
152
- pass
153
- elif event.type == "message_delta":
154
- pass
155
- elif event.type == "message_stop":
156
- pass
157
- elif event.type == "content_block_start":
158
- if event.content_block.type == "tool_use":
159
- self._tool_call_id = event.content_block.id
160
- self._fnc_raw_arguments = ""
161
- self._fnc_name = event.content_block.name
162
- elif event.type == "content_block_delta":
163
- delta = event.delta
164
- if delta.type == "text_delta":
165
- text = delta.text
166
-
167
- # Anthropic seems to add a prompt when tool calling is enabled
168
- # where responses always start with a "<thinking>" block containing
169
- # the LLM's chain of thought. It's very verbose and not useful for voice
170
- # applications.
171
- if fn_calling_enabled:
172
- if text.startswith("<thinking>"):
173
- ignore = True
174
-
175
- if "</thinking>" in text:
176
- text = text.split("</thinking>")[-1]
177
- ignore = False
178
-
179
- if ignore:
180
- continue
181
-
182
- return llm.ChatChunk(
183
- choices=[
184
- llm.Choice(
185
- delta=llm.ChoiceDelta(content=text, role="assistant")
186
- )
187
- ]
188
- )
189
- elif delta.type == "input_json_delta":
190
- assert self._fnc_raw_arguments is not None
191
- self._fnc_raw_arguments += delta.partial_json
192
-
193
- elif event.type == "content_block_stop":
194
- if self._tool_call_id is not None and self._fnc_ctx:
195
- assert self._fnc_name is not None
196
- assert self._fnc_raw_arguments is not None
197
- fnc_info = _create_ai_function_info(
198
- self._fnc_ctx,
199
- self._tool_call_id,
200
- self._fnc_name,
201
- self._fnc_raw_arguments,
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
+ ),
202
176
  )
203
- self._function_calls_info.append(fnc_info)
204
- chunk = llm.ChatChunk(
205
- choices=[
206
- llm.Choice(
207
- delta=llm.ChoiceDelta(
208
- role="assistant", tool_calls=[fnc_info]
209
- ),
210
- index=0,
211
- )
212
- ]
213
- )
214
- self._tool_call_id = None
215
- self._fnc_raw_arguments = None
216
- self._fnc_name = None
217
- return chunk
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
218
257
 
219
- raise StopAsyncIteration
258
+ return None
220
259
 
221
260
 
222
261
  def _latest_system_message(chat_ctx: llm.ChatContext) -> str:
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.2.1"
15
+ __version__ = "0.2.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: livekit-plugins-anthropic
3
- Version: 0.2.1
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 ~=0.8
23
- Requires-Dist: anthropic ~=0.34
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- livekit/plugins/anthropic/__init__.py,sha256=g6KUqOfZo9DIBwBD98u6QOWY7pr8ZYJJ61fk3AWpoa4,1006
2
- livekit/plugins/anthropic/llm.py,sha256=s6Xt_5kuvypG1ZLUq_aYaYO_ZaTgagyM4b1c5iI2WUo,17652
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=tb-eucPKT0bz3vQDaI1ltUZEhGKRFOFhN3U5ZIv_-xY,600
7
- livekit_plugins_anthropic-0.2.1.dist-info/METADATA,sha256=-m1Q9gn3H1gRoVjFb1uIDfyJ5TP6p1HEMrh-zDyGeQs,1264
8
- livekit_plugins_anthropic-0.2.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
9
- livekit_plugins_anthropic-0.2.1.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
10
- livekit_plugins_anthropic-0.2.1.dist-info/RECORD,,