livekit-plugins-google 1.0.0rc6__py3-none-any.whl → 1.0.0rc8__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.
@@ -25,7 +25,7 @@ from google.auth._default_async import default_async
25
25
  from google.genai import types
26
26
  from google.genai.errors import APIError, ClientError, ServerError
27
27
  from livekit.agents import APIConnectionError, APIStatusError, llm, utils
28
- from livekit.agents.llm import FunctionTool, ToolChoice
28
+ from livekit.agents.llm import FunctionTool, ToolChoice, utils as llm_utils
29
29
  from livekit.agents.types import (
30
30
  DEFAULT_API_CONNECT_OPTIONS,
31
31
  NOT_GIVEN,
@@ -36,7 +36,7 @@ from livekit.agents.utils import is_given
36
36
 
37
37
  from .log import logger
38
38
  from .models import ChatModels
39
- from .utils import to_chat_ctx, to_fnc_ctx
39
+ from .utils import to_chat_ctx, to_fnc_ctx, to_response_format
40
40
 
41
41
 
42
42
  @dataclass
@@ -148,6 +148,9 @@ class LLM(llm.LLM):
148
148
  conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
149
149
  parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
150
150
  tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
151
+ response_format: NotGivenOr[
152
+ types.SchemaUnion | type[llm_utils.ResponseFormatT]
153
+ ] = NOT_GIVEN,
151
154
  extra_kwargs: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
152
155
  ) -> LLMStream:
153
156
  extra = {}
@@ -189,6 +192,10 @@ class LLM(llm.LLM):
189
192
  )
190
193
  extra["tool_config"] = gemini_tool_choice
191
194
 
195
+ if is_given(response_format):
196
+ extra["response_schema"] = to_response_format(response_format)
197
+ extra["response_mime_type"] = "application/json"
198
+
192
199
  if is_given(self._opts.temperature):
193
200
  extra["temperature"] = self._opts.temperature
194
201
  if is_given(self._opts.max_output_tokens):
@@ -237,10 +244,11 @@ class LLMStream(llm.LLMStream):
237
244
 
238
245
  try:
239
246
  turns, system_instruction = to_chat_ctx(self._chat_ctx, id(self._llm))
240
-
241
- self._extra_kwargs["tools"] = [
242
- types.Tool(function_declarations=to_fnc_ctx(self._tools))
243
- ]
247
+ function_declarations = to_fnc_ctx(self._tools)
248
+ if function_declarations:
249
+ self._extra_kwargs["tools"] = [
250
+ types.Tool(function_declarations=function_declarations)
251
+ ]
244
252
  config = types.GenerateContentConfig(
245
253
  system_instruction=system_instruction,
246
254
  **self._extra_kwargs,
@@ -299,7 +307,7 @@ class LLMStream(llm.LLMStream):
299
307
  raise APIStatusError(
300
308
  "gemini llm: client error",
301
309
  status_code=e.code,
302
- body=e.message,
310
+ body=e.message + e.status,
303
311
  request_id=request_id,
304
312
  retryable=False if e.code != 429 else True,
305
313
  ) from e
@@ -307,7 +315,7 @@ class LLMStream(llm.LLMStream):
307
315
  raise APIStatusError(
308
316
  "gemini llm: server error",
309
317
  status_code=e.code,
310
- body=e.message,
318
+ body=e.message + e.status,
311
319
  request_id=request_id,
312
320
  retryable=retryable,
313
321
  ) from e
@@ -315,13 +323,13 @@ class LLMStream(llm.LLMStream):
315
323
  raise APIStatusError(
316
324
  "gemini llm: api error",
317
325
  status_code=e.code,
318
- body=e.message,
326
+ body=e.message + e.status,
319
327
  request_id=request_id,
320
328
  retryable=retryable,
321
329
  ) from e
322
330
  except Exception as e:
323
331
  raise APIConnectionError(
324
- "gemini llm: error generating content",
332
+ f"gemini llm: error generating content {str(e)}",
325
333
  retryable=retryable,
326
334
  ) from e
327
335
 
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from dataclasses import dataclass
18
18
 
19
+ from google.api_core.client_options import ClientOptions
19
20
  from google.api_core.exceptions import DeadlineExceeded, GoogleAPICallError
20
21
  from google.cloud import texttospeech
21
22
  from google.cloud.texttospeech_v1.types import SsmlVoiceGender, SynthesizeSpeechResponse
@@ -50,6 +51,7 @@ class TTS(tts.TTS):
50
51
  pitch: int = 0,
51
52
  effects_profile_id: str = "",
52
53
  speaking_rate: float = 1.0,
54
+ location: str = "global",
53
55
  credentials_info: NotGivenOr[dict] = NOT_GIVEN,
54
56
  credentials_file: NotGivenOr[str] = NOT_GIVEN,
55
57
  ) -> None:
@@ -63,6 +65,7 @@ class TTS(tts.TTS):
63
65
  Args:
64
66
  voice (texttospeech.VoiceSelectionParams, optional): Voice selection parameters.
65
67
  sample_rate (int, optional): Audio sample rate in Hz. Default is 24000.
68
+ location (str, optional): Location for the TTS client. Default is "global".
66
69
  pitch (float, optional): Speaking pitch, ranging from -20.0 to 20.0 semitones relative to the original pitch. Default is 0.
67
70
  effects_profile_id (str): Optional identifier for selecting audio effects profiles to apply to the synthesized speech.
68
71
  speaking_rate (float, optional): Speed of speech. Default is 1.0.
@@ -81,7 +84,7 @@ class TTS(tts.TTS):
81
84
  self._client: texttospeech.TextToSpeechAsyncClient | None = None
82
85
  self._credentials_info = credentials_info
83
86
  self._credentials_file = credentials_file
84
-
87
+ self._location = location
85
88
  if not is_given(voice):
86
89
  voice = texttospeech.VoiceSelectionParams(
87
90
  name="",
@@ -119,18 +122,24 @@ class TTS(tts.TTS):
119
122
  self._opts.audio_config.speaking_rate = speaking_rate
120
123
 
121
124
  def _ensure_client(self) -> texttospeech.TextToSpeechAsyncClient:
125
+ api_endpoint = "texttospeech.googleapis.com"
126
+ if self._location != "global":
127
+ api_endpoint = f"{self._location}-texttospeech.googleapis.com"
128
+
122
129
  if self._client is None:
123
130
  if self._credentials_info:
124
131
  self._client = texttospeech.TextToSpeechAsyncClient.from_service_account_info(
125
- self._credentials_info
132
+ self._credentials_info, client_options=ClientOptions(api_endpoint=api_endpoint)
126
133
  )
127
134
 
128
135
  elif self._credentials_file:
129
136
  self._client = texttospeech.TextToSpeechAsyncClient.from_service_account_file(
130
- self._credentials_file
137
+ self._credentials_file, client_options=ClientOptions(api_endpoint=api_endpoint)
131
138
  )
132
139
  else:
133
- self._client = texttospeech.TextToSpeechAsyncClient()
140
+ self._client = texttospeech.TextToSpeechAsyncClient(
141
+ client_options=ClientOptions(api_endpoint=api_endpoint)
142
+ )
134
143
 
135
144
  assert self._client is not None
136
145
  return self._client
@@ -5,9 +5,11 @@ import re
5
5
  from copy import deepcopy
6
6
  from typing import Any
7
7
 
8
+ from pydantic import TypeAdapter
9
+
8
10
  from google.genai import types
9
11
  from livekit.agents import llm
10
- from livekit.agents.llm import FunctionTool
12
+ from livekit.agents.llm import FunctionTool, utils as llm_utils
11
13
 
12
14
  from .log import logger
13
15
 
@@ -78,6 +80,10 @@ def to_chat_ctx(
78
80
 
79
81
  if current_role is not None and parts:
80
82
  turns.append(types.Content(role=current_role, parts=parts))
83
+
84
+ if not turns:
85
+ # if no turns, add a user message with a placeholder
86
+ turns = [types.Content(role="user", parts=[types.Part(text=".")])]
81
87
  return turns, system_instruction
82
88
 
83
89
 
@@ -105,6 +111,16 @@ def _build_gemini_fnc(function_tool: FunctionTool) -> types.FunctionDeclaration:
105
111
  )
106
112
 
107
113
 
114
+ def to_response_format(response_format: type | dict) -> types.SchemaUnion:
115
+ _, json_schema_type = llm_utils.to_response_format_param(response_format)
116
+ if isinstance(json_schema_type, TypeAdapter):
117
+ schema = json_schema_type.json_schema()
118
+ else:
119
+ schema = json_schema_type.model_json_schema()
120
+
121
+ return _GeminiJsonSchema(schema).simplify()
122
+
123
+
108
124
  class _GeminiJsonSchema:
109
125
  """
110
126
  Transforms the JSON Schema from Pydantic to be suitable for Gemini.
@@ -136,6 +152,7 @@ class _GeminiJsonSchema:
136
152
  def _simplify(self, schema: dict[str, Any], refs_stack: tuple[str, ...]) -> None:
137
153
  schema.pop("title", None)
138
154
  schema.pop("default", None)
155
+ schema.pop("additionalProperties", None)
139
156
  if ref := schema.pop("$ref", None):
140
157
  key = re.sub(r"^#/\$defs/", "", ref)
141
158
  if key in refs_stack:
@@ -196,7 +213,6 @@ class _GeminiJsonSchema:
196
213
  "maxItems": "max_items",
197
214
  "minProperties": "min_properties",
198
215
  "maxProperties": "max_properties",
199
- "additionalProperties": "additional_properties",
200
216
  }
201
217
 
202
218
  for json_name, gemini_name in mappings.items():
@@ -204,11 +220,6 @@ class _GeminiJsonSchema:
204
220
  schema[gemini_name] = schema.pop(json_name)
205
221
 
206
222
  def _object(self, schema: dict[str, Any], refs_stack: tuple[str, ...]) -> None:
207
- # Gemini doesn't support additionalProperties
208
- ad_props = schema.pop("additional_properties", None)
209
- if ad_props:
210
- raise ValueError("Additional properties in JSON Schema are not supported by Gemini")
211
-
212
223
  if properties := schema.get("properties"):
213
224
  for value in properties.values():
214
225
  self._simplify(value, refs_stack)
@@ -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__ = '1.0.0.rc6'
15
+ __version__ = '1.0.0.rc8'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: livekit-plugins-google
3
- Version: 1.0.0rc6
3
+ Version: 1.0.0rc8
4
4
  Summary: Agent Framework plugin for services from Google Cloud
5
5
  Project-URL: Documentation, https://docs.livekit.io
6
6
  Project-URL: Website, https://livekit.io/
@@ -22,7 +22,7 @@ Requires-Dist: google-auth<3,>=2
22
22
  Requires-Dist: google-cloud-speech<3,>=2
23
23
  Requires-Dist: google-cloud-texttospeech<3,>=2
24
24
  Requires-Dist: google-genai==1.5.0
25
- Requires-Dist: livekit-agents>=1.0.0.rc6
25
+ Requires-Dist: livekit-agents>=1.0.0.rc8
26
26
  Description-Content-Type: text/markdown
27
27
 
28
28
  # LiveKit Plugins Google
@@ -1,18 +1,18 @@
1
1
  livekit/plugins/google/__init__.py,sha256=e_kSlFNmKhyyeliz7f4WOKc_Y0-y39QjO5nCWuguhss,1171
2
- livekit/plugins/google/llm.py,sha256=1yy6DaPxcO5EUN3JORpZ-q5ygdPWWmq6P3x73EbAqK4,14251
2
+ livekit/plugins/google/llm.py,sha256=81LCCJPmpMOkApX0S0a-zu5xIvcm2Pk8lTTz-PoK5m0,14740
3
3
  livekit/plugins/google/log.py,sha256=GI3YWN5YzrafnUccljzPRS_ZALkMNk1i21IRnTl2vNA,69
4
4
  livekit/plugins/google/models.py,sha256=SGjAumdDK97NNLwMFcqZdKR68f1NoGB2Rk1UP2-imG0,1457
5
5
  livekit/plugins/google/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  livekit/plugins/google/stt.py,sha256=fT5JtDM8ck2iMAzRvFKVeyT1oPt_R-bDkqiLa-ysikc,22539
7
- livekit/plugins/google/tts.py,sha256=mYm9n4zDzmNEAF3bSOb4-603CJrrdv9YJhrfbp5_k5A,7455
8
- livekit/plugins/google/utils.py,sha256=Ezh8eX6ld_11achNzUQqT4Owe1mKgOfBTl5IInt4Fm4,8411
9
- livekit/plugins/google/version.py,sha256=PDOEKN5zsYLQdbfAm5Di6G1sFYANNA0LqJR8zZzeghg,604
7
+ livekit/plugins/google/tts.py,sha256=aA3VuNaMcE6I1M43Sm-2mmvNyA9D2EyqfpyAporMUSg,8042
8
+ livekit/plugins/google/utils.py,sha256=dLkq-8lbWDC7AQ7nULd9unWwu_Wv9czdlxchyiJQ2KQ,8740
9
+ livekit/plugins/google/version.py,sha256=5qCM_eosxi507YOAQ3_Cf_-PVZbiE3NumVYwZ2yRcHQ,604
10
10
  livekit/plugins/google/beta/__init__.py,sha256=AxRYc7NGG62Tv1MmcZVCDHNvlhbC86hM-_yP01Qb28k,47
11
11
  livekit/plugins/google/beta/realtime/__init__.py,sha256=_fW2NMN22F-hnQ4xAJ_g5lPbR7CvM_xXzSWlUQY-E-U,188
12
12
  livekit/plugins/google/beta/realtime/api_proto.py,sha256=VO6QqOGOrxzsaOLBqnwNd8c-BId0PjwKicdrPTJisy0,688
13
13
  livekit/plugins/google/beta/realtime/realtime_api.py,sha256=ERM6WvcTtrfIyKpukzoSYrkhd3eYxIY-I09mKWp8vLk,22576
14
14
  livekit/plugins/google/beta/realtime/temp.py,sha256=an_YueuS_tUw3_QC6xWkkcw5JrJOBQFv2pJh6atpNcc,108
15
15
  livekit/plugins/google/beta/realtime/transcriber.py,sha256=DD7q894xc25GeeuKDar6-GwH-MxStEwhwBiX-KZ-Jo4,9559
16
- livekit_plugins_google-1.0.0rc6.dist-info/METADATA,sha256=FsushpxpQGoSVYyzBTT-mZ1tRLVx4kOquWN9-k-Nb0U,3496
17
- livekit_plugins_google-1.0.0rc6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- livekit_plugins_google-1.0.0rc6.dist-info/RECORD,,
16
+ livekit_plugins_google-1.0.0rc8.dist-info/METADATA,sha256=eEzJyC5OWDJoXzs5SdI6HyRhYzVJhJtwttc7px5daX4,3496
17
+ livekit_plugins_google-1.0.0rc8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ livekit_plugins_google-1.0.0rc8.dist-info/RECORD,,