agno 2.0.0rc1__py3-none-any.whl → 2.0.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 +101 -140
- agno/db/mongo/mongo.py +8 -3
- agno/eval/accuracy.py +12 -5
- agno/knowledge/chunking/strategy.py +14 -14
- agno/knowledge/knowledge.py +156 -120
- agno/knowledge/reader/arxiv_reader.py +5 -5
- agno/knowledge/reader/csv_reader.py +6 -77
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/firecrawl_reader.py +5 -5
- agno/knowledge/reader/json_reader.py +5 -5
- agno/knowledge/reader/markdown_reader.py +31 -9
- agno/knowledge/reader/pdf_reader.py +10 -123
- agno/knowledge/reader/reader_factory.py +65 -72
- agno/knowledge/reader/s3_reader.py +44 -114
- agno/knowledge/reader/text_reader.py +5 -5
- agno/knowledge/reader/url_reader.py +75 -31
- agno/knowledge/reader/web_search_reader.py +6 -29
- agno/knowledge/reader/website_reader.py +5 -5
- agno/knowledge/reader/wikipedia_reader.py +5 -5
- agno/knowledge/reader/youtube_reader.py +6 -6
- agno/knowledge/reranker/__init__.py +9 -0
- agno/knowledge/utils.py +10 -10
- agno/media.py +269 -268
- agno/models/aws/bedrock.py +3 -7
- agno/models/base.py +50 -54
- agno/models/google/gemini.py +11 -10
- agno/models/message.py +4 -4
- agno/models/ollama/chat.py +1 -1
- agno/models/openai/chat.py +33 -14
- agno/models/response.py +5 -5
- agno/os/app.py +40 -29
- agno/os/mcp.py +39 -59
- agno/os/router.py +547 -16
- agno/os/routers/evals/evals.py +197 -12
- agno/os/routers/knowledge/knowledge.py +428 -14
- agno/os/routers/memory/memory.py +250 -28
- agno/os/routers/metrics/metrics.py +125 -7
- agno/os/routers/session/session.py +393 -25
- agno/os/schema.py +55 -2
- agno/run/agent.py +37 -28
- agno/run/base.py +9 -19
- agno/run/team.py +110 -19
- agno/run/workflow.py +41 -28
- agno/team/team.py +808 -1080
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +7 -4
- agno/tools/desi_vocal.py +2 -2
- agno/tools/e2b.py +6 -6
- agno/tools/eleven_labs.py +3 -3
- agno/tools/fal.py +4 -4
- agno/tools/function.py +7 -7
- agno/tools/giphy.py +2 -2
- agno/tools/lumalab.py +3 -3
- agno/tools/mcp.py +1 -2
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +2 -2
- agno/tools/models_labs.py +5 -5
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/replicate.py +7 -7
- agno/utils/events.py +5 -5
- agno/utils/gemini.py +1 -1
- agno/utils/log.py +52 -2
- agno/utils/mcp.py +57 -5
- agno/utils/models/aws_claude.py +1 -1
- agno/utils/models/claude.py +0 -8
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/team.py +177 -73
- agno/utils/streamlit.py +27 -0
- agno/vectordb/lancedb/lance_db.py +82 -25
- agno/workflow/step.py +7 -7
- agno/workflow/types.py +13 -13
- agno/workflow/workflow.py +37 -28
- {agno-2.0.0rc1.dist-info → agno-2.0.1.dist-info}/METADATA +140 -1
- {agno-2.0.0rc1.dist-info → agno-2.0.1.dist-info}/RECORD +83 -84
- agno-2.0.1.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/gcs_reader.py +0 -67
- agno-2.0.0rc1.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc1.dist-info → agno-2.0.1.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc1.dist-info → agno-2.0.1.dist-info}/top_level.txt +0 -0
agno/utils/log.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from functools import lru_cache
|
|
2
3
|
from os import getenv
|
|
3
4
|
from typing import Any, Literal, Optional
|
|
4
5
|
|
|
@@ -64,6 +65,11 @@ class AgnoLogger(logging.Logger):
|
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def build_logger(logger_name: str, source_type: Optional[str] = None) -> Any:
|
|
68
|
+
# If a logger with the name "agno.{source_type}" is already set, we want to use that one
|
|
69
|
+
_logger = logging.getLogger(f"agno.{logger_name}")
|
|
70
|
+
if _logger.handlers or _logger.level != logging.NOTSET:
|
|
71
|
+
return _logger
|
|
72
|
+
|
|
67
73
|
# Set the custom logger class as the default for this logger
|
|
68
74
|
logging.setLoggerClass(AgnoLogger)
|
|
69
75
|
|
|
@@ -174,6 +180,12 @@ def use_workflow_logger():
|
|
|
174
180
|
logger = workflow_logger
|
|
175
181
|
|
|
176
182
|
|
|
183
|
+
@lru_cache(maxsize=128)
|
|
184
|
+
def _using_default_logger(logger_instance: Any) -> bool:
|
|
185
|
+
"""Return True if the currently active logger is our default AgnoLogger"""
|
|
186
|
+
return isinstance(logger_instance, AgnoLogger)
|
|
187
|
+
|
|
188
|
+
|
|
177
189
|
def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1, 2] = 1, *args, **kwargs):
|
|
178
190
|
global logger
|
|
179
191
|
global debug_on
|
|
@@ -181,12 +193,18 @@ def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1
|
|
|
181
193
|
|
|
182
194
|
if debug_on:
|
|
183
195
|
if debug_level >= log_level:
|
|
184
|
-
logger
|
|
196
|
+
if _using_default_logger(logger):
|
|
197
|
+
logger.debug(msg, center, symbol, *args, **kwargs)
|
|
198
|
+
else:
|
|
199
|
+
logger.debug(msg, *args, **kwargs)
|
|
185
200
|
|
|
186
201
|
|
|
187
202
|
def log_info(msg, center: bool = False, symbol: str = "*", *args, **kwargs):
|
|
188
203
|
global logger
|
|
189
|
-
logger
|
|
204
|
+
if _using_default_logger(logger):
|
|
205
|
+
logger.info(msg, center, symbol, *args, **kwargs)
|
|
206
|
+
else:
|
|
207
|
+
logger.info(msg, *args, **kwargs)
|
|
190
208
|
|
|
191
209
|
|
|
192
210
|
def log_warning(msg, *args, **kwargs):
|
|
@@ -202,3 +220,35 @@ def log_error(msg, *args, **kwargs):
|
|
|
202
220
|
def log_exception(msg, *args, **kwargs):
|
|
203
221
|
global logger
|
|
204
222
|
logger.exception(msg, *args, **kwargs)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def configure_agno_logging(
|
|
226
|
+
custom_default_logger: Optional[Any] = None,
|
|
227
|
+
custom_agent_logger: Optional[Any] = None,
|
|
228
|
+
custom_team_logger: Optional[Any] = None,
|
|
229
|
+
custom_workflow_logger: Optional[Any] = None,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""
|
|
232
|
+
Util to set custom loggers. These will be used everywhere across the Agno library.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
custom_default_logger: Default logger to use (overrides agent_logger for default)
|
|
236
|
+
custom_agent_logger: Custom logger for agent operations
|
|
237
|
+
custom_team_logger: Custom logger for team operations
|
|
238
|
+
custom_workflow_logger: Custom logger for workflow operations
|
|
239
|
+
"""
|
|
240
|
+
if custom_default_logger is not None:
|
|
241
|
+
global logger
|
|
242
|
+
logger = custom_default_logger
|
|
243
|
+
|
|
244
|
+
if custom_agent_logger is not None:
|
|
245
|
+
global agent_logger
|
|
246
|
+
agent_logger = custom_agent_logger
|
|
247
|
+
|
|
248
|
+
if custom_team_logger is not None:
|
|
249
|
+
global team_logger
|
|
250
|
+
team_logger = custom_team_logger
|
|
251
|
+
|
|
252
|
+
if custom_workflow_logger is not None:
|
|
253
|
+
global workflow_logger
|
|
254
|
+
workflow_logger = custom_workflow_logger
|
agno/utils/mcp.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from functools import partial
|
|
2
3
|
from uuid import uuid4
|
|
3
4
|
|
|
@@ -11,7 +12,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
11
12
|
raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
from agno.media import
|
|
15
|
+
from agno.media import Image
|
|
15
16
|
from agno.tools.function import ToolResult
|
|
16
17
|
|
|
17
18
|
|
|
@@ -43,13 +44,64 @@ def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
|
|
|
43
44
|
|
|
44
45
|
for content_item in result.content:
|
|
45
46
|
if isinstance(content_item, TextContent):
|
|
46
|
-
|
|
47
|
+
text_content = content_item.text
|
|
48
|
+
|
|
49
|
+
# Parse as JSON to check for custom image format
|
|
50
|
+
try:
|
|
51
|
+
parsed_json = json.loads(text_content)
|
|
52
|
+
if (
|
|
53
|
+
isinstance(parsed_json, dict)
|
|
54
|
+
and parsed_json.get("type") == "image"
|
|
55
|
+
and "data" in parsed_json
|
|
56
|
+
):
|
|
57
|
+
log_debug("Found custom JSON image format in TextContent")
|
|
58
|
+
|
|
59
|
+
# Extract image data
|
|
60
|
+
image_data = parsed_json.get("data")
|
|
61
|
+
mime_type = parsed_json.get("mimeType", "image/png")
|
|
62
|
+
|
|
63
|
+
if image_data and isinstance(image_data, str):
|
|
64
|
+
import base64
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
image_bytes = base64.b64decode(image_data)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
70
|
+
image_bytes = None
|
|
71
|
+
|
|
72
|
+
if image_bytes:
|
|
73
|
+
img_artifact = Image(
|
|
74
|
+
id=str(uuid4()),
|
|
75
|
+
url=None,
|
|
76
|
+
content=image_bytes,
|
|
77
|
+
mime_type=mime_type,
|
|
78
|
+
)
|
|
79
|
+
images.append(img_artifact)
|
|
80
|
+
response_str += "Image has been generated and added to the response.\n"
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
except (json.JSONDecodeError, TypeError):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
response_str += text_content + "\n"
|
|
87
|
+
|
|
47
88
|
elif isinstance(content_item, ImageContent):
|
|
48
|
-
# Handle
|
|
49
|
-
|
|
89
|
+
# Handle standard MCP ImageContent
|
|
90
|
+
image_data = getattr(content_item, "data", None)
|
|
91
|
+
|
|
92
|
+
if image_data and isinstance(image_data, str):
|
|
93
|
+
import base64
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
image_data = base64.b64decode(image_data)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
log_debug(f"Failed to decode base64 image data: {e}")
|
|
99
|
+
image_data = None
|
|
100
|
+
|
|
101
|
+
img_artifact = Image(
|
|
50
102
|
id=str(uuid4()),
|
|
51
103
|
url=getattr(content_item, "url", None),
|
|
52
|
-
content=
|
|
104
|
+
content=image_data,
|
|
53
105
|
mime_type=getattr(content_item, "mimeType", "image/png"),
|
|
54
106
|
)
|
|
55
107
|
images.append(img_artifact)
|
agno/utils/models/aws_claude.py
CHANGED
|
@@ -53,7 +53,7 @@ def _format_image_for_message(image: Image) -> Optional[Dict[str, Any]]:
|
|
|
53
53
|
try:
|
|
54
54
|
# Case 1: Image is a URL
|
|
55
55
|
if image.url is not None:
|
|
56
|
-
content_bytes = image.
|
|
56
|
+
content_bytes = image.get_content_bytes() # type: ignore
|
|
57
57
|
|
|
58
58
|
# Case 2: Image is a local file path
|
|
59
59
|
elif image.filepath is not None:
|
agno/utils/models/claude.py
CHANGED
|
@@ -307,14 +307,6 @@ def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Opti
|
|
|
307
307
|
required: List[str] = parameters.get("required", [])
|
|
308
308
|
required_params: List[str] = required
|
|
309
309
|
|
|
310
|
-
if not required_params:
|
|
311
|
-
for param_name, param_info in properties.items():
|
|
312
|
-
param_type = param_info.get("type", "")
|
|
313
|
-
param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
|
|
314
|
-
|
|
315
|
-
if "null" not in param_type_list:
|
|
316
|
-
required_params.append(param_name)
|
|
317
|
-
|
|
318
310
|
input_properties: Dict[str, Any] = {}
|
|
319
311
|
for param_name, param_info in properties.items():
|
|
320
312
|
# Preserve the complete schema structure for complex types
|
agno/utils/models/cohere.py
CHANGED
|
@@ -20,7 +20,7 @@ def _format_images_for_message(message: Message, images: Sequence[Image]) -> Lis
|
|
|
20
20
|
if image.content is not None:
|
|
21
21
|
image_content = image.content
|
|
22
22
|
elif image.url is not None:
|
|
23
|
-
image_content = image.
|
|
23
|
+
image_content = image.get_content_bytes() # type: ignore
|
|
24
24
|
elif image.filepath is not None:
|
|
25
25
|
if isinstance(image.filepath, Path):
|
|
26
26
|
image_content = image.filepath.read_bytes()
|
agno/utils/models/watsonx.py
CHANGED
|
@@ -19,7 +19,7 @@ def format_images_for_message(message: Message, images: Sequence[Image]) -> Mess
|
|
|
19
19
|
if image.content is not None:
|
|
20
20
|
image_content = image.content
|
|
21
21
|
elif image.url is not None:
|
|
22
|
-
image_content = image.
|
|
22
|
+
image_content = image.get_content_bytes() # type: ignore
|
|
23
23
|
else:
|
|
24
24
|
log_warning(f"Unsupported image format: {image}")
|
|
25
25
|
continue
|
agno/utils/openai.py
CHANGED
|
@@ -39,7 +39,7 @@ def audio_to_message(audio: Sequence[Audio]) -> List[Dict[str, Any]]:
|
|
|
39
39
|
|
|
40
40
|
# The audio is a URL
|
|
41
41
|
elif audio_snippet.url:
|
|
42
|
-
audio_bytes = audio_snippet.
|
|
42
|
+
audio_bytes = audio_snippet.get_content_bytes()
|
|
43
43
|
if audio_bytes is not None:
|
|
44
44
|
encoded_string = base64.b64encode(audio_bytes).decode("utf-8")
|
|
45
45
|
if not audio_format:
|
|
@@ -6,7 +6,7 @@ from agno.media import Audio, File, Image, Video
|
|
|
6
6
|
from agno.models.message import Message
|
|
7
7
|
from agno.models.response import ToolExecution
|
|
8
8
|
from agno.reasoning.step import ReasoningStep
|
|
9
|
-
from agno.run.agent import
|
|
9
|
+
from agno.run.agent import RunOutput
|
|
10
10
|
from agno.run.team import TeamRunEvent, TeamRunOutput, TeamRunOutputEvent
|
|
11
11
|
from agno.utils.log import log_warning
|
|
12
12
|
from agno.utils.message import get_text_from_message
|
|
@@ -184,7 +184,7 @@ def print_response(
|
|
|
184
184
|
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
185
185
|
show_markdown = member_markdown.get(member_response.team_id, False)
|
|
186
186
|
|
|
187
|
-
member_response_content: Union[str, JSON, Markdown] =
|
|
187
|
+
member_response_content: Union[str, JSON, Markdown] = _parse_response_content( # type: ignore
|
|
188
188
|
member_response,
|
|
189
189
|
tags_to_include_in_markdown,
|
|
190
190
|
show_markdown=show_markdown,
|
|
@@ -247,7 +247,7 @@ def print_response(
|
|
|
247
247
|
panels.append(team_tool_calls_panel)
|
|
248
248
|
live_console.update(Group(*panels))
|
|
249
249
|
|
|
250
|
-
response_content_batch: Union[str, JSON, Markdown] =
|
|
250
|
+
response_content_batch: Union[str, JSON, Markdown] = _parse_response_content( # type: ignore
|
|
251
251
|
run_response, tags_to_include_in_markdown, show_markdown=team_markdown
|
|
252
252
|
)
|
|
253
253
|
|
|
@@ -388,6 +388,7 @@ def print_response_stream(
|
|
|
388
388
|
dependencies=dependencies,
|
|
389
389
|
metadata=metadata,
|
|
390
390
|
debug_mode=debug_mode,
|
|
391
|
+
yield_run_response=True,
|
|
391
392
|
**kwargs,
|
|
392
393
|
)
|
|
393
394
|
|
|
@@ -397,9 +398,8 @@ def print_response_stream(
|
|
|
397
398
|
# Dict to track member response panels by member_id
|
|
398
399
|
member_response_panels = {}
|
|
399
400
|
|
|
400
|
-
|
|
401
|
+
final_run_response = None
|
|
401
402
|
for resp in stream_resp:
|
|
402
|
-
run_id = resp.run_id
|
|
403
403
|
if team_markdown is None:
|
|
404
404
|
if markdown:
|
|
405
405
|
team_markdown = True
|
|
@@ -409,6 +409,10 @@ def print_response_stream(
|
|
|
409
409
|
if team.output_schema is not None:
|
|
410
410
|
team_markdown = False
|
|
411
411
|
|
|
412
|
+
if isinstance(resp, TeamRunOutput):
|
|
413
|
+
final_run_response = resp
|
|
414
|
+
continue
|
|
415
|
+
|
|
412
416
|
if isinstance(resp, tuple(get_args(TeamRunOutputEvent))):
|
|
413
417
|
if resp.event == TeamRunEvent.run_content:
|
|
414
418
|
if isinstance(resp.content, str):
|
|
@@ -424,8 +428,8 @@ def print_response_stream(
|
|
|
424
428
|
reasoning_steps = resp.reasoning_steps # type: ignore
|
|
425
429
|
|
|
426
430
|
# Collect team tool calls, avoiding duplicates
|
|
427
|
-
if
|
|
428
|
-
tool = resp.tool
|
|
431
|
+
if resp.event == TeamRunEvent.tool_call_completed and resp.tool: # type: ignore
|
|
432
|
+
tool = resp.tool # type: ignore
|
|
429
433
|
# Generate a unique ID for this tool call
|
|
430
434
|
if tool.tool_call_id:
|
|
431
435
|
tool_id = tool.tool_call_id
|
|
@@ -537,7 +541,7 @@ def print_response_stream(
|
|
|
537
541
|
if markdown:
|
|
538
542
|
show_markdown = True
|
|
539
543
|
|
|
540
|
-
member_response_content =
|
|
544
|
+
member_response_content = _parse_response_content(
|
|
541
545
|
member_response,
|
|
542
546
|
tags_to_include_in_markdown,
|
|
543
547
|
show_markdown=show_markdown,
|
|
@@ -597,7 +601,7 @@ def print_response_stream(
|
|
|
597
601
|
live_console.update(Group(*panels))
|
|
598
602
|
|
|
599
603
|
response_timer.stop()
|
|
600
|
-
run_response =
|
|
604
|
+
run_response = final_run_response
|
|
601
605
|
|
|
602
606
|
# Add citations
|
|
603
607
|
if hasattr(resp, "citations") and resp.citations is not None and resp.citations.urls is not None:
|
|
@@ -725,7 +729,7 @@ def print_response_stream(
|
|
|
725
729
|
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
726
730
|
show_markdown = member_markdown.get(member_response.team_id, False)
|
|
727
731
|
|
|
728
|
-
member_response_content =
|
|
732
|
+
member_response_content = _parse_response_content( # type: ignore
|
|
729
733
|
member_response,
|
|
730
734
|
tags_to_include_in_markdown,
|
|
731
735
|
show_markdown=show_markdown,
|
|
@@ -985,7 +989,7 @@ async def aprint_response(
|
|
|
985
989
|
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
986
990
|
show_markdown = member_markdown.get(member_response.team_id, False)
|
|
987
991
|
|
|
988
|
-
member_response_content: Union[str, JSON, Markdown] =
|
|
992
|
+
member_response_content: Union[str, JSON, Markdown] = _parse_response_content( # type: ignore
|
|
989
993
|
member_response,
|
|
990
994
|
tags_to_include_in_markdown,
|
|
991
995
|
show_markdown=show_markdown,
|
|
@@ -1046,7 +1050,7 @@ async def aprint_response(
|
|
|
1046
1050
|
panels.append(team_tool_calls_panel)
|
|
1047
1051
|
live_console.update(Group(*panels))
|
|
1048
1052
|
|
|
1049
|
-
response_content_batch: Union[str, JSON, Markdown] =
|
|
1053
|
+
response_content_batch: Union[str, JSON, Markdown] = _parse_response_content( # type: ignore
|
|
1050
1054
|
run_response, tags_to_include_in_markdown, show_markdown=team_markdown
|
|
1051
1055
|
)
|
|
1052
1056
|
|
|
@@ -1175,7 +1179,10 @@ async def aprint_response_stream(
|
|
|
1175
1179
|
team_markdown = None
|
|
1176
1180
|
member_markdown = {}
|
|
1177
1181
|
|
|
1178
|
-
|
|
1182
|
+
# Dict to track member response panels by member_id
|
|
1183
|
+
member_response_panels = {}
|
|
1184
|
+
|
|
1185
|
+
final_run_response = None
|
|
1179
1186
|
async for resp in team.arun( # type: ignore
|
|
1180
1187
|
input=input,
|
|
1181
1188
|
audio=audio,
|
|
@@ -1192,9 +1199,9 @@ async def aprint_response_stream(
|
|
|
1192
1199
|
dependencies=dependencies,
|
|
1193
1200
|
metadata=metadata,
|
|
1194
1201
|
debug_mode=debug_mode,
|
|
1202
|
+
yield_run_response=True,
|
|
1195
1203
|
**kwargs,
|
|
1196
1204
|
):
|
|
1197
|
-
run_id = resp.run_id
|
|
1198
1205
|
if team_markdown is None:
|
|
1199
1206
|
if markdown:
|
|
1200
1207
|
team_markdown = True
|
|
@@ -1204,6 +1211,10 @@ async def aprint_response_stream(
|
|
|
1204
1211
|
if team.output_schema is not None:
|
|
1205
1212
|
team_markdown = False
|
|
1206
1213
|
|
|
1214
|
+
if isinstance(resp, TeamRunOutput):
|
|
1215
|
+
final_run_response = resp
|
|
1216
|
+
continue
|
|
1217
|
+
|
|
1207
1218
|
if isinstance(resp, tuple(get_args(TeamRunOutputEvent))):
|
|
1208
1219
|
if resp.event == TeamRunEvent.run_content:
|
|
1209
1220
|
if isinstance(resp.content, str):
|
|
@@ -1219,8 +1230,8 @@ async def aprint_response_stream(
|
|
|
1219
1230
|
reasoning_steps = resp.reasoning_steps # type: ignore
|
|
1220
1231
|
|
|
1221
1232
|
# Collect team tool calls, avoiding duplicates
|
|
1222
|
-
if
|
|
1223
|
-
tool = resp.tool
|
|
1233
|
+
if resp.event == TeamRunEvent.tool_call_completed and resp.tool: # type: ignore
|
|
1234
|
+
tool = resp.tool # type: ignore
|
|
1224
1235
|
# Generate a unique ID for this tool call
|
|
1225
1236
|
if tool.tool_call_id is not None:
|
|
1226
1237
|
tool_id = tool.tool_call_id
|
|
@@ -1259,7 +1270,7 @@ async def aprint_response_stream(
|
|
|
1259
1270
|
response_content_stream = Markdown(escaped_content)
|
|
1260
1271
|
|
|
1261
1272
|
# Create new panels for each chunk
|
|
1262
|
-
panels = [
|
|
1273
|
+
panels = []
|
|
1263
1274
|
|
|
1264
1275
|
if input and show_message:
|
|
1265
1276
|
render = True
|
|
@@ -1271,8 +1282,6 @@ async def aprint_response_stream(
|
|
|
1271
1282
|
border_style="cyan",
|
|
1272
1283
|
)
|
|
1273
1284
|
panels.append(message_panel)
|
|
1274
|
-
if render:
|
|
1275
|
-
live_console.update(Group(*panels))
|
|
1276
1285
|
|
|
1277
1286
|
if len(reasoning_steps) > 0 and show_reasoning:
|
|
1278
1287
|
render = True
|
|
@@ -1280,8 +1289,6 @@ async def aprint_response_stream(
|
|
|
1280
1289
|
for i, step in enumerate(reasoning_steps, 1):
|
|
1281
1290
|
reasoning_panel = build_reasoning_step_panel(i, step, show_full_reasoning)
|
|
1282
1291
|
panels.append(reasoning_panel)
|
|
1283
|
-
if render:
|
|
1284
|
-
live_console.update(Group(*panels))
|
|
1285
1292
|
|
|
1286
1293
|
if len(_response_reasoning_content) > 0:
|
|
1287
1294
|
render = True
|
|
@@ -1292,29 +1299,95 @@ async def aprint_response_stream(
|
|
|
1292
1299
|
border_style="green",
|
|
1293
1300
|
)
|
|
1294
1301
|
panels.append(thinking_panel)
|
|
1295
|
-
|
|
1296
|
-
|
|
1302
|
+
elif _response_content == "":
|
|
1303
|
+
# Keep showing status if no content yet
|
|
1304
|
+
panels.append(status)
|
|
1297
1305
|
|
|
1298
|
-
#
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
and
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1306
|
+
# Process member responses and their tool calls
|
|
1307
|
+
for member_response in resp.member_responses if hasattr(resp, "member_responses") else []:
|
|
1308
|
+
member_id = None
|
|
1309
|
+
member_name = "Team Member"
|
|
1310
|
+
if isinstance(member_response, RunOutput) and member_response.agent_id is not None:
|
|
1311
|
+
member_id = member_response.agent_id
|
|
1312
|
+
member_name = team._get_member_name(member_id)
|
|
1313
|
+
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
1314
|
+
member_id = member_response.team_id
|
|
1315
|
+
|
|
1316
|
+
member_name = team._get_member_name(member_id)
|
|
1317
|
+
|
|
1318
|
+
# If we have tool calls for this member, display them
|
|
1319
|
+
if member_id in member_tool_calls and member_tool_calls[member_id]:
|
|
1320
|
+
formatted_calls = format_tool_calls(member_tool_calls[member_id])
|
|
1321
|
+
if formatted_calls:
|
|
1322
|
+
console_width = console.width if console else 80
|
|
1323
|
+
panel_width = console_width + 30
|
|
1324
|
+
|
|
1325
|
+
lines = []
|
|
1326
|
+
for call in formatted_calls:
|
|
1327
|
+
wrapped_call = textwrap.fill(f"• {call}", width=panel_width, subsequent_indent=" ")
|
|
1328
|
+
lines.append(wrapped_call)
|
|
1329
|
+
|
|
1330
|
+
tool_calls_text = "\n\n".join(lines)
|
|
1331
|
+
|
|
1332
|
+
member_tool_calls_panel = create_panel(
|
|
1333
|
+
content=tool_calls_text,
|
|
1334
|
+
title=f"{member_name} Tool Calls",
|
|
1335
|
+
border_style="yellow",
|
|
1336
|
+
)
|
|
1337
|
+
panels.append(member_tool_calls_panel)
|
|
1338
|
+
|
|
1339
|
+
# Process member response content
|
|
1340
|
+
if team.show_members_responses and member_id is not None:
|
|
1341
|
+
show_markdown = False
|
|
1342
|
+
if markdown:
|
|
1343
|
+
show_markdown = True
|
|
1344
|
+
|
|
1345
|
+
member_response_content = _parse_response_content(
|
|
1346
|
+
member_response,
|
|
1347
|
+
tags_to_include_in_markdown,
|
|
1348
|
+
show_markdown=show_markdown,
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
member_response_panel = create_panel(
|
|
1352
|
+
content=member_response_content,
|
|
1353
|
+
title=f"{member_name} Response",
|
|
1354
|
+
border_style="magenta",
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
panels.append(member_response_panel)
|
|
1358
|
+
|
|
1359
|
+
# Store for reference
|
|
1360
|
+
if member_id is not None:
|
|
1361
|
+
member_response_panels[member_id] = member_response_panel
|
|
1362
|
+
|
|
1363
|
+
# Add team tool calls panel if available (before the team response)
|
|
1364
|
+
if team_tool_calls:
|
|
1365
|
+
formatted_calls = format_tool_calls(team_tool_calls)
|
|
1366
|
+
if formatted_calls:
|
|
1367
|
+
console_width = console.width if console else 80
|
|
1368
|
+
panel_width = console_width + 30
|
|
1369
|
+
|
|
1370
|
+
lines = []
|
|
1371
|
+
# Create a set to track already added calls by their string representation
|
|
1372
|
+
added_calls = set()
|
|
1373
|
+
for call in formatted_calls:
|
|
1374
|
+
if call not in added_calls:
|
|
1375
|
+
added_calls.add(call)
|
|
1376
|
+
# Wrap the call text to fit within the panel
|
|
1377
|
+
wrapped_call = textwrap.fill(f"• {call}", width=panel_width, subsequent_indent=" ")
|
|
1378
|
+
lines.append(wrapped_call)
|
|
1379
|
+
|
|
1380
|
+
# Join with blank lines between items
|
|
1381
|
+
tool_calls_text = "\n\n".join(lines)
|
|
1382
|
+
|
|
1383
|
+
team_tool_calls_panel = create_panel(
|
|
1384
|
+
content=tool_calls_text,
|
|
1385
|
+
title="Team Tool Calls",
|
|
1386
|
+
border_style="yellow",
|
|
1387
|
+
)
|
|
1388
|
+
panels.append(team_tool_calls_panel)
|
|
1317
1389
|
|
|
1390
|
+
# Add the team response panel at the end
|
|
1318
1391
|
if response_content_stream:
|
|
1319
1392
|
render = True
|
|
1320
1393
|
# Create panel for response
|
|
@@ -1324,11 +1397,13 @@ async def aprint_response_stream(
|
|
|
1324
1397
|
border_style="blue",
|
|
1325
1398
|
)
|
|
1326
1399
|
panels.append(response_panel)
|
|
1327
|
-
|
|
1400
|
+
|
|
1401
|
+
if render or len(panels) > 0:
|
|
1328
1402
|
live_console.update(Group(*panels))
|
|
1403
|
+
|
|
1329
1404
|
response_timer.stop()
|
|
1330
1405
|
|
|
1331
|
-
run_response =
|
|
1406
|
+
run_response = final_run_response
|
|
1332
1407
|
|
|
1333
1408
|
# Add citations
|
|
1334
1409
|
if hasattr(resp, "citations") and resp.citations is not None and resp.citations.urls is not None:
|
|
@@ -1415,6 +1490,7 @@ async def aprint_response_stream(
|
|
|
1415
1490
|
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
1416
1491
|
member_id = member_response.team_id
|
|
1417
1492
|
|
|
1493
|
+
# Print tool calls
|
|
1418
1494
|
if member_id:
|
|
1419
1495
|
# First add tool calls if any
|
|
1420
1496
|
if member_id in member_tool_calls and member_tool_calls[member_id]:
|
|
@@ -1472,39 +1548,39 @@ async def aprint_response_stream(
|
|
|
1472
1548
|
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
1473
1549
|
show_markdown = member_markdown.get(member_response.team_id, False)
|
|
1474
1550
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1551
|
+
member_response_content = _parse_response_content( # type: ignore
|
|
1552
|
+
member_response,
|
|
1553
|
+
tags_to_include_in_markdown,
|
|
1554
|
+
show_markdown=show_markdown,
|
|
1555
|
+
)
|
|
1480
1556
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1557
|
+
member_name = "Team Member"
|
|
1558
|
+
if isinstance(member_response, RunOutput) and member_response.agent_id is not None:
|
|
1559
|
+
member_name = team._get_member_name(member_response.agent_id)
|
|
1560
|
+
elif isinstance(member_response, TeamRunOutput) and member_response.team_id is not None:
|
|
1561
|
+
member_name = team._get_member_name(member_response.team_id)
|
|
1486
1562
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1563
|
+
member_response_panel = create_panel(
|
|
1564
|
+
content=member_response_content,
|
|
1565
|
+
title=f"{member_name} Response",
|
|
1566
|
+
border_style="magenta",
|
|
1567
|
+
)
|
|
1568
|
+
final_panels.append(member_response_panel)
|
|
1493
1569
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1570
|
+
# Add citations if any
|
|
1571
|
+
if member_response.citations is not None and member_response.citations.urls is not None:
|
|
1572
|
+
md_content = "\n".join(
|
|
1573
|
+
f"{i + 1}. [{citation.title or citation.url}]({citation.url})"
|
|
1574
|
+
for i, citation in enumerate(member_response.citations.urls)
|
|
1575
|
+
if citation.url # Only include citations with valid URLs
|
|
1576
|
+
)
|
|
1577
|
+
if md_content: # Only create panel if there are citations
|
|
1578
|
+
citations_panel = create_panel(
|
|
1579
|
+
content=Markdown(md_content),
|
|
1580
|
+
title="Citations",
|
|
1581
|
+
border_style="magenta",
|
|
1500
1582
|
)
|
|
1501
|
-
|
|
1502
|
-
citations_panel = create_panel(
|
|
1503
|
-
content=Markdown(md_content),
|
|
1504
|
-
title="Citations",
|
|
1505
|
-
border_style="magenta",
|
|
1506
|
-
)
|
|
1507
|
-
final_panels.append(citations_panel)
|
|
1583
|
+
final_panels.append(citations_panel)
|
|
1508
1584
|
|
|
1509
1585
|
# Add team tool calls before team response
|
|
1510
1586
|
if team_tool_calls:
|
|
@@ -1563,3 +1639,31 @@ async def aprint_response_stream(
|
|
|
1563
1639
|
|
|
1564
1640
|
# Final update with correctly ordered panels
|
|
1565
1641
|
live_console.update(Group(*final_panels))
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def _parse_response_content(
|
|
1645
|
+
run_response: Union[TeamRunOutput, RunOutput],
|
|
1646
|
+
tags_to_include_in_markdown: Set[str],
|
|
1647
|
+
show_markdown: bool = True,
|
|
1648
|
+
) -> Any:
|
|
1649
|
+
from rich.json import JSON
|
|
1650
|
+
from rich.markdown import Markdown
|
|
1651
|
+
|
|
1652
|
+
if isinstance(run_response.content, str):
|
|
1653
|
+
if show_markdown:
|
|
1654
|
+
escaped_content = escape_markdown_tags(run_response.content, tags_to_include_in_markdown)
|
|
1655
|
+
return Markdown(escaped_content)
|
|
1656
|
+
else:
|
|
1657
|
+
return run_response.get_content_as_string(indent=4)
|
|
1658
|
+
elif isinstance(run_response.content, BaseModel):
|
|
1659
|
+
try:
|
|
1660
|
+
return JSON(run_response.content.model_dump_json(exclude_none=True), indent=2)
|
|
1661
|
+
except Exception as e:
|
|
1662
|
+
log_warning(f"Failed to convert response to JSON: {e}")
|
|
1663
|
+
else:
|
|
1664
|
+
import json
|
|
1665
|
+
|
|
1666
|
+
try:
|
|
1667
|
+
return JSON(json.dumps(run_response.content), indent=4)
|
|
1668
|
+
except Exception as e:
|
|
1669
|
+
log_warning(f"Failed to convert response to JSON: {e}")
|