agno 1.7.10__py3-none-any.whl → 1.7.11__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/app/fastapi/app.py +3 -1
- agno/app/fastapi/async_router.py +1 -1
- agno/app/playground/app.py +1 -0
- agno/document/chunking/semantic.py +1 -3
- agno/document/reader/markdown_reader.py +2 -7
- agno/document/reader/text_reader.py +2 -2
- agno/knowledge/agent.py +4 -5
- agno/knowledge/markdown.py +15 -2
- agno/knowledge/website.py +4 -1
- agno/media.py +2 -0
- agno/models/aws/bedrock.py +51 -21
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +81 -0
- agno/models/openai/chat.py +3 -0
- agno/models/openai/responses.py +31 -14
- agno/models/qwen/__init__.py +5 -0
- agno/run/response.py +4 -0
- agno/run/team.py +4 -0
- agno/storage/in_memory.py +234 -0
- agno/team/team.py +10 -2
- agno/tools/brandfetch.py +210 -0
- agno/tools/github.py +10 -18
- agno/tools/trafilatura.py +372 -0
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/milvus/milvus.py +89 -1
- agno/workflow/workflow.py +3 -0
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/METADATA +4 -1
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/RECORD +32 -26
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/WHEEL +0 -0
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/entry_points.txt +0 -0
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.10.dist-info → agno-1.7.11.dist-info}/top_level.txt +0 -0
agno/app/fastapi/app.py
CHANGED
|
@@ -81,6 +81,7 @@ class FastAPIApp(BaseAPIApp):
|
|
|
81
81
|
workflow.app_id = self.app_id
|
|
82
82
|
if not workflow.workflow_id:
|
|
83
83
|
workflow.workflow_id = generate_id(workflow.name)
|
|
84
|
+
workflow.initialize_workflow()
|
|
84
85
|
|
|
85
86
|
def get_router(self) -> APIRouter:
|
|
86
87
|
return get_sync_router(agents=self.agents, teams=self.teams, workflows=self.workflows)
|
|
@@ -95,6 +96,7 @@ class FastAPIApp(BaseAPIApp):
|
|
|
95
96
|
host: str = "localhost",
|
|
96
97
|
port: int = 7777,
|
|
97
98
|
reload: bool = False,
|
|
99
|
+
workers: Optional[int] = None,
|
|
98
100
|
**kwargs,
|
|
99
101
|
):
|
|
100
102
|
self.set_app_id()
|
|
@@ -102,4 +104,4 @@ class FastAPIApp(BaseAPIApp):
|
|
|
102
104
|
|
|
103
105
|
log_info(f"Starting API on {host}:{port}")
|
|
104
106
|
|
|
105
|
-
uvicorn.run(app=app, host=host, port=port, reload=reload, **kwargs)
|
|
107
|
+
uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, **kwargs)
|
agno/app/fastapi/async_router.py
CHANGED
agno/app/playground/app.py
CHANGED
|
@@ -87,6 +87,7 @@ class Playground:
|
|
|
87
87
|
workflow.app_id = self.app_id
|
|
88
88
|
if not workflow.workflow_id:
|
|
89
89
|
workflow.workflow_id = generate_id(workflow.name)
|
|
90
|
+
workflow.initialize_workflow()
|
|
90
91
|
|
|
91
92
|
def set_app_id(self) -> str:
|
|
92
93
|
# If app_id is already set, keep it instead of overriding with UUID
|
|
@@ -14,9 +14,7 @@ except ImportError:
|
|
|
14
14
|
class SemanticChunking(ChunkingStrategy):
|
|
15
15
|
"""Chunking strategy that splits text into semantic chunks using chonkie"""
|
|
16
16
|
|
|
17
|
-
def __init__(
|
|
18
|
-
self, embedder: Optional[Embedder] = None, chunk_size: int = 5000, similarity_threshold: Optional[float] = 0.5
|
|
19
|
-
):
|
|
17
|
+
def __init__(self, embedder: Optional[Embedder] = None, chunk_size: int = 5000, similarity_threshold: float = 0.5):
|
|
20
18
|
self.embedder = embedder or OpenAIEmbedder(id="text-embedding-3-small") # type: ignore
|
|
21
19
|
self.chunk_size = chunk_size
|
|
22
20
|
self.similarity_threshold = similarity_threshold
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import uuid
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import IO, Any, List,
|
|
4
|
+
from typing import IO, Any, List, Union
|
|
5
5
|
|
|
6
6
|
from agno.document.base import Document
|
|
7
|
-
from agno.document.chunking.markdown import MarkdownChunking
|
|
8
|
-
from agno.document.chunking.strategy import ChunkingStrategy
|
|
9
7
|
from agno.document.reader.base import Reader
|
|
10
8
|
from agno.utils.log import log_info, logger
|
|
11
9
|
|
|
@@ -13,9 +11,6 @@ from agno.utils.log import log_info, logger
|
|
|
13
11
|
class MarkdownReader(Reader):
|
|
14
12
|
"""Reader for Markdown files"""
|
|
15
13
|
|
|
16
|
-
def __init__(self, chunking_strategy: Optional[ChunkingStrategy] = MarkdownChunking()) -> None:
|
|
17
|
-
super().__init__(chunking_strategy=chunking_strategy)
|
|
18
|
-
|
|
19
14
|
def read(self, file: Union[Path, IO[Any]]) -> List[Document]:
|
|
20
15
|
try:
|
|
21
16
|
if isinstance(file, Path):
|
|
@@ -30,7 +25,7 @@ class MarkdownReader(Reader):
|
|
|
30
25
|
file.seek(0)
|
|
31
26
|
file_contents = file.read().decode("utf-8")
|
|
32
27
|
|
|
33
|
-
documents = [Document(name=file_name, id=str(
|
|
28
|
+
documents = [Document(name=file_name, id=str(uuid.uuid4()), content=file_contents)]
|
|
34
29
|
if self.chunk:
|
|
35
30
|
chunked_documents = []
|
|
36
31
|
for document in documents:
|
|
@@ -28,7 +28,7 @@ class TextReader(Reader):
|
|
|
28
28
|
documents = [
|
|
29
29
|
Document(
|
|
30
30
|
name=file_name,
|
|
31
|
-
id=str(
|
|
31
|
+
id=str(uuid.uuid4()),
|
|
32
32
|
content=file_contents,
|
|
33
33
|
)
|
|
34
34
|
]
|
|
@@ -67,7 +67,7 @@ class TextReader(Reader):
|
|
|
67
67
|
|
|
68
68
|
document = Document(
|
|
69
69
|
name=file_name,
|
|
70
|
-
id=str(
|
|
70
|
+
id=str(uuid.uuid4()),
|
|
71
71
|
content=file_contents,
|
|
72
72
|
)
|
|
73
73
|
|
agno/knowledge/agent.py
CHANGED
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Set, Tuple
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel, ConfigDict,
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
6
6
|
|
|
7
7
|
from agno.document import Document
|
|
8
8
|
from agno.document.chunking.fixed import FixedSizeChunking
|
|
@@ -24,8 +24,7 @@ class AgentKnowledge(BaseModel):
|
|
|
24
24
|
# Number of documents to optimize the vector db on
|
|
25
25
|
optimize_on: Optional[int] = 1000
|
|
26
26
|
|
|
27
|
-
chunking_strategy: ChunkingStrategy =
|
|
28
|
-
|
|
27
|
+
chunking_strategy: Optional[ChunkingStrategy] = None
|
|
29
28
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
30
29
|
|
|
31
30
|
valid_metadata_filters: Set[str] = None # type: ignore
|
|
@@ -33,7 +32,7 @@ class AgentKnowledge(BaseModel):
|
|
|
33
32
|
@model_validator(mode="after")
|
|
34
33
|
def update_reader(self) -> "AgentKnowledge":
|
|
35
34
|
if self.reader is not None and self.reader.chunking_strategy is None:
|
|
36
|
-
self.reader.chunking_strategy = self.chunking_strategy
|
|
35
|
+
self.reader.chunking_strategy = self.chunking_strategy or FixedSizeChunking()
|
|
37
36
|
return self
|
|
38
37
|
|
|
39
38
|
@property
|
|
@@ -237,7 +236,7 @@ class AgentKnowledge(BaseModel):
|
|
|
237
236
|
self._load_init(recreate=False, upsert=upsert)
|
|
238
237
|
if self.vector_db is None:
|
|
239
238
|
return
|
|
240
|
-
|
|
239
|
+
|
|
241
240
|
log_info("Loading knowledge base")
|
|
242
241
|
# Upsert documents if upsert is True
|
|
243
242
|
if upsert and self.vector_db.upsert_available():
|
agno/knowledge/markdown.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union
|
|
2
|
+
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Union, cast
|
|
3
|
+
|
|
4
|
+
from pydantic import model_validator
|
|
3
5
|
|
|
4
6
|
from agno.document import Document
|
|
7
|
+
from agno.document.chunking.markdown import MarkdownChunking
|
|
5
8
|
from agno.document.reader.markdown_reader import MarkdownReader
|
|
6
9
|
from agno.knowledge.agent import AgentKnowledge
|
|
7
10
|
from agno.utils.log import log_info, logger
|
|
@@ -10,11 +13,18 @@ from agno.utils.log import log_info, logger
|
|
|
10
13
|
class MarkdownKnowledgeBase(AgentKnowledge):
|
|
11
14
|
path: Optional[Union[str, Path, List[Dict[str, Union[str, Dict[str, Any]]]]]] = None
|
|
12
15
|
formats: List[str] = [".md"]
|
|
13
|
-
reader: MarkdownReader =
|
|
16
|
+
reader: Optional[MarkdownReader] = None
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def set_reader(self) -> "MarkdownKnowledgeBase":
|
|
20
|
+
if self.reader is None:
|
|
21
|
+
self.reader = MarkdownReader(chunking_strategy=self.chunking_strategy or MarkdownChunking())
|
|
22
|
+
return self
|
|
14
23
|
|
|
15
24
|
@property
|
|
16
25
|
def document_lists(self) -> Iterator[List[Document]]:
|
|
17
26
|
"""Iterate over text files and yield lists of documents."""
|
|
27
|
+
self.reader = cast(MarkdownReader, self.reader)
|
|
18
28
|
if self.path is None:
|
|
19
29
|
raise ValueError("Path is not set")
|
|
20
30
|
|
|
@@ -49,6 +59,7 @@ class MarkdownKnowledgeBase(AgentKnowledge):
|
|
|
49
59
|
@property
|
|
50
60
|
async def async_document_lists(self) -> AsyncIterator[List[Document]]:
|
|
51
61
|
"""Iterate over text files and yield lists of documents asynchronously."""
|
|
62
|
+
self.reader = cast(MarkdownReader, self.reader)
|
|
52
63
|
if self.path is None:
|
|
53
64
|
raise ValueError("Path is not set")
|
|
54
65
|
|
|
@@ -85,6 +96,7 @@ class MarkdownKnowledgeBase(AgentKnowledge):
|
|
|
85
96
|
skip_existing: bool = True,
|
|
86
97
|
) -> None:
|
|
87
98
|
"""Load documents from a single text file with specific metadata into the vector DB."""
|
|
99
|
+
self.reader = cast(MarkdownReader, self.reader)
|
|
88
100
|
|
|
89
101
|
_file_path = Path(path) if isinstance(path, str) else path
|
|
90
102
|
|
|
@@ -117,6 +129,7 @@ class MarkdownKnowledgeBase(AgentKnowledge):
|
|
|
117
129
|
skip_existing: bool = True,
|
|
118
130
|
) -> None:
|
|
119
131
|
"""Load documents from a single text file with specific metadata into the vector DB."""
|
|
132
|
+
self.reader = cast(MarkdownReader, self.reader)
|
|
120
133
|
|
|
121
134
|
_file_path = Path(path) if isinstance(path, str) else path
|
|
122
135
|
|
agno/knowledge/website.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional
|
|
|
4
4
|
from pydantic import model_validator
|
|
5
5
|
|
|
6
6
|
from agno.document import Document
|
|
7
|
+
from agno.document.chunking.fixed import FixedSizeChunking
|
|
7
8
|
from agno.document.reader.website_reader import WebsiteReader
|
|
8
9
|
from agno.knowledge.agent import AgentKnowledge
|
|
9
10
|
from agno.utils.log import log_debug, log_info, logger
|
|
@@ -21,7 +22,9 @@ class WebsiteKnowledgeBase(AgentKnowledge):
|
|
|
21
22
|
def set_reader(self) -> "WebsiteKnowledgeBase":
|
|
22
23
|
if self.reader is None:
|
|
23
24
|
self.reader = WebsiteReader(
|
|
24
|
-
max_depth=self.max_depth,
|
|
25
|
+
max_depth=self.max_depth,
|
|
26
|
+
max_links=self.max_links,
|
|
27
|
+
chunking_strategy=self.chunking_strategy or FixedSizeChunking(),
|
|
25
28
|
)
|
|
26
29
|
return self
|
|
27
30
|
|
agno/media.py
CHANGED
|
@@ -319,6 +319,8 @@ class File(BaseModel):
|
|
|
319
319
|
mime_type: Optional[str] = None
|
|
320
320
|
# External file object (e.g. GeminiFile, must be a valid object as expected by the model you are using)
|
|
321
321
|
external: Optional[Any] = None
|
|
322
|
+
format: Optional[str] = None # E.g. `pdf`, `txt`, `csv`, `xml`, etc.
|
|
323
|
+
name: Optional[str] = None # Name of the file, mandatory for AWS Bedrock document input
|
|
322
324
|
|
|
323
325
|
@model_validator(mode="before")
|
|
324
326
|
@classmethod
|
agno/models/aws/bedrock.py
CHANGED
|
@@ -27,6 +27,11 @@ except ImportError:
|
|
|
27
27
|
AIOBOTO3_AVAILABLE = False
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
BEDROCK_SUPPORTED_IMAGE_FORMATS = ["png", "jpeg", "webp", "gif"]
|
|
31
|
+
BEDROCK_SUPPORTED_VIDEO_FORMATS = ["mp4", "mov", "mkv", "webm", "flv", "mpeg", "mpg", "wmv", "three_gp"]
|
|
32
|
+
BEDROCK_SUPPORTED_FILE_FORMATS = ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]
|
|
33
|
+
|
|
34
|
+
|
|
30
35
|
@dataclass
|
|
31
36
|
class AwsBedrock(Model):
|
|
32
37
|
"""
|
|
@@ -262,11 +267,16 @@ class AwsBedrock(Model):
|
|
|
262
267
|
|
|
263
268
|
if message.images:
|
|
264
269
|
for image in message.images:
|
|
265
|
-
if not image.content
|
|
266
|
-
raise ValueError("Image content
|
|
270
|
+
if not image.content:
|
|
271
|
+
raise ValueError("Image content is required for AWS Bedrock.")
|
|
272
|
+
if not image.format:
|
|
273
|
+
raise ValueError("Image format is required for AWS Bedrock.")
|
|
267
274
|
|
|
268
|
-
if image.format not in
|
|
269
|
-
raise ValueError(
|
|
275
|
+
if image.format not in BEDROCK_SUPPORTED_IMAGE_FORMATS:
|
|
276
|
+
raise ValueError(
|
|
277
|
+
f"Unsupported image format: {image.format}. "
|
|
278
|
+
f"Supported formats: {BEDROCK_SUPPORTED_IMAGE_FORMATS}"
|
|
279
|
+
)
|
|
270
280
|
|
|
271
281
|
formatted_message["content"].append(
|
|
272
282
|
{
|
|
@@ -283,21 +293,16 @@ class AwsBedrock(Model):
|
|
|
283
293
|
|
|
284
294
|
if message.videos:
|
|
285
295
|
for video in message.videos:
|
|
286
|
-
if not video.content
|
|
287
|
-
raise ValueError("Video content
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"mpg",
|
|
297
|
-
"wmv",
|
|
298
|
-
"three_gp",
|
|
299
|
-
]:
|
|
300
|
-
raise ValueError(f"Unsupported video format: {video.format}")
|
|
296
|
+
if not video.content:
|
|
297
|
+
raise ValueError("Video content is required for AWS Bedrock.")
|
|
298
|
+
if not video.format:
|
|
299
|
+
raise ValueError("Video format is required for AWS Bedrock.")
|
|
300
|
+
|
|
301
|
+
if video.format not in BEDROCK_SUPPORTED_VIDEO_FORMATS:
|
|
302
|
+
raise ValueError(
|
|
303
|
+
f"Unsupported video format: {video.format}. "
|
|
304
|
+
f"Supported formats: {BEDROCK_SUPPORTED_VIDEO_FORMATS}"
|
|
305
|
+
)
|
|
301
306
|
|
|
302
307
|
formatted_message["content"].append(
|
|
303
308
|
{
|
|
@@ -309,8 +314,33 @@ class AwsBedrock(Model):
|
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
316
|
)
|
|
312
|
-
|
|
313
|
-
|
|
317
|
+
|
|
318
|
+
if message.files:
|
|
319
|
+
for file in message.files:
|
|
320
|
+
if not file.content:
|
|
321
|
+
raise ValueError("File content is required for AWS Bedrock document input.")
|
|
322
|
+
if not file.format:
|
|
323
|
+
raise ValueError("File format is required for AWS Bedrock document input.")
|
|
324
|
+
if not file.name:
|
|
325
|
+
raise ValueError("File name is required for AWS Bedrock document input.")
|
|
326
|
+
|
|
327
|
+
if file.format not in BEDROCK_SUPPORTED_FILE_FORMATS:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
f"Unsupported file format: {file.format}. "
|
|
330
|
+
f"Supported formats: {BEDROCK_SUPPORTED_FILE_FORMATS}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
formatted_message["content"].append(
|
|
334
|
+
{
|
|
335
|
+
"document": {
|
|
336
|
+
"format": file.format,
|
|
337
|
+
"name": file.name,
|
|
338
|
+
"source": {
|
|
339
|
+
"bytes": file.content,
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
)
|
|
314
344
|
|
|
315
345
|
formatted_messages.append(formatted_message)
|
|
316
346
|
# TODO: Add caching: https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from agno.exceptions import ModelProviderError
|
|
8
|
+
from agno.models.openai.like import OpenAILike
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DashScope(OpenAILike):
|
|
13
|
+
"""
|
|
14
|
+
A class for interacting with Qwen models via DashScope API.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
id (str): The model id. Defaults to "qwen-plus".
|
|
18
|
+
name (str): The model name. Defaults to "Qwen".
|
|
19
|
+
provider (str): The provider name. Defaults to "Qwen".
|
|
20
|
+
api_key (Optional[str]): The DashScope API key.
|
|
21
|
+
base_url (str): The base URL. Defaults to "https://dashscope-intl.aliyuncs.com/compatible-mode/v1".
|
|
22
|
+
enable_thinking (Optional[bool]): Enable thinking process (DashScope native parameter). Defaults to None.
|
|
23
|
+
include_thoughts (Optional[bool]): Include thinking process in response (alternative parameter). Defaults to None.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
id: str = "qwen-plus"
|
|
27
|
+
name: str = "Qwen"
|
|
28
|
+
provider: str = "Dashscope"
|
|
29
|
+
|
|
30
|
+
api_key: Optional[str] = getenv("DASHSCOPE_API_KEY") or getenv("QWEN_API_KEY")
|
|
31
|
+
base_url: str = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
|
32
|
+
|
|
33
|
+
# Thinking parameters
|
|
34
|
+
enable_thinking: Optional[bool] = None
|
|
35
|
+
include_thoughts: Optional[bool] = None
|
|
36
|
+
|
|
37
|
+
# DashScope supports structured outputs
|
|
38
|
+
supports_native_structured_outputs: bool = True
|
|
39
|
+
supports_json_schema_outputs: bool = True
|
|
40
|
+
|
|
41
|
+
def _get_client_params(self) -> Dict[str, Any]:
|
|
42
|
+
if not self.api_key:
|
|
43
|
+
self.api_key = getenv("DASHSCOPE_API_KEY")
|
|
44
|
+
if not self.api_key:
|
|
45
|
+
raise ModelProviderError(
|
|
46
|
+
message="DASHSCOPE_API_KEY not set. Please set the DASHSCOPE_API_KEY environment variable.",
|
|
47
|
+
model_name=self.name,
|
|
48
|
+
model_id=self.id,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Define base client params
|
|
52
|
+
base_params = {
|
|
53
|
+
"api_key": self.api_key,
|
|
54
|
+
"organization": self.organization,
|
|
55
|
+
"base_url": self.base_url,
|
|
56
|
+
"timeout": self.timeout,
|
|
57
|
+
"max_retries": self.max_retries,
|
|
58
|
+
"default_headers": self.default_headers,
|
|
59
|
+
"default_query": self.default_query,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Create client_params dict with non-None values
|
|
63
|
+
client_params = {k: v for k, v in base_params.items() if v is not None}
|
|
64
|
+
|
|
65
|
+
# Add additional client params if provided
|
|
66
|
+
if self.client_params:
|
|
67
|
+
client_params.update(self.client_params)
|
|
68
|
+
return client_params
|
|
69
|
+
|
|
70
|
+
def get_request_params(
|
|
71
|
+
self,
|
|
72
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
73
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
74
|
+
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
75
|
+
) -> Dict[str, Any]:
|
|
76
|
+
params = super().get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice)
|
|
77
|
+
|
|
78
|
+
should_include_thoughts = self.enable_thinking or self.include_thoughts
|
|
79
|
+
if should_include_thoughts:
|
|
80
|
+
params["extra_body"] = {"enable_thinking": True}
|
|
81
|
+
return params
|
agno/models/openai/chat.py
CHANGED
|
@@ -694,6 +694,9 @@ class OpenAIChat(Model):
|
|
|
694
694
|
if choice_delta.tool_calls is not None:
|
|
695
695
|
model_response.tool_calls = choice_delta.tool_calls # type: ignore
|
|
696
696
|
|
|
697
|
+
if hasattr(choice_delta, "reasoning_content") and choice_delta.reasoning_content is not None:
|
|
698
|
+
model_response.reasoning_content = choice_delta.reasoning_content
|
|
699
|
+
|
|
697
700
|
# Add audio if present
|
|
698
701
|
if hasattr(choice_delta, "audio") and choice_delta.audio is not None:
|
|
699
702
|
try:
|
agno/models/openai/responses.py
CHANGED
|
@@ -372,6 +372,17 @@ class OpenAIResponses(Model):
|
|
|
372
372
|
previous_response_id = msg.provider_data["response_id"]
|
|
373
373
|
break
|
|
374
374
|
|
|
375
|
+
# Build a mapping from function_call id (fc_*) → call_id (call_*) from prior assistant tool_calls
|
|
376
|
+
fc_id_to_call_id: Dict[str, str] = {}
|
|
377
|
+
for msg in messages:
|
|
378
|
+
tool_calls = getattr(msg, "tool_calls", None)
|
|
379
|
+
if tool_calls:
|
|
380
|
+
for tc in tool_calls:
|
|
381
|
+
fc_id = tc.get("id")
|
|
382
|
+
call_id = tc.get("call_id") or fc_id
|
|
383
|
+
if isinstance(fc_id, str) and isinstance(call_id, str):
|
|
384
|
+
fc_id_to_call_id[fc_id] = call_id
|
|
385
|
+
|
|
375
386
|
for message in messages:
|
|
376
387
|
if message.role in ["user", "system"]:
|
|
377
388
|
message_dict: Dict[str, Any] = {
|
|
@@ -398,27 +409,32 @@ class OpenAIResponses(Model):
|
|
|
398
409
|
|
|
399
410
|
formatted_messages.append(message_dict)
|
|
400
411
|
|
|
412
|
+
# Tool call result
|
|
401
413
|
elif message.role == "tool":
|
|
402
414
|
if message.tool_call_id and message.content is not None:
|
|
415
|
+
function_call_id = message.tool_call_id
|
|
416
|
+
# Normalize: if a fc_* id was provided, translate to its corresponding call_* id
|
|
417
|
+
if isinstance(function_call_id, str) and function_call_id in fc_id_to_call_id:
|
|
418
|
+
call_id_value = fc_id_to_call_id[function_call_id]
|
|
419
|
+
else:
|
|
420
|
+
call_id_value = function_call_id
|
|
403
421
|
formatted_messages.append(
|
|
404
|
-
{"type": "function_call_output", "call_id":
|
|
422
|
+
{"type": "function_call_output", "call_id": call_id_value, "output": message.content}
|
|
405
423
|
)
|
|
424
|
+
# Tool Calls
|
|
406
425
|
elif message.tool_calls is not None and len(message.tool_calls) > 0:
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
# errors (e.g., missing required reasoning item).
|
|
413
|
-
if previous_response_id is not None:
|
|
414
|
-
continue
|
|
426
|
+
# Only skip re-sending prior function_call items when we have a previous_response_id
|
|
427
|
+
# (reasoning models). For non-reasoning models, we must include the prior function_call
|
|
428
|
+
# so the API can associate the subsequent function_call_output by call_id.
|
|
429
|
+
if self._using_reasoning_model() and previous_response_id is not None:
|
|
430
|
+
continue
|
|
415
431
|
|
|
416
432
|
for tool_call in message.tool_calls:
|
|
417
433
|
formatted_messages.append(
|
|
418
434
|
{
|
|
419
435
|
"type": "function_call",
|
|
420
|
-
"id": tool_call
|
|
421
|
-
"call_id": tool_call
|
|
436
|
+
"id": tool_call.get("id"),
|
|
437
|
+
"call_id": tool_call.get("call_id", tool_call.get("id")),
|
|
422
438
|
"name": tool_call["function"]["name"],
|
|
423
439
|
"arguments": tool_call["function"]["arguments"],
|
|
424
440
|
"status": "completed",
|
|
@@ -719,7 +735,8 @@ class OpenAIResponses(Model):
|
|
|
719
735
|
model_response.tool_calls.append(
|
|
720
736
|
{
|
|
721
737
|
"id": output.id,
|
|
722
|
-
|
|
738
|
+
# Store additional call_id from OpenAI responses
|
|
739
|
+
"call_id": output.call_id or output.id,
|
|
723
740
|
"type": "function",
|
|
724
741
|
"function": {
|
|
725
742
|
"name": output.name,
|
|
@@ -809,8 +826,8 @@ class OpenAIResponses(Model):
|
|
|
809
826
|
item = stream_event.item
|
|
810
827
|
if item.type == "function_call":
|
|
811
828
|
tool_use = {
|
|
812
|
-
"id": item
|
|
813
|
-
"call_id": item
|
|
829
|
+
"id": getattr(item, "id", None),
|
|
830
|
+
"call_id": getattr(item, "call_id", None) or getattr(item, "id", None),
|
|
814
831
|
"type": "function",
|
|
815
832
|
"function": {
|
|
816
833
|
"name": item.name,
|
agno/run/response.py
CHANGED
|
@@ -420,6 +420,9 @@ class RunResponse:
|
|
|
420
420
|
messages = data.pop("messages", None)
|
|
421
421
|
messages = [Message.model_validate(message) for message in messages] if messages else None
|
|
422
422
|
|
|
423
|
+
citations = data.pop("citations", None)
|
|
424
|
+
citations = Citations.model_validate(citations) if citations else None
|
|
425
|
+
|
|
423
426
|
tools = data.pop("tools", [])
|
|
424
427
|
tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
|
|
425
428
|
|
|
@@ -441,6 +444,7 @@ class RunResponse:
|
|
|
441
444
|
|
|
442
445
|
return cls(
|
|
443
446
|
messages=messages,
|
|
447
|
+
citations=citations,
|
|
444
448
|
tools=tools,
|
|
445
449
|
images=images,
|
|
446
450
|
audio=audio,
|
agno/run/team.py
CHANGED
|
@@ -416,6 +416,9 @@ class TeamRunResponse:
|
|
|
416
416
|
response_audio = data.pop("response_audio", None)
|
|
417
417
|
response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
|
|
418
418
|
|
|
419
|
+
citations = data.pop("citations", None)
|
|
420
|
+
citations = Citations.model_validate(citations) if citations else None
|
|
421
|
+
|
|
419
422
|
# To make it backwards compatible
|
|
420
423
|
if "event" in data:
|
|
421
424
|
data.pop("event")
|
|
@@ -428,6 +431,7 @@ class TeamRunResponse:
|
|
|
428
431
|
videos=videos,
|
|
429
432
|
audio=audio,
|
|
430
433
|
response_audio=response_audio,
|
|
434
|
+
citations=citations,
|
|
431
435
|
tools=tools,
|
|
432
436
|
events=events,
|
|
433
437
|
**data,
|