livekit-plugins-anthropic 0.2.7__py3-none-any.whl → 0.2.9__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.
@@ -24,8 +24,8 @@ from typing import (
24
24
  Awaitable,
25
25
  List,
26
26
  Literal,
27
- Tuple,
28
27
  Union,
28
+ cast,
29
29
  get_args,
30
30
  get_origin,
31
31
  )
@@ -40,6 +40,10 @@ from livekit.agents import (
40
40
  utils,
41
41
  )
42
42
  from livekit.agents.llm import ToolChoice
43
+ from livekit.agents.llm.function_context import (
44
+ _create_ai_function_info,
45
+ _is_optional_type,
46
+ )
43
47
  from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS, APIConnectOptions
44
48
 
45
49
  import anthropic
@@ -202,6 +206,7 @@ class LLMStream(llm.LLMStream):
202
206
  self._output_tokens = 0
203
207
 
204
208
  async def _run(self) -> None:
209
+ retryable = True
205
210
  try:
206
211
  if not self._anthropic_stream:
207
212
  self._anthropic_stream = await self._awaitable_anthropic_stream
@@ -211,6 +216,7 @@ class LLMStream(llm.LLMStream):
211
216
  chat_chunk = self._parse_event(event)
212
217
  if chat_chunk is not None:
213
218
  self._event_ch.send_nowait(chat_chunk)
219
+ retryable = False
214
220
 
215
221
  self._event_ch.send_nowait(
216
222
  llm.ChatChunk(
@@ -223,7 +229,7 @@ class LLMStream(llm.LLMStream):
223
229
  )
224
230
  )
225
231
  except anthropic.APITimeoutError:
226
- raise APITimeoutError()
232
+ raise APITimeoutError(retryable=retryable)
227
233
  except anthropic.APIStatusError as e:
228
234
  raise APIStatusError(
229
235
  e.message,
@@ -232,7 +238,7 @@ class LLMStream(llm.LLMStream):
232
238
  body=e.body,
233
239
  )
234
240
  except Exception as e:
235
- raise APIConnectionError() from e
241
+ raise APIConnectionError(retryable=retryable) from e
236
242
 
237
243
  def _parse_event(
238
244
  self, event: anthropic.types.RawMessageStreamEvent
@@ -427,12 +433,36 @@ def _build_anthropic_message(
427
433
  def _build_anthropic_image_content(
428
434
  image: llm.ChatImage, cache_key: Any
429
435
  ) -> anthropic.types.ImageBlockParam:
430
- if isinstance(image.image, str): # image url
431
- logger.warning(
432
- "ChatImage with url is not yet supported by the LiveKit Anthropic plugin, skipping image '%s'",
433
- image.image,
434
- )
435
- elif isinstance(image.image, rtc.VideoFrame): # VideoFrame
436
+ if isinstance(image.image, str): # image is a URL
437
+ if not image.image.startswith("data:"):
438
+ raise ValueError("LiveKit Anthropic Plugin: Image URLs must be data URLs")
439
+
440
+ try:
441
+ header, b64_data = image.image.split(",", 1)
442
+ media_type = header.split(";")[0].split(":")[1]
443
+
444
+ supported_types = {"image/jpeg", "image/png", "image/webp", "image/gif"}
445
+ if media_type not in supported_types:
446
+ raise ValueError(
447
+ f"LiveKit Anthropic Plugin: Unsupported media type {media_type}. Must be jpeg, png, webp, or gif"
448
+ )
449
+
450
+ return {
451
+ "type": "image",
452
+ "source": {
453
+ "type": "base64",
454
+ "data": b64_data,
455
+ "media_type": cast(
456
+ Literal["image/jpeg", "image/png", "image/gif", "image/webp"],
457
+ media_type,
458
+ ),
459
+ },
460
+ }
461
+ except (ValueError, IndexError) as e:
462
+ raise ValueError(
463
+ f"LiveKit Anthropic Plugin: Invalid image data URL {str(e)}"
464
+ )
465
+ elif isinstance(image.image, rtc.VideoFrame): # image is a VideoFrame
436
466
  if cache_key not in image._cache:
437
467
  # inside our internal implementation, we allow to put extra metadata to
438
468
  # each ChatImage (avoid to reencode each time we do a chatcompletion request)
@@ -441,7 +471,7 @@ def _build_anthropic_image_content(
441
471
  opts.resize_options = utils.images.ResizeOptions(
442
472
  width=image.inference_width,
443
473
  height=image.inference_height,
444
- strategy="center_aspect_fit",
474
+ strategy="scale_aspect_fit",
445
475
  )
446
476
 
447
477
  encoded_data = utils.images.encode(image.image, opts)
@@ -456,65 +486,8 @@ def _build_anthropic_image_content(
456
486
  },
457
487
  }
458
488
 
459
- raise ValueError(f"unknown image type {type(image.image)}")
460
-
461
-
462
- def _create_ai_function_info(
463
- fnc_ctx: llm.function_context.FunctionContext,
464
- tool_call_id: str,
465
- fnc_name: str,
466
- raw_arguments: str, # JSON string
467
- ) -> llm.function_context.FunctionCallInfo:
468
- if fnc_name not in fnc_ctx.ai_functions:
469
- raise ValueError(f"AI function {fnc_name} not found")
470
-
471
- parsed_arguments: dict[str, Any] = {}
472
- try:
473
- if raw_arguments: # ignore empty string
474
- parsed_arguments = json.loads(raw_arguments)
475
- except json.JSONDecodeError:
476
- raise ValueError(
477
- f"AI function {fnc_name} received invalid JSON arguments - {raw_arguments}"
478
- )
479
-
480
- fnc_info = fnc_ctx.ai_functions[fnc_name]
481
-
482
- # Ensure all necessary arguments are present and of the correct type.
483
- sanitized_arguments: dict[str, Any] = {}
484
- for arg_info in fnc_info.arguments.values():
485
- if arg_info.name not in parsed_arguments:
486
- if arg_info.default is inspect.Parameter.empty:
487
- raise ValueError(
488
- f"AI function {fnc_name} missing required argument {arg_info.name}"
489
- )
490
- continue
491
-
492
- arg_value = parsed_arguments[arg_info.name]
493
- if get_origin(arg_info.type) is not None:
494
- if not isinstance(arg_value, list):
495
- raise ValueError(
496
- f"AI function {fnc_name} argument {arg_info.name} should be a list"
497
- )
498
-
499
- inner_type = get_args(arg_info.type)[0]
500
- sanitized_value = [
501
- _sanitize_primitive(
502
- value=v, expected_type=inner_type, choices=arg_info.choices
503
- )
504
- for v in arg_value
505
- ]
506
- else:
507
- sanitized_value = _sanitize_primitive(
508
- value=arg_value, expected_type=arg_info.type, choices=arg_info.choices
509
- )
510
-
511
- sanitized_arguments[arg_info.name] = sanitized_value
512
-
513
- return llm.function_context.FunctionCallInfo(
514
- tool_call_id=tool_call_id,
515
- raw_arguments=raw_arguments,
516
- function_info=fnc_info,
517
- arguments=sanitized_arguments,
489
+ raise ValueError(
490
+ "LiveKit Anthropic Plugin: ChatImage must be an rtc.VideoFrame or a data URL"
518
491
  )
519
492
 
520
493
 
@@ -541,8 +514,10 @@ def _build_function_description(
541
514
  if arg_info.description:
542
515
  p["description"] = arg_info.description
543
516
 
544
- if get_origin(arg_info.type) is list:
545
- inner_type = get_args(arg_info.type)[0]
517
+ is_optional, inner_th = _is_optional_type(arg_info.type)
518
+
519
+ if get_origin(inner_th) is list:
520
+ inner_type = get_args(inner_th)[0]
546
521
  p["type"] = "array"
547
522
  p["items"] = {}
548
523
  p["items"]["type"] = type2str(inner_type)
@@ -550,7 +525,7 @@ def _build_function_description(
550
525
  if arg_info.choices:
551
526
  p["items"]["enum"] = arg_info.choices
552
527
  else:
553
- p["type"] = type2str(arg_info.type)
528
+ p["type"] = type2str(inner_th)
554
529
  if arg_info.choices:
555
530
  p["enum"] = arg_info.choices
556
531
 
@@ -566,31 +541,3 @@ def _build_function_description(
566
541
  "description": fnc_info.description,
567
542
  "input_schema": input_schema,
568
543
  }
569
-
570
-
571
- def _sanitize_primitive(
572
- *, value: Any, expected_type: type, choices: Tuple[Any] | None
573
- ) -> Any:
574
- if expected_type is str:
575
- if not isinstance(value, str):
576
- raise ValueError(f"expected str, got {type(value)}")
577
- elif expected_type in (int, float):
578
- if not isinstance(value, (int, float)):
579
- raise ValueError(f"expected number, got {type(value)}")
580
-
581
- if expected_type is int:
582
- if value % 1 != 0:
583
- raise ValueError("expected int, got float")
584
-
585
- value = int(value)
586
- elif expected_type is float:
587
- value = float(value)
588
-
589
- elif expected_type is bool:
590
- if not isinstance(value, bool):
591
- raise ValueError(f"expected bool, got {type(value)}")
592
-
593
- if choices and value not in choices:
594
- raise ValueError(f"invalid value {value}, not in {choices}")
595
-
596
- return value
@@ -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.7"
15
+ __version__ = "0.2.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: livekit-plugins-anthropic
3
- Version: 0.2.7
3
+ Version: 0.2.9
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,7 +19,7 @@ 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.11
22
+ Requires-Dist: livekit-agents>=0.12.3
23
23
  Requires-Dist: anthropic>=0.34
24
24
 
25
25
  # LiveKit Plugins Anthropic
@@ -0,0 +1,10 @@
1
+ livekit/plugins/anthropic/__init__.py,sha256=1WCyNEaR6qBsX54qJQM0SeY-QHIucww16PLXcSnMqRo,1175
2
+ livekit/plugins/anthropic/llm.py,sha256=bl3cLKLvz_hTf_fL9PfDLMl6uPvFkRJTn_R33T39ZQw,19080
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/version.py,sha256=H-ib9_H1SbJY8B6-psDc7ndkyQeTmdfDstZPRVGab34,600
7
+ livekit_plugins_anthropic-0.2.9.dist-info/METADATA,sha256=7g5vPjgRL8_Nlv1jBKE7SeK9kFrPv4SGWtA8INpvMjk,1265
8
+ livekit_plugins_anthropic-0.2.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
+ livekit_plugins_anthropic-0.2.9.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
10
+ livekit_plugins_anthropic-0.2.9.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- livekit/plugins/anthropic/__init__.py,sha256=1WCyNEaR6qBsX54qJQM0SeY-QHIucww16PLXcSnMqRo,1175
2
- livekit/plugins/anthropic/llm.py,sha256=6v_f7G8_zxFnf8D-mIDN-2jYaju8mMPCVnZ485SIuzM,20798
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/version.py,sha256=ctbdsqNKaYDxuDqpKvxGUGiUJhMWF_Cgw8jw_g8sC_0,600
7
- livekit_plugins_anthropic-0.2.7.dist-info/METADATA,sha256=bl5blok7UehQompcSjpT1P00MssZH-H8OOC-au_Je1M,1263
8
- livekit_plugins_anthropic-0.2.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
- livekit_plugins_anthropic-0.2.7.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
10
- livekit_plugins_anthropic-0.2.7.dist-info/RECORD,,