agno 2.2.13__py3-none-any.whl → 2.3.1__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.
- agno/agent/agent.py +197 -110
- agno/api/api.py +2 -0
- agno/db/base.py +26 -0
- agno/db/dynamo/dynamo.py +8 -0
- agno/db/dynamo/schemas.py +1 -0
- agno/db/firestore/firestore.py +8 -0
- agno/db/firestore/schemas.py +1 -0
- agno/db/gcs_json/gcs_json_db.py +8 -0
- agno/db/in_memory/in_memory_db.py +8 -1
- agno/db/json/json_db.py +8 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/async_mongo.py +16 -6
- agno/db/mongo/mongo.py +11 -0
- agno/db/mongo/schemas.py +3 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/mysql.py +76 -3
- agno/db/mysql/schemas.py +20 -10
- agno/db/postgres/async_postgres.py +99 -25
- agno/db/postgres/postgres.py +75 -6
- agno/db/postgres/schemas.py +30 -20
- agno/db/redis/redis.py +15 -2
- agno/db/redis/schemas.py +4 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +11 -0
- agno/db/singlestore/singlestore.py +79 -5
- agno/db/sqlite/async_sqlite.py +97 -19
- agno/db/sqlite/schemas.py +10 -0
- agno/db/sqlite/sqlite.py +79 -2
- agno/db/surrealdb/surrealdb.py +8 -0
- agno/knowledge/chunking/semantic.py +7 -2
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/knowledge.py +57 -86
- agno/knowledge/reader/csv_reader.py +7 -9
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -18
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +11 -11
- agno/knowledge/reader/pptx_reader.py +5 -5
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +8 -8
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/models/anthropic/claude.py +319 -28
- agno/models/aws/claude.py +32 -0
- agno/models/azure/openai_chat.py +19 -10
- agno/models/base.py +612 -545
- agno/models/cerebras/cerebras.py +8 -11
- agno/models/cohere/chat.py +27 -1
- agno/models/google/gemini.py +39 -7
- agno/models/groq/groq.py +25 -11
- agno/models/meta/llama.py +20 -9
- agno/models/meta/llama_openai.py +3 -19
- agno/models/nebius/nebius.py +4 -4
- agno/models/openai/chat.py +30 -14
- agno/models/openai/responses.py +10 -13
- agno/models/response.py +1 -0
- agno/models/vertexai/claude.py +26 -0
- agno/os/app.py +8 -19
- agno/os/router.py +54 -0
- agno/os/routers/knowledge/knowledge.py +2 -2
- agno/os/schema.py +2 -2
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -112
- agno/session/workflow.py +353 -57
- agno/team/team.py +227 -125
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/http.py +111 -0
- agno/utils/media.py +11 -0
- agno/utils/models/claude.py +8 -0
- agno/utils/print_response/agent.py +33 -12
- agno/utils/print_response/team.py +22 -12
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/workflow/condition.py +13 -0
- agno/workflow/loop.py +13 -0
- agno/workflow/parallel.py +13 -0
- agno/workflow/router.py +13 -0
- agno/workflow/step.py +120 -20
- agno/workflow/steps.py +13 -0
- agno/workflow/workflow.py +76 -63
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/METADATA +6 -2
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/RECORD +91 -88
- agno/tools/googlesearch.py +0 -98
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/WHEEL +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.3.1.dist-info}/top_level.txt +0 -0
agno/models/base.py
CHANGED
|
@@ -325,155 +325,168 @@ class Model(ABC):
|
|
|
325
325
|
run_response: Run response to use
|
|
326
326
|
send_media_to_model: Whether to send media to the model
|
|
327
327
|
"""
|
|
328
|
+
try:
|
|
329
|
+
# Check cache if enabled
|
|
330
|
+
if self.cache_response:
|
|
331
|
+
cache_key = self._get_model_cache_key(
|
|
332
|
+
messages, stream=False, response_format=response_format, tools=tools
|
|
333
|
+
)
|
|
334
|
+
cached_data = self._get_cached_model_response(cache_key)
|
|
328
335
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
cached_data = self._get_cached_model_response(cache_key)
|
|
333
|
-
|
|
334
|
-
if cached_data:
|
|
335
|
-
log_info("Cache hit for model response")
|
|
336
|
-
return self._model_response_from_cache(cached_data)
|
|
337
|
-
|
|
338
|
-
log_debug(f"{self.get_provider()} Response Start", center=True, symbol="-")
|
|
339
|
-
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
340
|
-
|
|
341
|
-
_log_messages(messages)
|
|
342
|
-
model_response = ModelResponse()
|
|
343
|
-
|
|
344
|
-
function_call_count = 0
|
|
345
|
-
|
|
346
|
-
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
347
|
-
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
348
|
-
|
|
349
|
-
while True:
|
|
350
|
-
# Get response from model
|
|
351
|
-
assistant_message = Message(role=self.assistant_message_role)
|
|
352
|
-
self._process_model_response(
|
|
353
|
-
messages=messages,
|
|
354
|
-
assistant_message=assistant_message,
|
|
355
|
-
model_response=model_response,
|
|
356
|
-
response_format=response_format,
|
|
357
|
-
tools=_tool_dicts,
|
|
358
|
-
tool_choice=tool_choice or self._tool_choice,
|
|
359
|
-
run_response=run_response,
|
|
360
|
-
)
|
|
336
|
+
if cached_data:
|
|
337
|
+
log_info("Cache hit for model response")
|
|
338
|
+
return self._model_response_from_cache(cached_data)
|
|
361
339
|
|
|
362
|
-
|
|
363
|
-
|
|
340
|
+
log_debug(f"{self.get_provider()} Response Start", center=True, symbol="-")
|
|
341
|
+
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
364
342
|
|
|
365
|
-
|
|
366
|
-
|
|
343
|
+
_log_messages(messages)
|
|
344
|
+
model_response = ModelResponse()
|
|
367
345
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
346
|
+
function_call_count = 0
|
|
347
|
+
|
|
348
|
+
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
349
|
+
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
350
|
+
|
|
351
|
+
while True:
|
|
352
|
+
# Get response from model
|
|
353
|
+
assistant_message = Message(role=self.assistant_message_role)
|
|
354
|
+
self._process_model_response(
|
|
373
355
|
messages=messages,
|
|
356
|
+
assistant_message=assistant_message,
|
|
374
357
|
model_response=model_response,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
# Execute function calls
|
|
380
|
-
for function_call_response in self.run_function_calls(
|
|
381
|
-
function_calls=function_calls_to_run,
|
|
382
|
-
function_call_results=function_call_results,
|
|
383
|
-
current_function_call_count=function_call_count,
|
|
384
|
-
function_call_limit=tool_call_limit,
|
|
385
|
-
):
|
|
386
|
-
if isinstance(function_call_response, ModelResponse):
|
|
387
|
-
# The session state is updated by the function call
|
|
388
|
-
if function_call_response.updated_session_state is not None:
|
|
389
|
-
model_response.updated_session_state = function_call_response.updated_session_state
|
|
390
|
-
|
|
391
|
-
# Media artifacts are generated by the function call
|
|
392
|
-
if function_call_response.images is not None:
|
|
393
|
-
if model_response.images is None:
|
|
394
|
-
model_response.images = []
|
|
395
|
-
model_response.images.extend(function_call_response.images)
|
|
396
|
-
|
|
397
|
-
if function_call_response.audios is not None:
|
|
398
|
-
if model_response.audios is None:
|
|
399
|
-
model_response.audios = []
|
|
400
|
-
model_response.audios.extend(function_call_response.audios)
|
|
401
|
-
|
|
402
|
-
if function_call_response.videos is not None:
|
|
403
|
-
if model_response.videos is None:
|
|
404
|
-
model_response.videos = []
|
|
405
|
-
model_response.videos.extend(function_call_response.videos)
|
|
406
|
-
|
|
407
|
-
if function_call_response.files is not None:
|
|
408
|
-
if model_response.files is None:
|
|
409
|
-
model_response.files = []
|
|
410
|
-
model_response.files.extend(function_call_response.files)
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
function_call_response.event
|
|
414
|
-
in [
|
|
415
|
-
ModelResponseEvent.tool_call_completed.value,
|
|
416
|
-
ModelResponseEvent.tool_call_paused.value,
|
|
417
|
-
]
|
|
418
|
-
and function_call_response.tool_executions is not None
|
|
419
|
-
):
|
|
420
|
-
if model_response.tool_executions is None:
|
|
421
|
-
model_response.tool_executions = []
|
|
422
|
-
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
423
|
-
|
|
424
|
-
elif function_call_response.event not in [
|
|
425
|
-
ModelResponseEvent.tool_call_started.value,
|
|
426
|
-
ModelResponseEvent.tool_call_completed.value,
|
|
427
|
-
]:
|
|
428
|
-
if function_call_response.content:
|
|
429
|
-
model_response.content += function_call_response.content # type: ignore
|
|
430
|
-
|
|
431
|
-
# Add a function call for each successful execution
|
|
432
|
-
function_call_count += len(function_call_results)
|
|
433
|
-
|
|
434
|
-
# Format and add results to messages
|
|
435
|
-
self.format_function_call_results(
|
|
436
|
-
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
358
|
+
response_format=response_format,
|
|
359
|
+
tools=_tool_dicts,
|
|
360
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
361
|
+
run_response=run_response,
|
|
437
362
|
)
|
|
438
363
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
364
|
+
# Add assistant message to messages
|
|
365
|
+
messages.append(assistant_message)
|
|
366
|
+
|
|
367
|
+
# Log response and metrics
|
|
368
|
+
assistant_message.log(metrics=True)
|
|
369
|
+
|
|
370
|
+
# Handle tool calls if present
|
|
371
|
+
if assistant_message.tool_calls:
|
|
372
|
+
# Prepare function calls
|
|
373
|
+
function_calls_to_run = self._prepare_function_calls(
|
|
374
|
+
assistant_message=assistant_message,
|
|
442
375
|
messages=messages,
|
|
376
|
+
model_response=model_response,
|
|
377
|
+
functions=_functions,
|
|
378
|
+
)
|
|
379
|
+
function_call_results: List[Message] = []
|
|
380
|
+
|
|
381
|
+
# Execute function calls
|
|
382
|
+
for function_call_response in self.run_function_calls(
|
|
383
|
+
function_calls=function_calls_to_run,
|
|
443
384
|
function_call_results=function_call_results,
|
|
444
|
-
|
|
385
|
+
current_function_call_count=function_call_count,
|
|
386
|
+
function_call_limit=tool_call_limit,
|
|
387
|
+
):
|
|
388
|
+
if isinstance(function_call_response, ModelResponse):
|
|
389
|
+
# The session state is updated by the function call
|
|
390
|
+
if function_call_response.updated_session_state is not None:
|
|
391
|
+
model_response.updated_session_state = function_call_response.updated_session_state
|
|
392
|
+
|
|
393
|
+
# Media artifacts are generated by the function call
|
|
394
|
+
if function_call_response.images is not None:
|
|
395
|
+
if model_response.images is None:
|
|
396
|
+
model_response.images = []
|
|
397
|
+
model_response.images.extend(function_call_response.images)
|
|
398
|
+
|
|
399
|
+
if function_call_response.audios is not None:
|
|
400
|
+
if model_response.audios is None:
|
|
401
|
+
model_response.audios = []
|
|
402
|
+
model_response.audios.extend(function_call_response.audios)
|
|
403
|
+
|
|
404
|
+
if function_call_response.videos is not None:
|
|
405
|
+
if model_response.videos is None:
|
|
406
|
+
model_response.videos = []
|
|
407
|
+
model_response.videos.extend(function_call_response.videos)
|
|
408
|
+
|
|
409
|
+
if function_call_response.files is not None:
|
|
410
|
+
if model_response.files is None:
|
|
411
|
+
model_response.files = []
|
|
412
|
+
model_response.files.extend(function_call_response.files)
|
|
413
|
+
|
|
414
|
+
if (
|
|
415
|
+
function_call_response.event
|
|
416
|
+
in [
|
|
417
|
+
ModelResponseEvent.tool_call_completed.value,
|
|
418
|
+
ModelResponseEvent.tool_call_paused.value,
|
|
419
|
+
]
|
|
420
|
+
and function_call_response.tool_executions is not None
|
|
421
|
+
):
|
|
422
|
+
if model_response.tool_executions is None:
|
|
423
|
+
model_response.tool_executions = []
|
|
424
|
+
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
425
|
+
|
|
426
|
+
elif function_call_response.event not in [
|
|
427
|
+
ModelResponseEvent.tool_call_started.value,
|
|
428
|
+
ModelResponseEvent.tool_call_completed.value,
|
|
429
|
+
]:
|
|
430
|
+
if function_call_response.content:
|
|
431
|
+
model_response.content += function_call_response.content # type: ignore
|
|
432
|
+
|
|
433
|
+
# Add a function call for each successful execution
|
|
434
|
+
function_call_count += len(function_call_results)
|
|
435
|
+
|
|
436
|
+
# Format and add results to messages
|
|
437
|
+
self.format_function_call_results(
|
|
438
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
445
439
|
)
|
|
446
440
|
|
|
447
|
-
|
|
448
|
-
|
|
441
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
442
|
+
# Handle function call media
|
|
443
|
+
self._handle_function_call_media(
|
|
444
|
+
messages=messages,
|
|
445
|
+
function_call_results=function_call_results,
|
|
446
|
+
send_media_to_model=send_media_to_model,
|
|
447
|
+
)
|
|
449
448
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
break
|
|
449
|
+
for function_call_result in function_call_results:
|
|
450
|
+
function_call_result.log(metrics=True)
|
|
453
451
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
452
|
+
# Check if we should stop after tool calls
|
|
453
|
+
if any(m.stop_after_tool_call for m in function_call_results):
|
|
454
|
+
break
|
|
457
455
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
# If we have any tool calls that require confirmation, break the loop
|
|
457
|
+
if any(tc.requires_confirmation for tc in model_response.tool_executions or []):
|
|
458
|
+
break
|
|
461
459
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
460
|
+
# If we have any tool calls that require external execution, break the loop
|
|
461
|
+
if any(tc.external_execution_required for tc in model_response.tool_executions or []):
|
|
462
|
+
break
|
|
465
463
|
|
|
466
|
-
|
|
467
|
-
|
|
464
|
+
# If we have any tool calls that require user input, break the loop
|
|
465
|
+
if any(tc.requires_user_input for tc in model_response.tool_executions or []):
|
|
466
|
+
break
|
|
468
467
|
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
# Continue loop to get next response
|
|
469
|
+
continue
|
|
471
470
|
|
|
472
|
-
|
|
471
|
+
# No tool calls or finished processing them
|
|
472
|
+
break
|
|
473
473
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
474
|
+
log_debug(f"{self.get_provider()} Response End", center=True, symbol="-")
|
|
475
|
+
|
|
476
|
+
# Save to cache if enabled
|
|
477
|
+
if self.cache_response:
|
|
478
|
+
self._save_model_response_to_cache(cache_key, model_response, is_streaming=False)
|
|
479
|
+
finally:
|
|
480
|
+
# Close the Gemini client
|
|
481
|
+
if self.__class__.__name__ == "Gemini" and self.client is not None: # type: ignore
|
|
482
|
+
try:
|
|
483
|
+
self.client.close() # type: ignore
|
|
484
|
+
self.client = None
|
|
485
|
+
except AttributeError:
|
|
486
|
+
log_warning(
|
|
487
|
+
"Your Gemini client is outdated. For Agno to properly handle the lifecycle of the client,"
|
|
488
|
+
" please upgrade Gemini to the latest version: pip install -U google-genai"
|
|
489
|
+
)
|
|
477
490
|
|
|
478
491
|
return model_response
|
|
479
492
|
|
|
@@ -490,153 +503,166 @@ class Model(ABC):
|
|
|
490
503
|
"""
|
|
491
504
|
Generate an asynchronous response from the model.
|
|
492
505
|
"""
|
|
506
|
+
try:
|
|
507
|
+
# Check cache if enabled
|
|
508
|
+
if self.cache_response:
|
|
509
|
+
cache_key = self._get_model_cache_key(
|
|
510
|
+
messages, stream=False, response_format=response_format, tools=tools
|
|
511
|
+
)
|
|
512
|
+
cached_data = self._get_cached_model_response(cache_key)
|
|
493
513
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
cached_data = self._get_cached_model_response(cache_key)
|
|
498
|
-
|
|
499
|
-
if cached_data:
|
|
500
|
-
log_info("Cache hit for model response")
|
|
501
|
-
return self._model_response_from_cache(cached_data)
|
|
502
|
-
|
|
503
|
-
log_debug(f"{self.get_provider()} Async Response Start", center=True, symbol="-")
|
|
504
|
-
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
505
|
-
_log_messages(messages)
|
|
506
|
-
model_response = ModelResponse()
|
|
507
|
-
|
|
508
|
-
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
509
|
-
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
510
|
-
|
|
511
|
-
function_call_count = 0
|
|
512
|
-
|
|
513
|
-
while True:
|
|
514
|
-
# Get response from model
|
|
515
|
-
assistant_message = Message(role=self.assistant_message_role)
|
|
516
|
-
await self._aprocess_model_response(
|
|
517
|
-
messages=messages,
|
|
518
|
-
assistant_message=assistant_message,
|
|
519
|
-
model_response=model_response,
|
|
520
|
-
response_format=response_format,
|
|
521
|
-
tools=_tool_dicts,
|
|
522
|
-
tool_choice=tool_choice or self._tool_choice,
|
|
523
|
-
run_response=run_response,
|
|
524
|
-
)
|
|
514
|
+
if cached_data:
|
|
515
|
+
log_info("Cache hit for model response")
|
|
516
|
+
return self._model_response_from_cache(cached_data)
|
|
525
517
|
|
|
526
|
-
|
|
527
|
-
|
|
518
|
+
log_debug(f"{self.get_provider()} Async Response Start", center=True, symbol="-")
|
|
519
|
+
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
520
|
+
_log_messages(messages)
|
|
521
|
+
model_response = ModelResponse()
|
|
528
522
|
|
|
529
|
-
|
|
530
|
-
|
|
523
|
+
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
524
|
+
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
531
525
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
526
|
+
function_call_count = 0
|
|
527
|
+
|
|
528
|
+
while True:
|
|
529
|
+
# Get response from model
|
|
530
|
+
assistant_message = Message(role=self.assistant_message_role)
|
|
531
|
+
await self._aprocess_model_response(
|
|
537
532
|
messages=messages,
|
|
533
|
+
assistant_message=assistant_message,
|
|
538
534
|
model_response=model_response,
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
# Execute function calls
|
|
544
|
-
async for function_call_response in self.arun_function_calls(
|
|
545
|
-
function_calls=function_calls_to_run,
|
|
546
|
-
function_call_results=function_call_results,
|
|
547
|
-
current_function_call_count=function_call_count,
|
|
548
|
-
function_call_limit=tool_call_limit,
|
|
549
|
-
):
|
|
550
|
-
if isinstance(function_call_response, ModelResponse):
|
|
551
|
-
# The session state is updated by the function call
|
|
552
|
-
if function_call_response.updated_session_state is not None:
|
|
553
|
-
model_response.updated_session_state = function_call_response.updated_session_state
|
|
554
|
-
|
|
555
|
-
# Media artifacts are generated by the function call
|
|
556
|
-
if function_call_response.images is not None:
|
|
557
|
-
if model_response.images is None:
|
|
558
|
-
model_response.images = []
|
|
559
|
-
model_response.images.extend(function_call_response.images)
|
|
560
|
-
|
|
561
|
-
if function_call_response.audios is not None:
|
|
562
|
-
if model_response.audios is None:
|
|
563
|
-
model_response.audios = []
|
|
564
|
-
model_response.audios.extend(function_call_response.audios)
|
|
565
|
-
|
|
566
|
-
if function_call_response.videos is not None:
|
|
567
|
-
if model_response.videos is None:
|
|
568
|
-
model_response.videos = []
|
|
569
|
-
model_response.videos.extend(function_call_response.videos)
|
|
570
|
-
|
|
571
|
-
if function_call_response.files is not None:
|
|
572
|
-
if model_response.files is None:
|
|
573
|
-
model_response.files = []
|
|
574
|
-
model_response.files.extend(function_call_response.files)
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
function_call_response.event
|
|
578
|
-
in [
|
|
579
|
-
ModelResponseEvent.tool_call_completed.value,
|
|
580
|
-
ModelResponseEvent.tool_call_paused.value,
|
|
581
|
-
]
|
|
582
|
-
and function_call_response.tool_executions is not None
|
|
583
|
-
):
|
|
584
|
-
if model_response.tool_executions is None:
|
|
585
|
-
model_response.tool_executions = []
|
|
586
|
-
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
587
|
-
elif function_call_response.event not in [
|
|
588
|
-
ModelResponseEvent.tool_call_started.value,
|
|
589
|
-
ModelResponseEvent.tool_call_completed.value,
|
|
590
|
-
]:
|
|
591
|
-
if function_call_response.content:
|
|
592
|
-
model_response.content += function_call_response.content # type: ignore
|
|
593
|
-
|
|
594
|
-
# Add a function call for each successful execution
|
|
595
|
-
function_call_count += len(function_call_results)
|
|
596
|
-
|
|
597
|
-
# Format and add results to messages
|
|
598
|
-
self.format_function_call_results(
|
|
599
|
-
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
535
|
+
response_format=response_format,
|
|
536
|
+
tools=_tool_dicts,
|
|
537
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
538
|
+
run_response=run_response,
|
|
600
539
|
)
|
|
601
540
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
541
|
+
# Add assistant message to messages
|
|
542
|
+
messages.append(assistant_message)
|
|
543
|
+
|
|
544
|
+
# Log response and metrics
|
|
545
|
+
assistant_message.log(metrics=True)
|
|
546
|
+
|
|
547
|
+
# Handle tool calls if present
|
|
548
|
+
if assistant_message.tool_calls:
|
|
549
|
+
# Prepare function calls
|
|
550
|
+
function_calls_to_run = self._prepare_function_calls(
|
|
551
|
+
assistant_message=assistant_message,
|
|
605
552
|
messages=messages,
|
|
553
|
+
model_response=model_response,
|
|
554
|
+
functions=_functions,
|
|
555
|
+
)
|
|
556
|
+
function_call_results: List[Message] = []
|
|
557
|
+
|
|
558
|
+
# Execute function calls
|
|
559
|
+
async for function_call_response in self.arun_function_calls(
|
|
560
|
+
function_calls=function_calls_to_run,
|
|
606
561
|
function_call_results=function_call_results,
|
|
607
|
-
|
|
562
|
+
current_function_call_count=function_call_count,
|
|
563
|
+
function_call_limit=tool_call_limit,
|
|
564
|
+
):
|
|
565
|
+
if isinstance(function_call_response, ModelResponse):
|
|
566
|
+
# The session state is updated by the function call
|
|
567
|
+
if function_call_response.updated_session_state is not None:
|
|
568
|
+
model_response.updated_session_state = function_call_response.updated_session_state
|
|
569
|
+
|
|
570
|
+
# Media artifacts are generated by the function call
|
|
571
|
+
if function_call_response.images is not None:
|
|
572
|
+
if model_response.images is None:
|
|
573
|
+
model_response.images = []
|
|
574
|
+
model_response.images.extend(function_call_response.images)
|
|
575
|
+
|
|
576
|
+
if function_call_response.audios is not None:
|
|
577
|
+
if model_response.audios is None:
|
|
578
|
+
model_response.audios = []
|
|
579
|
+
model_response.audios.extend(function_call_response.audios)
|
|
580
|
+
|
|
581
|
+
if function_call_response.videos is not None:
|
|
582
|
+
if model_response.videos is None:
|
|
583
|
+
model_response.videos = []
|
|
584
|
+
model_response.videos.extend(function_call_response.videos)
|
|
585
|
+
|
|
586
|
+
if function_call_response.files is not None:
|
|
587
|
+
if model_response.files is None:
|
|
588
|
+
model_response.files = []
|
|
589
|
+
model_response.files.extend(function_call_response.files)
|
|
590
|
+
|
|
591
|
+
if (
|
|
592
|
+
function_call_response.event
|
|
593
|
+
in [
|
|
594
|
+
ModelResponseEvent.tool_call_completed.value,
|
|
595
|
+
ModelResponseEvent.tool_call_paused.value,
|
|
596
|
+
]
|
|
597
|
+
and function_call_response.tool_executions is not None
|
|
598
|
+
):
|
|
599
|
+
if model_response.tool_executions is None:
|
|
600
|
+
model_response.tool_executions = []
|
|
601
|
+
model_response.tool_executions.extend(function_call_response.tool_executions)
|
|
602
|
+
elif function_call_response.event not in [
|
|
603
|
+
ModelResponseEvent.tool_call_started.value,
|
|
604
|
+
ModelResponseEvent.tool_call_completed.value,
|
|
605
|
+
]:
|
|
606
|
+
if function_call_response.content:
|
|
607
|
+
model_response.content += function_call_response.content # type: ignore
|
|
608
|
+
|
|
609
|
+
# Add a function call for each successful execution
|
|
610
|
+
function_call_count += len(function_call_results)
|
|
611
|
+
|
|
612
|
+
# Format and add results to messages
|
|
613
|
+
self.format_function_call_results(
|
|
614
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
608
615
|
)
|
|
609
616
|
|
|
610
|
-
|
|
611
|
-
|
|
617
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
618
|
+
# Handle function call media
|
|
619
|
+
self._handle_function_call_media(
|
|
620
|
+
messages=messages,
|
|
621
|
+
function_call_results=function_call_results,
|
|
622
|
+
send_media_to_model=send_media_to_model,
|
|
623
|
+
)
|
|
612
624
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
break
|
|
625
|
+
for function_call_result in function_call_results:
|
|
626
|
+
function_call_result.log(metrics=True)
|
|
616
627
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
628
|
+
# Check if we should stop after tool calls
|
|
629
|
+
if any(m.stop_after_tool_call for m in function_call_results):
|
|
630
|
+
break
|
|
620
631
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
632
|
+
# If we have any tool calls that require confirmation, break the loop
|
|
633
|
+
if any(tc.requires_confirmation for tc in model_response.tool_executions or []):
|
|
634
|
+
break
|
|
624
635
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
636
|
+
# If we have any tool calls that require external execution, break the loop
|
|
637
|
+
if any(tc.external_execution_required for tc in model_response.tool_executions or []):
|
|
638
|
+
break
|
|
628
639
|
|
|
629
|
-
|
|
630
|
-
|
|
640
|
+
# If we have any tool calls that require user input, break the loop
|
|
641
|
+
if any(tc.requires_user_input for tc in model_response.tool_executions or []):
|
|
642
|
+
break
|
|
631
643
|
|
|
632
|
-
|
|
633
|
-
|
|
644
|
+
# Continue loop to get next response
|
|
645
|
+
continue
|
|
634
646
|
|
|
635
|
-
|
|
647
|
+
# No tool calls or finished processing them
|
|
648
|
+
break
|
|
636
649
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
650
|
+
log_debug(f"{self.get_provider()} Async Response End", center=True, symbol="-")
|
|
651
|
+
|
|
652
|
+
# Save to cache if enabled
|
|
653
|
+
if self.cache_response:
|
|
654
|
+
self._save_model_response_to_cache(cache_key, model_response, is_streaming=False)
|
|
655
|
+
finally:
|
|
656
|
+
# Close the Gemini client
|
|
657
|
+
if self.__class__.__name__ == "Gemini" and self.client is not None:
|
|
658
|
+
try:
|
|
659
|
+
await self.client.aio.aclose() # type: ignore
|
|
660
|
+
self.client = None
|
|
661
|
+
except AttributeError:
|
|
662
|
+
log_warning(
|
|
663
|
+
"Your Gemini client is outdated. For Agno to properly handle the lifecycle of the client,"
|
|
664
|
+
" please upgrade Gemini to the latest version: pip install -U google-genai"
|
|
665
|
+
)
|
|
640
666
|
|
|
641
667
|
return model_response
|
|
642
668
|
|
|
@@ -865,143 +891,158 @@ class Model(ABC):
|
|
|
865
891
|
"""
|
|
866
892
|
Generate a streaming response from the model.
|
|
867
893
|
"""
|
|
894
|
+
try:
|
|
895
|
+
# Check cache if enabled - capture key BEFORE streaming to avoid mismatch
|
|
896
|
+
cache_key = None
|
|
897
|
+
if self.cache_response:
|
|
898
|
+
cache_key = self._get_model_cache_key(
|
|
899
|
+
messages, stream=True, response_format=response_format, tools=tools
|
|
900
|
+
)
|
|
901
|
+
cached_data = self._get_cached_model_response(cache_key)
|
|
868
902
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
if cached_data:
|
|
876
|
-
log_info("Cache hit for streaming model response")
|
|
877
|
-
# Yield cached responses
|
|
878
|
-
for response in self._streaming_responses_from_cache(cached_data["streaming_responses"]):
|
|
879
|
-
yield response
|
|
880
|
-
return
|
|
903
|
+
if cached_data:
|
|
904
|
+
log_info("Cache hit for streaming model response")
|
|
905
|
+
# Yield cached responses
|
|
906
|
+
for response in self._streaming_responses_from_cache(cached_data["streaming_responses"]):
|
|
907
|
+
yield response
|
|
908
|
+
return
|
|
881
909
|
|
|
882
|
-
|
|
910
|
+
log_info("Cache miss for streaming model response")
|
|
883
911
|
|
|
884
|
-
|
|
885
|
-
|
|
912
|
+
# Track streaming responses for caching
|
|
913
|
+
streaming_responses: List[ModelResponse] = []
|
|
886
914
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
915
|
+
log_debug(f"{self.get_provider()} Response Stream Start", center=True, symbol="-")
|
|
916
|
+
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
917
|
+
_log_messages(messages)
|
|
890
918
|
|
|
891
|
-
|
|
892
|
-
|
|
919
|
+
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
920
|
+
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
893
921
|
|
|
894
|
-
|
|
922
|
+
function_call_count = 0
|
|
895
923
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
924
|
+
while True:
|
|
925
|
+
assistant_message = Message(role=self.assistant_message_role)
|
|
926
|
+
# Create assistant message and stream data
|
|
927
|
+
stream_data = MessageData()
|
|
928
|
+
model_response = ModelResponse()
|
|
929
|
+
if stream_model_response:
|
|
930
|
+
# Generate response
|
|
931
|
+
for response in self.process_response_stream(
|
|
932
|
+
messages=messages,
|
|
933
|
+
assistant_message=assistant_message,
|
|
934
|
+
stream_data=stream_data,
|
|
935
|
+
response_format=response_format,
|
|
936
|
+
tools=_tool_dicts,
|
|
937
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
938
|
+
run_response=run_response,
|
|
939
|
+
):
|
|
940
|
+
if self.cache_response and isinstance(response, ModelResponse):
|
|
941
|
+
streaming_responses.append(response)
|
|
942
|
+
yield response
|
|
915
943
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
)
|
|
925
|
-
if self.cache_response:
|
|
926
|
-
streaming_responses.append(model_response)
|
|
927
|
-
yield model_response
|
|
928
|
-
|
|
929
|
-
# Add assistant message to messages
|
|
930
|
-
messages.append(assistant_message)
|
|
931
|
-
assistant_message.log(metrics=True)
|
|
932
|
-
|
|
933
|
-
# Handle tool calls if present
|
|
934
|
-
if assistant_message.tool_calls is not None:
|
|
935
|
-
# Prepare function calls
|
|
936
|
-
function_calls_to_run: List[FunctionCall] = self.get_function_calls_to_run(
|
|
937
|
-
assistant_message=assistant_message, messages=messages, functions=_functions
|
|
938
|
-
)
|
|
939
|
-
function_call_results: List[Message] = []
|
|
940
|
-
|
|
941
|
-
# Execute function calls
|
|
942
|
-
for function_call_response in self.run_function_calls(
|
|
943
|
-
function_calls=function_calls_to_run,
|
|
944
|
-
function_call_results=function_call_results,
|
|
945
|
-
current_function_call_count=function_call_count,
|
|
946
|
-
function_call_limit=tool_call_limit,
|
|
947
|
-
):
|
|
948
|
-
if self.cache_response and isinstance(function_call_response, ModelResponse):
|
|
949
|
-
streaming_responses.append(function_call_response)
|
|
950
|
-
yield function_call_response
|
|
951
|
-
|
|
952
|
-
# Add a function call for each successful execution
|
|
953
|
-
function_call_count += len(function_call_results)
|
|
954
|
-
|
|
955
|
-
# Format and add results to messages
|
|
956
|
-
if stream_data and stream_data.extra is not None:
|
|
957
|
-
self.format_function_call_results(
|
|
958
|
-
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
944
|
+
else:
|
|
945
|
+
self._process_model_response(
|
|
946
|
+
messages=messages,
|
|
947
|
+
assistant_message=assistant_message,
|
|
948
|
+
model_response=model_response,
|
|
949
|
+
response_format=response_format,
|
|
950
|
+
tools=_tool_dicts,
|
|
951
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
959
952
|
)
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
953
|
+
if self.cache_response:
|
|
954
|
+
streaming_responses.append(model_response)
|
|
955
|
+
yield model_response
|
|
956
|
+
|
|
957
|
+
# Add assistant message to messages
|
|
958
|
+
messages.append(assistant_message)
|
|
959
|
+
assistant_message.log(metrics=True)
|
|
960
|
+
|
|
961
|
+
# Handle tool calls if present
|
|
962
|
+
if assistant_message.tool_calls is not None:
|
|
963
|
+
# Prepare function calls
|
|
964
|
+
function_calls_to_run: List[FunctionCall] = self.get_function_calls_to_run(
|
|
965
|
+
assistant_message=assistant_message, messages=messages, functions=_functions
|
|
963
966
|
)
|
|
964
|
-
|
|
965
|
-
self.format_function_call_results(messages=messages, function_call_results=function_call_results)
|
|
967
|
+
function_call_results: List[Message] = []
|
|
966
968
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
messages=messages,
|
|
969
|
+
# Execute function calls
|
|
970
|
+
for function_call_response in self.run_function_calls(
|
|
971
|
+
function_calls=function_calls_to_run,
|
|
971
972
|
function_call_results=function_call_results,
|
|
972
|
-
|
|
973
|
-
|
|
973
|
+
current_function_call_count=function_call_count,
|
|
974
|
+
function_call_limit=tool_call_limit,
|
|
975
|
+
):
|
|
976
|
+
if self.cache_response and isinstance(function_call_response, ModelResponse):
|
|
977
|
+
streaming_responses.append(function_call_response)
|
|
978
|
+
yield function_call_response
|
|
974
979
|
|
|
975
|
-
|
|
976
|
-
|
|
980
|
+
# Add a function call for each successful execution
|
|
981
|
+
function_call_count += len(function_call_results)
|
|
977
982
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
983
|
+
# Format and add results to messages
|
|
984
|
+
if stream_data and stream_data.extra is not None:
|
|
985
|
+
self.format_function_call_results(
|
|
986
|
+
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
987
|
+
)
|
|
988
|
+
elif model_response and model_response.extra is not None:
|
|
989
|
+
self.format_function_call_results(
|
|
990
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra
|
|
991
|
+
)
|
|
992
|
+
else:
|
|
993
|
+
self.format_function_call_results(
|
|
994
|
+
messages=messages, function_call_results=function_call_results
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
# Handle function call media
|
|
998
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
999
|
+
self._handle_function_call_media(
|
|
1000
|
+
messages=messages,
|
|
1001
|
+
function_call_results=function_call_results,
|
|
1002
|
+
send_media_to_model=send_media_to_model,
|
|
1003
|
+
)
|
|
981
1004
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
break
|
|
1005
|
+
for function_call_result in function_call_results:
|
|
1006
|
+
function_call_result.log(metrics=True)
|
|
985
1007
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1008
|
+
# Check if we should stop after tool calls
|
|
1009
|
+
if any(m.stop_after_tool_call for m in function_call_results):
|
|
1010
|
+
break
|
|
989
1011
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1012
|
+
# If we have any tool calls that require confirmation, break the loop
|
|
1013
|
+
if any(fc.function.requires_confirmation for fc in function_calls_to_run):
|
|
1014
|
+
break
|
|
993
1015
|
|
|
994
|
-
|
|
995
|
-
|
|
1016
|
+
# If we have any tool calls that require external execution, break the loop
|
|
1017
|
+
if any(fc.function.external_execution for fc in function_calls_to_run):
|
|
1018
|
+
break
|
|
1019
|
+
|
|
1020
|
+
# If we have any tool calls that require user input, break the loop
|
|
1021
|
+
if any(fc.function.requires_user_input for fc in function_calls_to_run):
|
|
1022
|
+
break
|
|
996
1023
|
|
|
997
|
-
|
|
998
|
-
|
|
1024
|
+
# Continue loop to get next response
|
|
1025
|
+
continue
|
|
999
1026
|
|
|
1000
|
-
|
|
1027
|
+
# No tool calls or finished processing them
|
|
1028
|
+
break
|
|
1001
1029
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1030
|
+
log_debug(f"{self.get_provider()} Response Stream End", center=True, symbol="-")
|
|
1031
|
+
|
|
1032
|
+
# Save streaming responses to cache if enabled
|
|
1033
|
+
if self.cache_response and cache_key and streaming_responses:
|
|
1034
|
+
self._save_streaming_responses_to_cache(cache_key, streaming_responses)
|
|
1035
|
+
finally:
|
|
1036
|
+
# Close the Gemini client
|
|
1037
|
+
if self.__class__.__name__ == "Gemini" and self.client is not None:
|
|
1038
|
+
try:
|
|
1039
|
+
self.client.close() # type: ignore
|
|
1040
|
+
self.client = None
|
|
1041
|
+
except AttributeError:
|
|
1042
|
+
log_warning(
|
|
1043
|
+
"Your Gemini client is outdated. For Agno to properly handle the lifecycle of the client,"
|
|
1044
|
+
" please upgrade Gemini to the latest version: pip install -U google-genai"
|
|
1045
|
+
)
|
|
1005
1046
|
|
|
1006
1047
|
async def aprocess_response_stream(
|
|
1007
1048
|
self,
|
|
@@ -1047,144 +1088,160 @@ class Model(ABC):
|
|
|
1047
1088
|
"""
|
|
1048
1089
|
Generate an asynchronous streaming response from the model.
|
|
1049
1090
|
"""
|
|
1091
|
+
try:
|
|
1092
|
+
# Check cache if enabled - capture key BEFORE streaming to avoid mismatch
|
|
1093
|
+
cache_key = None
|
|
1094
|
+
if self.cache_response:
|
|
1095
|
+
cache_key = self._get_model_cache_key(
|
|
1096
|
+
messages, stream=True, response_format=response_format, tools=tools
|
|
1097
|
+
)
|
|
1098
|
+
cached_data = self._get_cached_model_response(cache_key)
|
|
1050
1099
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1100
|
+
if cached_data:
|
|
1101
|
+
log_info("Cache hit for async streaming model response")
|
|
1102
|
+
# Yield cached responses
|
|
1103
|
+
for response in self._streaming_responses_from_cache(cached_data["streaming_responses"]):
|
|
1104
|
+
yield response
|
|
1105
|
+
return
|
|
1056
1106
|
|
|
1057
|
-
|
|
1058
|
-
log_info("Cache hit for async streaming model response")
|
|
1059
|
-
# Yield cached responses
|
|
1060
|
-
for response in self._streaming_responses_from_cache(cached_data["streaming_responses"]):
|
|
1061
|
-
yield response
|
|
1062
|
-
return
|
|
1107
|
+
log_info("Cache miss for async streaming model response")
|
|
1063
1108
|
|
|
1064
|
-
|
|
1109
|
+
# Track streaming responses for caching
|
|
1110
|
+
streaming_responses: List[ModelResponse] = []
|
|
1065
1111
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1112
|
+
log_debug(f"{self.get_provider()} Async Response Stream Start", center=True, symbol="-")
|
|
1113
|
+
log_debug(f"Model: {self.id}", center=True, symbol="-")
|
|
1114
|
+
_log_messages(messages)
|
|
1068
1115
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
_log_messages(messages)
|
|
1116
|
+
_tool_dicts = self._format_tools(tools) if tools is not None else []
|
|
1117
|
+
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
1072
1118
|
|
|
1073
|
-
|
|
1074
|
-
_functions = {tool.name: tool for tool in tools if isinstance(tool, Function)} if tools is not None else {}
|
|
1119
|
+
function_call_count = 0
|
|
1075
1120
|
|
|
1076
|
-
|
|
1121
|
+
while True:
|
|
1122
|
+
# Create assistant message and stream data
|
|
1123
|
+
assistant_message = Message(role=self.assistant_message_role)
|
|
1124
|
+
stream_data = MessageData()
|
|
1125
|
+
model_response = ModelResponse()
|
|
1126
|
+
if stream_model_response:
|
|
1127
|
+
# Generate response
|
|
1128
|
+
async for model_response in self.aprocess_response_stream(
|
|
1129
|
+
messages=messages,
|
|
1130
|
+
assistant_message=assistant_message,
|
|
1131
|
+
stream_data=stream_data,
|
|
1132
|
+
response_format=response_format,
|
|
1133
|
+
tools=_tool_dicts,
|
|
1134
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
1135
|
+
run_response=run_response,
|
|
1136
|
+
):
|
|
1137
|
+
if self.cache_response and isinstance(model_response, ModelResponse):
|
|
1138
|
+
streaming_responses.append(model_response)
|
|
1139
|
+
yield model_response
|
|
1077
1140
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
response_format=response_format,
|
|
1090
|
-
tools=_tool_dicts,
|
|
1091
|
-
tool_choice=tool_choice or self._tool_choice,
|
|
1092
|
-
run_response=run_response,
|
|
1093
|
-
):
|
|
1094
|
-
if self.cache_response and isinstance(model_response, ModelResponse):
|
|
1141
|
+
else:
|
|
1142
|
+
await self._aprocess_model_response(
|
|
1143
|
+
messages=messages,
|
|
1144
|
+
assistant_message=assistant_message,
|
|
1145
|
+
model_response=model_response,
|
|
1146
|
+
response_format=response_format,
|
|
1147
|
+
tools=_tool_dicts,
|
|
1148
|
+
tool_choice=tool_choice or self._tool_choice,
|
|
1149
|
+
run_response=run_response,
|
|
1150
|
+
)
|
|
1151
|
+
if self.cache_response:
|
|
1095
1152
|
streaming_responses.append(model_response)
|
|
1096
1153
|
yield model_response
|
|
1097
1154
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
)
|
|
1108
|
-
if self.cache_response:
|
|
1109
|
-
streaming_responses.append(model_response)
|
|
1110
|
-
yield model_response
|
|
1111
|
-
|
|
1112
|
-
# Add assistant message to messages
|
|
1113
|
-
messages.append(assistant_message)
|
|
1114
|
-
assistant_message.log(metrics=True)
|
|
1115
|
-
|
|
1116
|
-
# Handle tool calls if present
|
|
1117
|
-
if assistant_message.tool_calls is not None:
|
|
1118
|
-
# Prepare function calls
|
|
1119
|
-
function_calls_to_run: List[FunctionCall] = self.get_function_calls_to_run(
|
|
1120
|
-
assistant_message=assistant_message, messages=messages, functions=_functions
|
|
1121
|
-
)
|
|
1122
|
-
function_call_results: List[Message] = []
|
|
1123
|
-
|
|
1124
|
-
# Execute function calls
|
|
1125
|
-
async for function_call_response in self.arun_function_calls(
|
|
1126
|
-
function_calls=function_calls_to_run,
|
|
1127
|
-
function_call_results=function_call_results,
|
|
1128
|
-
current_function_call_count=function_call_count,
|
|
1129
|
-
function_call_limit=tool_call_limit,
|
|
1130
|
-
):
|
|
1131
|
-
if self.cache_response and isinstance(function_call_response, ModelResponse):
|
|
1132
|
-
streaming_responses.append(function_call_response)
|
|
1133
|
-
yield function_call_response
|
|
1134
|
-
|
|
1135
|
-
# Add a function call for each successful execution
|
|
1136
|
-
function_call_count += len(function_call_results)
|
|
1137
|
-
|
|
1138
|
-
# Format and add results to messages
|
|
1139
|
-
if stream_data and stream_data.extra is not None:
|
|
1140
|
-
self.format_function_call_results(
|
|
1141
|
-
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
1142
|
-
)
|
|
1143
|
-
elif model_response and model_response.extra is not None:
|
|
1144
|
-
self.format_function_call_results(
|
|
1145
|
-
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
1155
|
+
# Add assistant message to messages
|
|
1156
|
+
messages.append(assistant_message)
|
|
1157
|
+
assistant_message.log(metrics=True)
|
|
1158
|
+
|
|
1159
|
+
# Handle tool calls if present
|
|
1160
|
+
if assistant_message.tool_calls is not None:
|
|
1161
|
+
# Prepare function calls
|
|
1162
|
+
function_calls_to_run: List[FunctionCall] = self.get_function_calls_to_run(
|
|
1163
|
+
assistant_message=assistant_message, messages=messages, functions=_functions
|
|
1146
1164
|
)
|
|
1147
|
-
|
|
1148
|
-
self.format_function_call_results(messages=messages, function_call_results=function_call_results)
|
|
1165
|
+
function_call_results: List[Message] = []
|
|
1149
1166
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
messages=messages,
|
|
1167
|
+
# Execute function calls
|
|
1168
|
+
async for function_call_response in self.arun_function_calls(
|
|
1169
|
+
function_calls=function_calls_to_run,
|
|
1154
1170
|
function_call_results=function_call_results,
|
|
1155
|
-
|
|
1156
|
-
|
|
1171
|
+
current_function_call_count=function_call_count,
|
|
1172
|
+
function_call_limit=tool_call_limit,
|
|
1173
|
+
):
|
|
1174
|
+
if self.cache_response and isinstance(function_call_response, ModelResponse):
|
|
1175
|
+
streaming_responses.append(function_call_response)
|
|
1176
|
+
yield function_call_response
|
|
1157
1177
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1178
|
+
# Add a function call for each successful execution
|
|
1179
|
+
function_call_count += len(function_call_results)
|
|
1160
1180
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1181
|
+
# Format and add results to messages
|
|
1182
|
+
if stream_data and stream_data.extra is not None:
|
|
1183
|
+
self.format_function_call_results(
|
|
1184
|
+
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
1185
|
+
)
|
|
1186
|
+
elif model_response and model_response.extra is not None:
|
|
1187
|
+
self.format_function_call_results(
|
|
1188
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
1189
|
+
)
|
|
1190
|
+
else:
|
|
1191
|
+
self.format_function_call_results(
|
|
1192
|
+
messages=messages, function_call_results=function_call_results
|
|
1193
|
+
)
|
|
1164
1194
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1195
|
+
# Handle function call media
|
|
1196
|
+
if any(msg.images or msg.videos or msg.audio or msg.files for msg in function_call_results):
|
|
1197
|
+
self._handle_function_call_media(
|
|
1198
|
+
messages=messages,
|
|
1199
|
+
function_call_results=function_call_results,
|
|
1200
|
+
send_media_to_model=send_media_to_model,
|
|
1201
|
+
)
|
|
1168
1202
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
break
|
|
1203
|
+
for function_call_result in function_call_results:
|
|
1204
|
+
function_call_result.log(metrics=True)
|
|
1172
1205
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1206
|
+
# Check if we should stop after tool calls
|
|
1207
|
+
if any(m.stop_after_tool_call for m in function_call_results):
|
|
1208
|
+
break
|
|
1176
1209
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1210
|
+
# If we have any tool calls that require confirmation, break the loop
|
|
1211
|
+
if any(fc.function.requires_confirmation for fc in function_calls_to_run):
|
|
1212
|
+
break
|
|
1179
1213
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1214
|
+
# If we have any tool calls that require external execution, break the loop
|
|
1215
|
+
if any(fc.function.external_execution for fc in function_calls_to_run):
|
|
1216
|
+
break
|
|
1182
1217
|
|
|
1183
|
-
|
|
1218
|
+
# If we have any tool calls that require user input, break the loop
|
|
1219
|
+
if any(fc.function.requires_user_input for fc in function_calls_to_run):
|
|
1220
|
+
break
|
|
1184
1221
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1222
|
+
# Continue loop to get next response
|
|
1223
|
+
continue
|
|
1224
|
+
|
|
1225
|
+
# No tool calls or finished processing them
|
|
1226
|
+
break
|
|
1227
|
+
|
|
1228
|
+
log_debug(f"{self.get_provider()} Async Response Stream End", center=True, symbol="-")
|
|
1229
|
+
|
|
1230
|
+
# Save streaming responses to cache if enabled
|
|
1231
|
+
if self.cache_response and cache_key and streaming_responses:
|
|
1232
|
+
self._save_streaming_responses_to_cache(cache_key, streaming_responses)
|
|
1233
|
+
|
|
1234
|
+
finally:
|
|
1235
|
+
# Close the Gemini client
|
|
1236
|
+
if self.__class__.__name__ == "Gemini" and self.client is not None:
|
|
1237
|
+
try:
|
|
1238
|
+
await self.client.aio.aclose() # type: ignore
|
|
1239
|
+
self.client = None
|
|
1240
|
+
except AttributeError:
|
|
1241
|
+
log_warning(
|
|
1242
|
+
"Your Gemini client is outdated. For Agno to properly handle the lifecycle of the client,"
|
|
1243
|
+
" please upgrade Gemini to the latest version: pip install -U google-genai"
|
|
1244
|
+
)
|
|
1188
1245
|
|
|
1189
1246
|
def _populate_assistant_message_from_stream_data(
|
|
1190
1247
|
self, assistant_message: Message, stream_data: MessageData
|
|
@@ -1452,44 +1509,49 @@ class Model(ABC):
|
|
|
1452
1509
|
function_call_output: str = ""
|
|
1453
1510
|
|
|
1454
1511
|
if isinstance(function_execution_result.result, (GeneratorType, collections.abc.Iterator)):
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
if item
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1512
|
+
try:
|
|
1513
|
+
for item in function_execution_result.result:
|
|
1514
|
+
# This function yields agent/team/workflow run events
|
|
1515
|
+
if (
|
|
1516
|
+
isinstance(item, tuple(get_args(RunOutputEvent)))
|
|
1517
|
+
or isinstance(item, tuple(get_args(TeamRunOutputEvent)))
|
|
1518
|
+
or isinstance(item, tuple(get_args(WorkflowRunOutputEvent)))
|
|
1519
|
+
):
|
|
1520
|
+
# We only capture content events for output accumulation
|
|
1521
|
+
if isinstance(item, RunContentEvent) or isinstance(item, TeamRunContentEvent):
|
|
1522
|
+
if item.content is not None and isinstance(item.content, BaseModel):
|
|
1523
|
+
function_call_output += item.content.model_dump_json()
|
|
1524
|
+
else:
|
|
1525
|
+
# Capture output
|
|
1526
|
+
function_call_output += item.content or ""
|
|
1469
1527
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1528
|
+
if function_call.function.show_result and item.content is not None:
|
|
1529
|
+
yield ModelResponse(content=item.content)
|
|
1472
1530
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1531
|
+
if isinstance(item, CustomEvent):
|
|
1532
|
+
function_call_output += str(item)
|
|
1475
1533
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1534
|
+
# For WorkflowCompletedEvent, extract content for final output
|
|
1535
|
+
from agno.run.workflow import WorkflowCompletedEvent
|
|
1478
1536
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1537
|
+
if isinstance(item, WorkflowCompletedEvent):
|
|
1538
|
+
if item.content is not None:
|
|
1539
|
+
if isinstance(item.content, BaseModel):
|
|
1540
|
+
function_call_output += item.content.model_dump_json()
|
|
1541
|
+
else:
|
|
1542
|
+
function_call_output += str(item.content)
|
|
1485
1543
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1544
|
+
# Yield the event itself to bubble it up
|
|
1545
|
+
yield item
|
|
1488
1546
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1547
|
+
else:
|
|
1548
|
+
function_call_output += str(item)
|
|
1549
|
+
if function_call.function.show_result and item is not None:
|
|
1550
|
+
yield ModelResponse(content=str(item))
|
|
1551
|
+
except Exception as e:
|
|
1552
|
+
log_error(f"Error while iterating function result generator for {function_call.function.name}: {e}")
|
|
1553
|
+
function_call.error = str(e)
|
|
1554
|
+
function_call_success = False
|
|
1493
1555
|
else:
|
|
1494
1556
|
from agno.tools.function import ToolResult
|
|
1495
1557
|
|
|
@@ -1975,32 +2037,37 @@ class Model(ABC):
|
|
|
1975
2037
|
function_call_output = async_function_call_output
|
|
1976
2038
|
# Events from async generators were already yielded in real-time above
|
|
1977
2039
|
elif isinstance(function_call.result, (GeneratorType, collections.abc.Iterator)):
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
if item
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2040
|
+
try:
|
|
2041
|
+
for item in function_call.result:
|
|
2042
|
+
# This function yields agent/team/workflow run events
|
|
2043
|
+
if isinstance(
|
|
2044
|
+
item,
|
|
2045
|
+
tuple(get_args(RunOutputEvent))
|
|
2046
|
+
+ tuple(get_args(TeamRunOutputEvent))
|
|
2047
|
+
+ tuple(get_args(WorkflowRunOutputEvent)),
|
|
2048
|
+
):
|
|
2049
|
+
# We only capture content events
|
|
2050
|
+
if isinstance(item, RunContentEvent) or isinstance(item, TeamRunContentEvent):
|
|
2051
|
+
if item.content is not None and isinstance(item.content, BaseModel):
|
|
2052
|
+
function_call_output += item.content.model_dump_json()
|
|
2053
|
+
else:
|
|
2054
|
+
# Capture output
|
|
2055
|
+
function_call_output += item.content or ""
|
|
2056
|
+
|
|
2057
|
+
if function_call.function.show_result and item.content is not None:
|
|
2058
|
+
yield ModelResponse(content=item.content)
|
|
2059
|
+
continue
|
|
2060
|
+
|
|
2061
|
+
# Yield the event itself to bubble it up
|
|
2062
|
+
yield item
|
|
2063
|
+
else:
|
|
2064
|
+
function_call_output += str(item)
|
|
2065
|
+
if function_call.function.show_result and item is not None:
|
|
2066
|
+
yield ModelResponse(content=str(item))
|
|
2067
|
+
except Exception as e:
|
|
2068
|
+
log_error(f"Error while iterating function result generator for {function_call.function.name}: {e}")
|
|
2069
|
+
function_call.error = str(e)
|
|
2070
|
+
function_call_success = False
|
|
2004
2071
|
else:
|
|
2005
2072
|
from agno.tools.function import ToolResult
|
|
2006
2073
|
|