llm-gemini 0.20a0__tar.gz → 0.20a2__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.
@@ -1,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llm-gemini
3
- Version: 0.20a0
3
+ Version: 0.20a2
4
4
  Summary: LLM plugin to access Google's Gemini family of models
5
5
  Author: Simon Willison
6
- License: Apache-2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/simonw/llm-gemini
8
8
  Project-URL: Changelog, https://github.com/simonw/llm-gemini/releases
9
9
  Project-URL: Issues, https://github.com/simonw/llm-gemini/issues
10
10
  Project-URL: CI, https://github.com/simonw/llm-gemini/actions
11
- Classifier: License :: OSI Approved :: Apache Software License
12
11
  Description-Content-Type: text/markdown
13
12
  License-File: LICENSE
14
- Requires-Dist: llm>=0.23
13
+ Requires-Dist: llm>=0.26a0
15
14
  Requires-Dist: httpx
16
15
  Requires-Dist: ijson
17
16
  Provides-Extra: test
@@ -67,7 +66,8 @@ llm "A joke about a pelican and a walrus"
67
66
  Other models are:
68
67
 
69
68
  - `gemini-2.5-pro-preview-05-06` - latest paid Gemini 2.5 Pro preview
70
- - `gemini-2.5-flash-preview-04-17` - Gemini 2.5 Flash preview
69
+ - `gemini-2.5-flash-preview-05-20` - Gemini 2.5 Flash preview
70
+ - `gemini-2.5-flash-preview-04-17` - Earlier Gemini 2.5 Flash preview
71
71
  - `gemini-2.5-pro-exp-03-25` - free experimental release of Gemini 2.5 Pro
72
72
  - `gemini-2.5-pro-preview-03-25` - paid preview of Gemini 2.5 Pro
73
73
  - `gemma-3-27b-it` - [Gemma 3](https://blog.google/technology/developers/gemma-3/) 27B
@@ -44,7 +44,8 @@ llm "A joke about a pelican and a walrus"
44
44
  Other models are:
45
45
 
46
46
  - `gemini-2.5-pro-preview-05-06` - latest paid Gemini 2.5 Pro preview
47
- - `gemini-2.5-flash-preview-04-17` - Gemini 2.5 Flash preview
47
+ - `gemini-2.5-flash-preview-05-20` - Gemini 2.5 Flash preview
48
+ - `gemini-2.5-flash-preview-04-17` - Earlier Gemini 2.5 Flash preview
48
49
  - `gemini-2.5-pro-exp-03-25` - free experimental release of Gemini 2.5 Pro
49
50
  - `gemini-2.5-pro-preview-03-25` - paid preview of Gemini 2.5 Pro
50
51
  - `gemma-3-27b-it` - [Gemma 3](https://blog.google/technology/developers/gemma-3/) 27B
@@ -1,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llm-gemini
3
- Version: 0.20a0
3
+ Version: 0.20a2
4
4
  Summary: LLM plugin to access Google's Gemini family of models
5
5
  Author: Simon Willison
6
- License: Apache-2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/simonw/llm-gemini
8
8
  Project-URL: Changelog, https://github.com/simonw/llm-gemini/releases
9
9
  Project-URL: Issues, https://github.com/simonw/llm-gemini/issues
10
10
  Project-URL: CI, https://github.com/simonw/llm-gemini/actions
11
- Classifier: License :: OSI Approved :: Apache Software License
12
11
  Description-Content-Type: text/markdown
13
12
  License-File: LICENSE
14
- Requires-Dist: llm>=0.23
13
+ Requires-Dist: llm>=0.26a0
15
14
  Requires-Dist: httpx
16
15
  Requires-Dist: ijson
17
16
  Provides-Extra: test
@@ -67,7 +66,8 @@ llm "A joke about a pelican and a walrus"
67
66
  Other models are:
68
67
 
69
68
  - `gemini-2.5-pro-preview-05-06` - latest paid Gemini 2.5 Pro preview
70
- - `gemini-2.5-flash-preview-04-17` - Gemini 2.5 Flash preview
69
+ - `gemini-2.5-flash-preview-05-20` - Gemini 2.5 Flash preview
70
+ - `gemini-2.5-flash-preview-04-17` - Earlier Gemini 2.5 Flash preview
71
71
  - `gemini-2.5-pro-exp-03-25` - free experimental release of Gemini 2.5 Pro
72
72
  - `gemini-2.5-pro-preview-03-25` - paid preview of Gemini 2.5 Pro
73
73
  - `gemma-3-27b-it` - [Gemma 3](https://blog.google/technology/developers/gemma-3/) 27B
@@ -1,4 +1,4 @@
1
- llm>=0.23
1
+ llm>=0.26a0
2
2
  httpx
3
3
  ijson
4
4
 
@@ -40,6 +40,7 @@ GOOGLE_SEARCH_MODELS = {
40
40
  "gemini-2.5-pro-exp-03-25",
41
41
  "gemini-2.5-flash-preview-04-17",
42
42
  "gemini-2.5-pro-preview-05-06",
43
+ "gemini-2.5-flash-preview-05-20",
43
44
  }
44
45
 
45
46
  # Older Google models used google_search_retrieval instead of google_search
@@ -55,6 +56,7 @@ GOOGLE_SEARCH_MODELS_USING_SEARCH_RETRIEVAL = {
55
56
 
56
57
  THINKING_BUDGET_MODELS = {
57
58
  "gemini-2.5-flash-preview-04-17",
59
+ "gemini-2.5-flash-preview-05-20",
58
60
  }
59
61
 
60
62
 
@@ -93,6 +95,8 @@ def register_models(register):
93
95
  "gemini-2.5-flash-preview-04-17",
94
96
  # 6th May 2025:
95
97
  "gemini-2.5-pro-preview-05-06",
98
+ # 20th May 2025:
99
+ "gemini-2.5-flash-preview-05-20",
96
100
  ]:
97
101
  can_google_search = model_id in GOOGLE_SEARCH_MODELS
98
102
  can_thinking_budget = model_id in THINKING_BUDGET_MODELS
@@ -148,6 +152,7 @@ class _SharedGemini:
148
152
  key_env_var = "LLM_GEMINI_KEY"
149
153
  can_stream = True
150
154
  supports_schema = True
155
+ supports_tools = True
151
156
 
152
157
  attachment_types = (
153
158
  # Text
@@ -273,14 +278,57 @@ class _SharedGemini:
273
278
  )
274
279
  if response.prompt.prompt:
275
280
  parts.append({"text": response.prompt.prompt})
281
+ if response.prompt.tool_results:
282
+ parts.extend(
283
+ [
284
+ {
285
+ "function_response": {
286
+ "name": tool_result.name,
287
+ "response": {
288
+ "output": tool_result.output,
289
+ },
290
+ }
291
+ }
292
+ for tool_result in response.prompt.tool_results
293
+ ]
294
+ )
276
295
  messages.append({"role": "user", "parts": parts})
277
- messages.append(
278
- {"role": "model", "parts": [{"text": response.text_or_raise()}]}
279
- )
296
+ model_parts = []
297
+ response_text = response.text_or_raise()
298
+ if response_text:
299
+ model_parts.append({"text": response_text})
300
+ tool_calls = response.tool_calls_or_raise()
301
+ if tool_calls:
302
+ model_parts.extend(
303
+ [
304
+ {
305
+ "function_call": {
306
+ "name": tool_call.name,
307
+ "args": tool_call.arguments,
308
+ }
309
+ }
310
+ for tool_call in tool_calls
311
+ ]
312
+ )
313
+ messages.append({"role": "model", "parts": model_parts})
280
314
 
281
315
  parts = []
282
316
  if prompt.prompt:
283
317
  parts.append({"text": prompt.prompt})
318
+ if prompt.tool_results:
319
+ parts.extend(
320
+ [
321
+ {
322
+ "function_response": {
323
+ "name": tool_result.name,
324
+ "response": {
325
+ "output": tool_result.output,
326
+ },
327
+ }
328
+ }
329
+ for tool_result in prompt.tool_results
330
+ ]
331
+ )
284
332
  for attachment in prompt.attachments:
285
333
  mime_type = resolve_type(attachment)
286
334
  parts.append(
@@ -300,17 +348,34 @@ class _SharedGemini:
300
348
  "contents": self.build_messages(prompt, conversation),
301
349
  "safetySettings": SAFETY_SETTINGS,
302
350
  }
351
+ if prompt.system:
352
+ body["systemInstruction"] = {"parts": [{"text": prompt.system}]}
353
+
354
+ tools = []
303
355
  if prompt.options and prompt.options.code_execution:
304
- body["tools"] = [{"codeExecution": {}}]
356
+ tools.append({"codeExecution": {}})
305
357
  if prompt.options and self.can_google_search and prompt.options.google_search:
306
358
  tool_name = (
307
359
  "google_search_retrieval"
308
360
  if self.model_id in GOOGLE_SEARCH_MODELS_USING_SEARCH_RETRIEVAL
309
361
  else "google_search"
310
362
  )
311
- body["tools"] = [{tool_name: {}}]
312
- if prompt.system:
313
- body["systemInstruction"] = {"parts": [{"text": prompt.system}]}
363
+ tools.append({tool_name: {}})
364
+ if prompt.tools:
365
+ tools.append(
366
+ {
367
+ "functionDeclarations": [
368
+ {
369
+ "name": tool.name,
370
+ "description": tool.description,
371
+ "parameters": tool.input_schema,
372
+ }
373
+ for tool in prompt.tools
374
+ ]
375
+ }
376
+ )
377
+ if tools:
378
+ body["tools"] = tools
314
379
 
315
380
  generation_config = {}
316
381
 
@@ -321,6 +386,7 @@ class _SharedGemini:
321
386
  "response_schema": cleanup_schema(copy.deepcopy(prompt.schema)),
322
387
  }
323
388
  )
389
+
324
390
  if self.can_thinking_budget and prompt.options.thinking_budget is not None:
325
391
  generation_config["thinking_config"] = {
326
392
  "thinking_budget": prompt.options.thinking_budget
@@ -348,7 +414,14 @@ class _SharedGemini:
348
414
 
349
415
  return body
350
416
 
351
- def process_part(self, part):
417
+ def process_part(self, part, response):
418
+ if "functionCall" in part:
419
+ response.add_tool_call(
420
+ llm.ToolCall(
421
+ name=part["functionCall"]["name"],
422
+ arguments=part["functionCall"]["args"],
423
+ )
424
+ )
352
425
  if "text" in part:
353
426
  return part["text"]
354
427
  elif "executableCode" in part:
@@ -357,10 +430,10 @@ class _SharedGemini:
357
430
  return f'```\n{part["codeExecutionResult"]["output"].strip()}\n```\n'
358
431
  return ""
359
432
 
360
- def process_candidates(self, candidates):
433
+ def process_candidates(self, candidates, response):
361
434
  # We only use the first candidate
362
435
  for part in candidates[0]["content"]["parts"]:
363
- yield self.process_part(part)
436
+ yield self.process_part(part, response)
364
437
 
365
438
  def set_usage(self, response):
366
439
  try:
@@ -404,7 +477,9 @@ class GeminiPro(_SharedGemini, llm.KeyModel):
404
477
  if isinstance(event, dict) and "error" in event:
405
478
  raise llm.ModelError(event["error"]["message"])
406
479
  try:
407
- yield from self.process_candidates(event["candidates"])
480
+ yield from self.process_candidates(
481
+ event["candidates"], response
482
+ )
408
483
  except KeyError:
409
484
  yield ""
410
485
  gathered.append(event)
@@ -437,7 +512,7 @@ class AsyncGeminiPro(_SharedGemini, llm.AsyncKeyModel):
437
512
  raise llm.ModelError(event["error"]["message"])
438
513
  try:
439
514
  for chunk in self.process_candidates(
440
- event["candidates"]
515
+ event["candidates"], response
441
516
  ):
442
517
  yield chunk
443
518
  except KeyError:
@@ -1,15 +1,13 @@
1
1
  [project]
2
2
  name = "llm-gemini"
3
- version = "0.20a0"
3
+ version = "0.20a2"
4
4
  description = "LLM plugin to access Google's Gemini family of models"
5
5
  readme = "README.md"
6
6
  authors = [{name = "Simon Willison"}]
7
- license = {text = "Apache-2.0"}
8
- classifiers = [
9
- "License :: OSI Approved :: Apache Software License"
10
- ]
7
+ license = "Apache-2.0"
8
+ classifiers = []
11
9
  dependencies = [
12
- "llm>=0.23",
10
+ "llm>=0.26a0",
13
11
  "httpx",
14
12
  "ijson"
15
13
  ]
@@ -232,3 +232,27 @@ def test_cli_gemini_models(tmpdir, monkeypatch):
232
232
  result2 = runner.invoke(cli, ["gemini", "models", "--key", GEMINI_API_KEY])
233
233
  assert result2.exit_code == 0
234
234
  assert "gemini-1.5-flash-latest" in result2.output
235
+
236
+
237
+ @pytest.mark.vcr
238
+ def test_tools():
239
+ model = llm.get_model("gemini-2.0-flash")
240
+ names = ["Charles", "Sammy"]
241
+ chain_response = model.chain(
242
+ "Two names for a pet pelican",
243
+ tools=[
244
+ llm.Tool.function(lambda: names.pop(0), name="pelican_name_generator"),
245
+ ],
246
+ key=GEMINI_API_KEY,
247
+ )
248
+ text = chain_response.text()
249
+ assert text == "Okay, here are two names for a pet pelican: Charles and Sammy.\n"
250
+ # This one did three
251
+ assert len(chain_response._responses) == 3
252
+ first, second, third = chain_response._responses
253
+ assert len(first.tool_calls()) == 1
254
+ assert first.tool_calls()[0].name == "pelican_name_generator"
255
+ assert len(second.tool_calls()) == 1
256
+ assert second.tool_calls()[0].name == "pelican_name_generator"
257
+ assert second.prompt.tool_results[0].output == "Charles"
258
+ assert third.prompt.tool_results[0].output == "Sammy"
File without changes
File without changes