livekit-plugins-anthropic 0.2.9__tar.gz → 0.2.11__tar.gz

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.
Files changed (16) hide show
  1. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/PKG-INFO +12 -2
  2. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/llm.py +111 -27
  3. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/version.py +1 -1
  4. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit_plugins_anthropic.egg-info/PKG-INFO +12 -2
  5. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/README.md +0 -0
  6. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/__init__.py +0 -0
  7. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/log.py +0 -0
  8. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/models.py +0 -0
  9. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit/plugins/anthropic/py.typed +0 -0
  10. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit_plugins_anthropic.egg-info/SOURCES.txt +0 -0
  11. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit_plugins_anthropic.egg-info/dependency_links.txt +0 -0
  12. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit_plugins_anthropic.egg-info/requires.txt +0 -0
  13. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/livekit_plugins_anthropic.egg-info/top_level.txt +0 -0
  14. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/pyproject.toml +0 -0
  15. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/setup.cfg +0 -0
  16. {livekit_plugins_anthropic-0.2.9 → livekit_plugins_anthropic-0.2.11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: livekit-plugins-anthropic
3
- Version: 0.2.9
3
+ Version: 0.2.11
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
@@ -21,6 +21,16 @@ Requires-Python: >=3.9.0
21
21
  Description-Content-Type: text/markdown
22
22
  Requires-Dist: livekit-agents>=0.12.3
23
23
  Requires-Dist: anthropic>=0.34
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: project-url
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
24
34
 
25
35
  # LiveKit Plugins Anthropic
26
36
 
@@ -39,7 +39,7 @@ from livekit.agents import (
39
39
  llm,
40
40
  utils,
41
41
  )
42
- from livekit.agents.llm import ToolChoice
42
+ from livekit.agents.llm import LLMCapabilities, ToolChoice
43
43
  from livekit.agents.llm.function_context import (
44
44
  _create_ai_function_info,
45
45
  _is_optional_type,
@@ -53,6 +53,8 @@ from .models import (
53
53
  ChatModels,
54
54
  )
55
55
 
56
+ CACHE_CONTROL_EPHEMERAL = anthropic.types.CacheControlEphemeralParam(type="ephemeral")
57
+
56
58
 
57
59
  @dataclass
58
60
  class LLMOptions:
@@ -61,6 +63,8 @@ class LLMOptions:
61
63
  temperature: float | None
62
64
  parallel_tool_calls: bool | None
63
65
  tool_choice: Union[ToolChoice, Literal["auto", "required", "none"]] | None
66
+ caching: Literal["ephemeral"] | None = None
67
+ """If set to "ephemeral", the system prompt, tools, and chat history will be cached."""
64
68
 
65
69
 
66
70
  class LLM(llm.LLM):
@@ -75,14 +79,31 @@ class LLM(llm.LLM):
75
79
  temperature: float | None = None,
76
80
  parallel_tool_calls: bool | None = None,
77
81
  tool_choice: Union[ToolChoice, Literal["auto", "required", "none"]] = "auto",
82
+ caching: Literal["ephemeral"] | None = None,
78
83
  ) -> None:
79
84
  """
80
85
  Create a new instance of Anthropic LLM.
81
86
 
82
87
  ``api_key`` must be set to your Anthropic API key, either using the argument or by setting
83
88
  the ``ANTHROPIC_API_KEY`` environmental variable.
89
+
90
+ model (str | ChatModels): The model to use. Defaults to "claude-3-5-sonnet-20241022".
91
+ api_key (str | None): The Anthropic API key. Defaults to the ANTHROPIC_API_KEY environment variable.
92
+ base_url (str | None): The base URL for the Anthropic API. Defaults to None.
93
+ user (str | None): The user for the Anthropic API. Defaults to None.
94
+ client (anthropic.AsyncClient | None): The Anthropic client to use. Defaults to None.
95
+ temperature (float | None): The temperature for the Anthropic API. Defaults to None.
96
+ parallel_tool_calls (bool | None): Whether to parallelize tool calls. Defaults to None.
97
+ tool_choice (Union[ToolChoice, Literal["auto", "required", "none"]] | None): The tool choice for the Anthropic API. Defaults to "auto".
98
+ caching (Literal["ephemeral"] | None): If set to "ephemeral", caching will be enabled for the system prompt, tools, and chat history.
84
99
  """
85
- super().__init__()
100
+
101
+ super().__init__(
102
+ capabilities=LLMCapabilities(
103
+ requires_persistent_functions=True,
104
+ supports_choices_on_int=True,
105
+ )
106
+ )
86
107
 
87
108
  # throw an error on our end
88
109
  api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
@@ -95,6 +116,7 @@ class LLM(llm.LLM):
95
116
  temperature=temperature,
96
117
  parallel_tool_calls=parallel_tool_calls,
97
118
  tool_choice=tool_choice,
119
+ caching=caching,
98
120
  )
99
121
  self._client = client or anthropic.AsyncClient(
100
122
  api_key=api_key,
@@ -132,8 +154,20 @@ class LLM(llm.LLM):
132
154
  opts: dict[str, Any] = dict()
133
155
  if fnc_ctx and len(fnc_ctx.ai_functions) > 0:
134
156
  fncs_desc: list[anthropic.types.ToolParam] = []
135
- for fnc in fnc_ctx.ai_functions.values():
136
- fncs_desc.append(_build_function_description(fnc))
157
+ for i, fnc in enumerate(fnc_ctx.ai_functions.values()):
158
+ # caching last tool will cache all the tools if caching is enabled
159
+ cache_ctrl = (
160
+ CACHE_CONTROL_EPHEMERAL
161
+ if (i == len(fnc_ctx.ai_functions) - 1)
162
+ and self._opts.caching == "ephemeral"
163
+ else None
164
+ )
165
+ fncs_desc.append(
166
+ _build_function_description(
167
+ fnc,
168
+ cache_ctrl=cache_ctrl,
169
+ )
170
+ )
137
171
 
138
172
  opts["tools"] = fncs_desc
139
173
  if tool_choice is not None:
@@ -151,13 +185,19 @@ class LLM(llm.LLM):
151
185
  anthropic_tool_choice["disable_parallel_tool_use"] = True
152
186
  opts["tool_choice"] = anthropic_tool_choice
153
187
 
154
- latest_system_message = _latest_system_message(chat_ctx)
155
- anthropic_ctx = _build_anthropic_context(chat_ctx.messages, id(self))
188
+ latest_system_message: anthropic.types.TextBlockParam = _latest_system_message(
189
+ chat_ctx, caching=self._opts.caching
190
+ )
191
+ anthropic_ctx = _build_anthropic_context(
192
+ chat_ctx.messages,
193
+ id(self),
194
+ caching=self._opts.caching,
195
+ )
156
196
  collaped_anthropic_ctx = _merge_messages(anthropic_ctx)
157
197
 
158
198
  stream = self._client.messages.create(
159
199
  max_tokens=opts.get("max_tokens", 1024),
160
- system=latest_system_message,
200
+ system=[latest_system_message],
161
201
  messages=collaped_anthropic_ctx,
162
202
  model=self._opts.model,
163
203
  temperature=temperature or anthropic.NOT_GIVEN,
@@ -203,6 +243,8 @@ class LLMStream(llm.LLMStream):
203
243
  self._request_id: str = ""
204
244
  self._ignoring_cot = False # ignore chain of thought
205
245
  self._input_tokens = 0
246
+ self._cache_creation_tokens = 0
247
+ self._cache_read_tokens = 0
206
248
  self._output_tokens = 0
207
249
 
208
250
  async def _run(self) -> None:
@@ -224,7 +266,12 @@ class LLMStream(llm.LLMStream):
224
266
  usage=llm.CompletionUsage(
225
267
  completion_tokens=self._output_tokens,
226
268
  prompt_tokens=self._input_tokens,
227
- total_tokens=self._input_tokens + self._output_tokens,
269
+ total_tokens=self._input_tokens
270
+ + self._output_tokens
271
+ + self._cache_creation_tokens
272
+ + self._cache_read_tokens,
273
+ cache_creation_input_tokens=self._cache_creation_tokens,
274
+ cache_read_input_tokens=self._cache_read_tokens,
228
275
  ),
229
276
  )
230
277
  )
@@ -247,6 +294,12 @@ class LLMStream(llm.LLMStream):
247
294
  self._request_id = event.message.id
248
295
  self._input_tokens = event.message.usage.input_tokens
249
296
  self._output_tokens = event.message.usage.output_tokens
297
+ if event.message.usage.cache_creation_input_tokens:
298
+ self._cache_creation_tokens = (
299
+ event.message.usage.cache_creation_input_tokens
300
+ )
301
+ if event.message.usage.cache_read_input_tokens:
302
+ self._cache_read_tokens = event.message.usage.cache_read_input_tokens
250
303
  elif event.type == "message_delta":
251
304
  self._output_tokens += event.usage.output_tokens
252
305
  elif event.type == "content_block_start":
@@ -311,7 +364,9 @@ class LLMStream(llm.LLMStream):
311
364
  return None
312
365
 
313
366
 
314
- def _latest_system_message(chat_ctx: llm.ChatContext) -> str:
367
+ def _latest_system_message(
368
+ chat_ctx: llm.ChatContext, caching: Literal["ephemeral"] | None = None
369
+ ) -> anthropic.types.TextBlockParam:
315
370
  latest_system_message: llm.ChatMessage | None = None
316
371
  for m in chat_ctx.messages:
317
372
  if m.role == "system":
@@ -326,7 +381,12 @@ def _latest_system_message(chat_ctx: llm.ChatContext) -> str:
326
381
  latest_system_str = " ".join(
327
382
  [c for c in latest_system_message.content if isinstance(c, str)]
328
383
  )
329
- return latest_system_str
384
+ system_text_block = anthropic.types.TextBlockParam(
385
+ text=latest_system_str,
386
+ type="text",
387
+ cache_control=CACHE_CONTROL_EPHEMERAL if caching == "ephemeral" else None,
388
+ )
389
+ return system_text_block
330
390
 
331
391
 
332
392
  def _merge_messages(
@@ -356,18 +416,29 @@ def _merge_messages(
356
416
 
357
417
 
358
418
  def _build_anthropic_context(
359
- chat_ctx: List[llm.ChatMessage], cache_key: Any
419
+ chat_ctx: List[llm.ChatMessage],
420
+ cache_key: Any,
421
+ caching: Literal["ephemeral"] | None,
360
422
  ) -> List[anthropic.types.MessageParam]:
361
423
  result: List[anthropic.types.MessageParam] = []
362
- for msg in chat_ctx:
363
- a_msg = _build_anthropic_message(msg, cache_key, chat_ctx)
424
+ for i, msg in enumerate(chat_ctx):
425
+ # caching last message will cache whole chat history if caching is enabled
426
+ cache_ctrl = (
427
+ CACHE_CONTROL_EPHEMERAL
428
+ if ((i == len(chat_ctx) - 1) and caching == "ephemeral")
429
+ else None
430
+ )
431
+ a_msg = _build_anthropic_message(msg, cache_key, cache_ctrl=cache_ctrl)
432
+
364
433
  if a_msg:
365
434
  result.append(a_msg)
366
435
  return result
367
436
 
368
437
 
369
438
  def _build_anthropic_message(
370
- msg: llm.ChatMessage, cache_key: Any, chat_ctx: List[llm.ChatMessage]
439
+ msg: llm.ChatMessage,
440
+ cache_key: Any,
441
+ cache_ctrl: anthropic.types.CacheControlEphemeralParam | None,
371
442
  ) -> anthropic.types.MessageParam | None:
372
443
  if msg.role == "user" or msg.role == "assistant":
373
444
  a_msg: anthropic.types.MessageParam = {
@@ -380,22 +451,27 @@ def _build_anthropic_message(
380
451
  # add content if provided
381
452
  if isinstance(msg.content, str) and msg.content:
382
453
  a_msg["content"].append(
383
- anthropic.types.TextBlock(
454
+ anthropic.types.TextBlockParam(
384
455
  text=msg.content,
385
456
  type="text",
457
+ cache_control=cache_ctrl,
386
458
  )
387
459
  )
388
460
  elif isinstance(msg.content, list):
389
461
  for cnt in msg.content:
390
462
  if isinstance(cnt, str) and cnt:
391
- content: anthropic.types.TextBlock = anthropic.types.TextBlock(
392
- text=cnt,
393
- type="text",
463
+ content: anthropic.types.TextBlockParam = (
464
+ anthropic.types.TextBlockParam(
465
+ text=cnt,
466
+ type="text",
467
+ cache_control=cache_ctrl,
468
+ )
394
469
  )
395
470
  a_content.append(content)
396
471
  elif isinstance(cnt, llm.ChatImage):
397
- a_content.append(_build_anthropic_image_content(cnt, cache_key))
398
-
472
+ a_content.append(
473
+ _build_anthropic_image_content(cnt, cache_key, cache_ctrl)
474
+ )
399
475
  if msg.tool_calls is not None:
400
476
  for fnc in msg.tool_calls:
401
477
  tool_use = anthropic.types.ToolUseBlockParam(
@@ -403,6 +479,7 @@ def _build_anthropic_message(
403
479
  type="tool_use",
404
480
  name=fnc.function_info.name,
405
481
  input=fnc.arguments,
482
+ cache_control=cache_ctrl,
406
483
  )
407
484
  a_content.append(tool_use)
408
485
 
@@ -421,6 +498,7 @@ def _build_anthropic_message(
421
498
  type="tool_result",
422
499
  content=msg.content,
423
500
  is_error=msg.tool_exception is not None,
501
+ cache_control=cache_ctrl,
424
502
  )
425
503
  return {
426
504
  "role": "user",
@@ -431,7 +509,9 @@ def _build_anthropic_message(
431
509
 
432
510
 
433
511
  def _build_anthropic_image_content(
434
- image: llm.ChatImage, cache_key: Any
512
+ image: llm.ChatImage,
513
+ cache_key: Any,
514
+ cache_ctrl: anthropic.types.CacheControlEphemeralParam | None,
435
515
  ) -> anthropic.types.ImageBlockParam:
436
516
  if isinstance(image.image, str): # image is a URL
437
517
  if not image.image.startswith("data:"):
@@ -457,6 +537,7 @@ def _build_anthropic_image_content(
457
537
  media_type,
458
538
  ),
459
539
  },
540
+ "cache_control": cache_ctrl,
460
541
  }
461
542
  except (ValueError, IndexError) as e:
462
543
  raise ValueError(
@@ -484,6 +565,7 @@ def _build_anthropic_image_content(
484
565
  "data": image._cache[cache_key],
485
566
  "media_type": "image/jpeg",
486
567
  },
568
+ "cache_control": cache_ctrl,
487
569
  }
488
570
 
489
571
  raise ValueError(
@@ -493,6 +575,7 @@ def _build_anthropic_image_content(
493
575
 
494
576
  def _build_function_description(
495
577
  fnc_info: llm.function_context.FunctionInfo,
578
+ cache_ctrl: anthropic.types.CacheControlEphemeralParam | None,
496
579
  ) -> anthropic.types.ToolParam:
497
580
  def build_schema_field(arg_info: llm.function_context.FunctionArgInfo):
498
581
  def type2str(t: type) -> str:
@@ -514,7 +597,7 @@ def _build_function_description(
514
597
  if arg_info.description:
515
598
  p["description"] = arg_info.description
516
599
 
517
- is_optional, inner_th = _is_optional_type(arg_info.type)
600
+ _, inner_th = _is_optional_type(arg_info.type)
518
601
 
519
602
  if get_origin(inner_th) is list:
520
603
  inner_type = get_args(inner_th)[0]
@@ -536,8 +619,9 @@ def _build_function_description(
536
619
  for arg_info in fnc_info.arguments.values():
537
620
  input_schema[arg_info.name] = build_schema_field(arg_info)
538
621
 
539
- return {
540
- "name": fnc_info.name,
541
- "description": fnc_info.description,
542
- "input_schema": input_schema,
543
- }
622
+ return anthropic.types.ToolParam(
623
+ name=fnc_info.name,
624
+ description=fnc_info.description,
625
+ input_schema=input_schema,
626
+ cache_control=cache_ctrl,
627
+ )
@@ -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.9"
15
+ __version__ = "0.2.11"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: livekit-plugins-anthropic
3
- Version: 0.2.9
3
+ Version: 0.2.11
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
@@ -21,6 +21,16 @@ Requires-Python: >=3.9.0
21
21
  Description-Content-Type: text/markdown
22
22
  Requires-Dist: livekit-agents>=0.12.3
23
23
  Requires-Dist: anthropic>=0.34
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: project-url
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
24
34
 
25
35
  # LiveKit Plugins Anthropic
26
36