livekit-plugins-aws 0.1.0__py3-none-any.whl → 1.0.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.

Potentially problematic release.


This version of livekit-plugins-aws might be problematic. Click here for more details.

@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import aioboto3
7
+ import boto3
8
+ from botocore.exceptions import NoCredentialsError
9
+
10
+ from livekit.agents import llm
11
+ from livekit.agents.llm import ChatContext, FunctionTool, ImageContent, utils
12
+
13
+ __all__ = ["to_fnc_ctx", "to_chat_ctx", "get_aws_async_session"]
14
+ DEFAULT_REGION = "us-east-1"
15
+
16
+
17
+ def get_aws_async_session(
18
+ region: str | None = None,
19
+ api_key: str | None = None,
20
+ api_secret: str | None = None,
21
+ ) -> aioboto3.Session:
22
+ _validate_aws_credentials(api_key, api_secret)
23
+ session = aioboto3.Session(
24
+ aws_access_key_id=api_key,
25
+ aws_secret_access_key=api_secret,
26
+ region_name=region or DEFAULT_REGION,
27
+ )
28
+ return session
29
+
30
+
31
+ def _validate_aws_credentials(
32
+ api_key: str | None = None,
33
+ api_secret: str | None = None,
34
+ ) -> None:
35
+ try:
36
+ session = boto3.Session(aws_access_key_id=api_key, aws_secret_access_key=api_secret)
37
+ creds = session.get_credentials()
38
+ if not creds:
39
+ raise ValueError("No credentials found")
40
+ except (NoCredentialsError, Exception) as e:
41
+ raise ValueError(f"Unable to locate valid AWS credentials: {str(e)}") from e
42
+
43
+
44
+ def to_fnc_ctx(fncs: list[FunctionTool]) -> list[dict]:
45
+ return [_build_tool_spec(fnc) for fnc in fncs]
46
+
47
+
48
+ def to_chat_ctx(chat_ctx: ChatContext, cache_key: Any) -> tuple[list[dict], dict | None]:
49
+ messages: list[dict] = []
50
+ system_message: dict | None = None
51
+ current_role: str | None = None
52
+ current_content: list[dict] = []
53
+
54
+ for msg in chat_ctx.items:
55
+ if msg.type == "message" and msg.role == "system":
56
+ for content in msg.content:
57
+ if content and isinstance(content, str):
58
+ system_message = {"text": content}
59
+ continue
60
+
61
+ if msg.type == "message":
62
+ role = "assistant" if msg.role == "assistant" else "user"
63
+ elif msg.type == "function_call":
64
+ role = "assistant"
65
+ elif msg.type == "function_call_output":
66
+ role = "user"
67
+
68
+ # if the effective role changed, finalize the previous turn.
69
+ if role != current_role:
70
+ if current_content and current_role is not None:
71
+ messages.append({"role": current_role, "content": current_content})
72
+ current_content = []
73
+ current_role = role
74
+
75
+ if msg.type == "message":
76
+ for content in msg.content:
77
+ if content and isinstance(content, str):
78
+ current_content.append({"text": content})
79
+ elif isinstance(content, ImageContent):
80
+ current_content.append(_build_image(content, cache_key))
81
+ elif msg.type == "function_call":
82
+ current_content.append(
83
+ {
84
+ "toolUse": {
85
+ "toolUseId": msg.call_id,
86
+ "name": msg.name,
87
+ "input": json.loads(msg.arguments or "{}"),
88
+ }
89
+ }
90
+ )
91
+ elif msg.type == "function_call_output":
92
+ tool_response = {
93
+ "toolResult": {
94
+ "toolUseId": msg.call_id,
95
+ "content": [],
96
+ "status": "success",
97
+ }
98
+ }
99
+ if isinstance(msg.output, dict):
100
+ tool_response["toolResult"]["content"].append({"json": msg.output})
101
+ elif isinstance(msg.output, str):
102
+ tool_response["toolResult"]["content"].append({"text": msg.output})
103
+ current_content.append(tool_response)
104
+
105
+ # Finalize the last message if there’s any content left
106
+ if current_role is not None and current_content:
107
+ messages.append({"role": current_role, "content": current_content})
108
+
109
+ # Ensure the message list starts with a "user" message
110
+ if not messages or messages[0]["role"] != "user":
111
+ messages.insert(0, {"role": "user", "content": [{"text": "(empty)"}]})
112
+
113
+ return messages, system_message
114
+
115
+
116
+ def _build_tool_spec(fnc: FunctionTool) -> dict:
117
+ fnc = llm.utils.build_legacy_openai_schema(fnc, internally_tagged=True)
118
+ return {
119
+ "toolSpec": _strip_nones(
120
+ {
121
+ "name": fnc["name"],
122
+ "description": fnc["description"] if fnc["description"] else None,
123
+ "inputSchema": {"json": fnc["parameters"] if fnc["parameters"] else {}},
124
+ }
125
+ )
126
+ }
127
+
128
+
129
+ def _build_image(image: ImageContent, cache_key: Any) -> dict:
130
+ img = utils.serialize_image(image)
131
+ if img.external_url:
132
+ raise ValueError("external_url is not supported by AWS Bedrock.")
133
+ if cache_key not in image._cache:
134
+ image._cache[cache_key] = img.data_bytes
135
+ return {
136
+ "image": {
137
+ "format": "jpeg",
138
+ "source": {"bytes": image._cache[cache_key]},
139
+ }
140
+ }
141
+
142
+
143
+ def _strip_nones(d: dict) -> dict:
144
+ return {k: v for k, v in d.items() if v is not None}
@@ -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.1.0"
15
+ __version__ = "1.0.0"
@@ -1,38 +1,28 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: livekit-plugins-aws
3
- Version: 0.1.0
3
+ Version: 1.0.0
4
4
  Summary: LiveKit Agents Plugin for services from AWS
5
- Home-page: https://github.com/livekit/agents
6
- License: Apache-2.0
7
5
  Project-URL: Documentation, https://docs.livekit.io
8
6
  Project-URL: Website, https://livekit.io/
9
7
  Project-URL: Source, https://github.com/livekit/agents
10
- Keywords: webrtc,realtime,audio,video,livekit,aws
8
+ Author-email: LiveKit <hello@livekit.io>
9
+ License-Expression: Apache-2.0
10
+ Keywords: audio,aws,livekit,realtime,video,webrtc
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Topic :: Multimedia :: Sound/Audio
14
- Classifier: Topic :: Multimedia :: Video
15
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
13
  Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
17
15
  Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Multimedia :: Sound/Audio
18
+ Classifier: Topic :: Multimedia :: Video
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
20
  Requires-Python: >=3.9.0
21
+ Requires-Dist: aioboto3==14.1.0
22
+ Requires-Dist: amazon-transcribe==0.6.2
23
+ Requires-Dist: boto3==1.37.1
24
+ Requires-Dist: livekit-agents>=1.0.0
21
25
  Description-Content-Type: text/markdown
22
- Requires-Dist: livekit-agents>=0.12.0
23
- Requires-Dist: aiobotocore==2.19.0
24
- Requires-Dist: boto3==1.36.3
25
- Requires-Dist: amazon-transcribe>=0.6.2
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: keywords
31
- Dynamic: license
32
- Dynamic: project-url
33
- Dynamic: requires-dist
34
- Dynamic: requires-python
35
- Dynamic: summary
36
26
 
37
27
  # LiveKit Plugins AWS
38
28
 
@@ -50,4 +40,4 @@ pip install livekit-plugins-aws
50
40
 
51
41
  ## Pre-requisites
52
42
 
53
- You'll need to specify an AWS Access Key and a Deployment Region. They can be set as environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_DEFAULT_REGION`, respectively.
43
+ You'll need to specify an AWS Access Key and a Deployment Region. They can be set as environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_DEFAULT_REGION`, respectively.
@@ -0,0 +1,12 @@
1
+ livekit/plugins/aws/__init__.py,sha256=Ea-hK7QdutnwdZvvs9K2fiR8RWJqz2JcONxXnV1kXF0,977
2
+ livekit/plugins/aws/llm.py,sha256=eP3VkdM4XA73995OBB-C2Q1iRVrvNtAkX002q51APDs,11252
3
+ livekit/plugins/aws/log.py,sha256=jFief0Xhv0n_F6sp6UFu9VKxs2bXNVGAfYGmEYfR_2Q,66
4
+ livekit/plugins/aws/models.py,sha256=Nf8RFmDulW7h03dG2lERTog3mgDK0TbLvW0eGOncuEE,704
5
+ livekit/plugins/aws/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ livekit/plugins/aws/stt.py,sha256=FgZ0hN-ToqQRzesdV28GNkHw_x2X_sFugji_BnLXm7c,9448
7
+ livekit/plugins/aws/tts.py,sha256=IRC_XgQ-uDLkE3JrZ_AtiNRfnQiX4trLV6c4clcNd8s,7337
8
+ livekit/plugins/aws/utils.py,sha256=BqZPyLr-xETbGybhE3-lEJovqkuCuAd-cxtUO3aFAVM,4988
9
+ livekit/plugins/aws/version.py,sha256=nW89L_U9N4ukT3wAO3BeTqOaa87zLUOsEFz8TkiKIP8,600
10
+ livekit_plugins_aws-1.0.0.dist-info/METADATA,sha256=0MGGgszS2vPlzlop1kDOcOjjynbq4jY2S5c9cR1KiAA,1473
11
+ livekit_plugins_aws-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ livekit_plugins_aws-1.0.0.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,216 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import base64
4
- import inspect
5
- import json
6
- import os
7
- from typing import Any, Dict, List, Optional, Tuple, get_args, get_origin
8
-
9
- import boto3
10
- from livekit import rtc
11
- from livekit.agents import llm, utils
12
- from livekit.agents.llm.function_context import _is_optional_type
13
-
14
- __all__ = ["_build_aws_ctx", "_build_tools", "_get_aws_credentials"]
15
-
16
-
17
- def _get_aws_credentials(
18
- api_key: Optional[str], api_secret: Optional[str], region: Optional[str]
19
- ):
20
- region = region or os.environ.get("AWS_DEFAULT_REGION")
21
- if not region:
22
- raise ValueError(
23
- "AWS_DEFAULT_REGION must be set using the argument or by setting the AWS_DEFAULT_REGION environment variable."
24
- )
25
-
26
- # If API key and secret are provided, create a session with them
27
- if api_key and api_secret:
28
- session = boto3.Session(
29
- aws_access_key_id=api_key,
30
- aws_secret_access_key=api_secret,
31
- region_name=region,
32
- )
33
- else:
34
- session = boto3.Session(region_name=region)
35
-
36
- credentials = session.get_credentials()
37
- if not credentials or not credentials.access_key or not credentials.secret_key:
38
- raise ValueError("No valid AWS credentials found.")
39
- return credentials.access_key, credentials.secret_key
40
-
41
-
42
- JSON_SCHEMA_TYPE_MAP: Dict[type, str] = {
43
- str: "string",
44
- int: "integer",
45
- float: "number",
46
- bool: "boolean",
47
- dict: "object",
48
- list: "array",
49
- }
50
-
51
-
52
- def _build_parameters(arguments: Dict[str, Any]) -> Optional[Dict[str, Any]]:
53
- properties: Dict[str, dict] = {}
54
- required: List[str] = []
55
-
56
- for arg_name, arg_info in arguments.items():
57
- prop = {}
58
- if hasattr(arg_info, "description") and arg_info.description:
59
- prop["description"] = arg_info.description
60
-
61
- _, py_type = _is_optional_type(arg_info.type)
62
- origin = get_origin(py_type)
63
- if origin is list:
64
- item_type = get_args(py_type)[0]
65
- if item_type not in JSON_SCHEMA_TYPE_MAP:
66
- raise ValueError(f"Unsupported type: {item_type}")
67
- prop["type"] = "array"
68
- prop["items"] = {"type": JSON_SCHEMA_TYPE_MAP[item_type]}
69
-
70
- if hasattr(arg_info, "choices") and arg_info.choices:
71
- prop["items"]["enum"] = list(arg_info.choices)
72
- else:
73
- if py_type not in JSON_SCHEMA_TYPE_MAP:
74
- raise ValueError(f"Unsupported type: {py_type}")
75
-
76
- prop["type"] = JSON_SCHEMA_TYPE_MAP[py_type]
77
-
78
- if arg_info.choices:
79
- prop["enum"] = list(arg_info.choices)
80
-
81
- properties[arg_name] = prop
82
-
83
- if arg_info.default is inspect.Parameter.empty:
84
- required.append(arg_name)
85
-
86
- if properties:
87
- parameters = {"json": {"type": "object", "properties": properties}}
88
- if required:
89
- parameters["json"]["required"] = required
90
-
91
- return parameters
92
-
93
- return None
94
-
95
-
96
- def _build_tools(fnc_ctx: Any) -> List[dict]:
97
- tools: List[dict] = []
98
- for fnc_info in fnc_ctx.ai_functions.values():
99
- parameters = _build_parameters(fnc_info.arguments)
100
-
101
- func_decl = {
102
- "toolSpec": {
103
- "name": fnc_info.name,
104
- "description": fnc_info.description,
105
- "inputSchema": parameters
106
- if parameters
107
- else {"json": {"type": "object", "properties": {}}},
108
- }
109
- }
110
-
111
- tools.append(func_decl)
112
- return tools
113
-
114
-
115
- def _build_image(image: llm.ChatImage, cache_key: Any) -> dict:
116
- if isinstance(image.image, str):
117
- if image.image.startswith("data:image/jpeg;base64,"):
118
- base64_data = image.image.split(",", 1)[1]
119
- try:
120
- image_bytes = base64.b64decode(base64_data)
121
- except Exception as e:
122
- raise ValueError("Invalid base64 data in image URL") from e
123
-
124
- return {"image": {"format": "jpeg", "source": {"bytes": image_bytes}}}
125
- else:
126
- return {"image": {"format": "jpeg", "source": {"uri": image.image}}}
127
-
128
- elif isinstance(image.image, rtc.VideoFrame):
129
- if cache_key not in image._cache:
130
- opts = utils.images.EncodeOptions()
131
- if image.inference_width and image.inference_height:
132
- opts.resize_options = utils.images.ResizeOptions(
133
- width=image.inference_width,
134
- height=image.inference_height,
135
- strategy="scale_aspect_fit",
136
- )
137
- image._cache[cache_key] = utils.images.encode(image.image, opts)
138
-
139
- return {
140
- "image": {
141
- "format": "jpeg",
142
- "source": {
143
- "bytes": image._cache[cache_key],
144
- },
145
- }
146
- }
147
- raise ValueError(f"Unsupported image type: {type(image.image)}")
148
-
149
-
150
- def _build_aws_ctx(
151
- chat_ctx: llm.ChatContext, cache_key: Any
152
- ) -> Tuple[List[dict], Optional[dict]]:
153
- messages: List[dict] = []
154
- system: Optional[dict] = None
155
- current_role: Optional[str] = None
156
- current_content: List[dict] = []
157
-
158
- for msg in chat_ctx.messages:
159
- if msg.role == "system":
160
- if isinstance(msg.content, str):
161
- system = {"text": msg.content}
162
- continue
163
-
164
- if msg.role == "assistant":
165
- role = "assistant"
166
- else:
167
- role = "user"
168
-
169
- if role != current_role:
170
- if current_role is not None and current_content:
171
- messages.append({"role": current_role, "content": current_content})
172
- current_role = role
173
- current_content = []
174
-
175
- if msg.tool_calls:
176
- for fnc in msg.tool_calls:
177
- current_content.append(
178
- {
179
- "toolUse": {
180
- "toolUseId": fnc.tool_call_id,
181
- "name": fnc.function_info.name,
182
- "input": fnc.arguments,
183
- }
184
- }
185
- )
186
-
187
- if msg.role == "tool":
188
- tool_response: dict = {
189
- "toolResult": {
190
- "toolUseId": msg.tool_call_id,
191
- "content": [],
192
- "status": "success",
193
- }
194
- }
195
- if isinstance(msg.content, dict):
196
- tool_response["toolResult"]["content"].append({"json": msg.content})
197
- elif isinstance(msg.content, str):
198
- tool_response["toolResult"]["content"].append({"text": msg.content})
199
- current_content.append(tool_response)
200
- else:
201
- if msg.content:
202
- if isinstance(msg.content, str):
203
- current_content.append({"text": msg.content})
204
- elif isinstance(msg.content, dict):
205
- current_content.append({"text": json.dumps(msg.content)})
206
- elif isinstance(msg.content, list):
207
- for item in msg.content:
208
- if isinstance(item, str):
209
- current_content.append({"text": item})
210
- elif isinstance(item, llm.ChatImage):
211
- current_content.append(_build_image(item, cache_key))
212
-
213
- if current_role is not None and current_content:
214
- messages.append({"role": current_role, "content": current_content})
215
-
216
- return messages, system
@@ -1,13 +0,0 @@
1
- livekit/plugins/aws/__init__.py,sha256=Ea-hK7QdutnwdZvvs9K2fiR8RWJqz2JcONxXnV1kXF0,977
2
- livekit/plugins/aws/_utils.py,sha256=iuDuQpPta4wLtgW1Wc2rHspZWoa7KZI76tujQIPY898,7411
3
- livekit/plugins/aws/llm.py,sha256=yUAiBCtb2jRB1_S9BNrILTMmDffvKOpDod802kYnPVM,13527
4
- livekit/plugins/aws/log.py,sha256=jFief0Xhv0n_F6sp6UFu9VKxs2bXNVGAfYGmEYfR_2Q,66
5
- livekit/plugins/aws/models.py,sha256=wb7AfN-z7qgtKMZnUbQsELi6wN8ha5exI3DH8z6Gz3M,711
6
- livekit/plugins/aws/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- livekit/plugins/aws/stt.py,sha256=eH7gKtdCjwki20Th6PrCsjjtH-zjXa8ZWu-cu_KaT80,7935
8
- livekit/plugins/aws/tts.py,sha256=miUYrhstJ7tcLkvJ-8Cpv1UCQxRSdOqaSC2tvHBh9WI,7800
9
- livekit/plugins/aws/version.py,sha256=vQH9cItKAVYAmrLbOntkbLqmxrUZrPiKb1TjkZ8jRKQ,600
10
- livekit_plugins_aws-0.1.0.dist-info/METADATA,sha256=FUzLRO0YcUvcIidEEq_EK7Lbp6yPYKjzT_BkclYNGhM,1686
11
- livekit_plugins_aws-0.1.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
12
- livekit_plugins_aws-0.1.0.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
13
- livekit_plugins_aws-0.1.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- livekit