agno 2.0.11__py3-none-any.whl → 2.1.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 +607 -176
- agno/db/in_memory/in_memory_db.py +42 -29
- agno/db/mongo/mongo.py +65 -66
- agno/db/postgres/postgres.py +6 -4
- agno/db/utils.py +50 -22
- agno/exceptions.py +62 -1
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +51 -0
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/ollama.py +5 -0
- agno/knowledge/embedder/openai.py +18 -54
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +11 -4
- agno/knowledge/reader/pdf_reader.py +4 -3
- agno/knowledge/reader/website_reader.py +3 -2
- agno/models/base.py +125 -32
- agno/models/cerebras/cerebras.py +1 -0
- agno/models/cerebras/cerebras_openai.py +1 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/google/gemini.py +27 -5
- agno/models/openai/chat.py +13 -4
- agno/models/openai/responses.py +1 -1
- agno/models/perplexity/perplexity.py +2 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +49 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +1 -0
- agno/os/app.py +98 -126
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/agui/agui.py +21 -5
- agno/os/interfaces/base.py +4 -2
- agno/os/interfaces/slack/slack.py +13 -8
- agno/os/interfaces/whatsapp/router.py +2 -0
- agno/os/interfaces/whatsapp/whatsapp.py +12 -5
- agno/os/mcp.py +2 -2
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +182 -46
- agno/os/routers/home.py +2 -2
- agno/os/routers/memory/memory.py +23 -1
- agno/os/routers/memory/schemas.py +1 -1
- agno/os/routers/session/session.py +20 -3
- agno/os/utils.py +74 -8
- agno/run/agent.py +120 -77
- agno/run/base.py +2 -13
- agno/run/team.py +115 -72
- agno/run/workflow.py +5 -15
- agno/session/summary.py +9 -10
- agno/session/team.py +2 -1
- agno/team/team.py +721 -169
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +42 -2
- agno/tools/knowledge.py +3 -3
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/spider.py +2 -2
- agno/tools/workflow.py +4 -5
- agno/utils/events.py +66 -1
- agno/utils/hooks.py +57 -0
- agno/utils/media.py +11 -9
- agno/utils/print_response/agent.py +43 -5
- agno/utils/print_response/team.py +48 -12
- agno/utils/serialize.py +32 -0
- agno/vectordb/cassandra/cassandra.py +44 -4
- agno/vectordb/chroma/chromadb.py +79 -8
- agno/vectordb/clickhouse/clickhousedb.py +43 -6
- agno/vectordb/couchbase/couchbase.py +76 -5
- agno/vectordb/lancedb/lance_db.py +38 -3
- agno/vectordb/milvus/milvus.py +76 -4
- agno/vectordb/mongodb/mongodb.py +76 -4
- agno/vectordb/pgvector/pgvector.py +50 -6
- agno/vectordb/pineconedb/pineconedb.py +39 -2
- agno/vectordb/qdrant/qdrant.py +76 -26
- agno/vectordb/singlestore/singlestore.py +77 -4
- agno/vectordb/upstashdb/upstashdb.py +42 -2
- agno/vectordb/weaviate/weaviate.py +39 -3
- agno/workflow/types.py +5 -6
- agno/workflow/workflow.py +58 -2
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/METADATA +4 -3
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/RECORD +93 -82
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/WHEEL +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/top_level.txt +0 -0
|
@@ -140,66 +140,24 @@ class OpenAIEmbedder(Embedder):
|
|
|
140
140
|
logger.warning(e)
|
|
141
141
|
return [], None
|
|
142
142
|
|
|
143
|
-
def
|
|
143
|
+
async def async_get_embeddings_batch_and_usage(
|
|
144
|
+
self, texts: List[str]
|
|
145
|
+
) -> Tuple[List[List[float]], List[Optional[Dict]]]:
|
|
144
146
|
"""
|
|
145
|
-
Get embeddings for multiple texts in batches.
|
|
147
|
+
Get embeddings and usage for multiple texts in batches (async version).
|
|
146
148
|
|
|
147
149
|
Args:
|
|
148
150
|
texts: List of text strings to embed
|
|
149
|
-
batch_size: Number of texts to process in each API call (max ~2048)
|
|
150
151
|
|
|
151
152
|
Returns:
|
|
152
|
-
List of embedding vectors
|
|
153
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
153
154
|
"""
|
|
154
155
|
all_embeddings = []
|
|
156
|
+
all_usage = []
|
|
157
|
+
logger.info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size} (async)")
|
|
155
158
|
|
|
156
|
-
for i in range(0, len(texts), batch_size):
|
|
157
|
-
batch_texts = texts[i : i + batch_size]
|
|
158
|
-
|
|
159
|
-
req: Dict[str, Any] = {
|
|
160
|
-
"input": batch_texts,
|
|
161
|
-
"model": self.id,
|
|
162
|
-
"encoding_format": self.encoding_format,
|
|
163
|
-
}
|
|
164
|
-
if self.user is not None:
|
|
165
|
-
req["user"] = self.user
|
|
166
|
-
if self.id.startswith("text-embedding-3"):
|
|
167
|
-
req["dimensions"] = self.dimensions
|
|
168
|
-
if self.request_params:
|
|
169
|
-
req.update(self.request_params)
|
|
170
|
-
|
|
171
|
-
try:
|
|
172
|
-
response: CreateEmbeddingResponse = self.client.embeddings.create(**req)
|
|
173
|
-
batch_embeddings = [data.embedding for data in response.data]
|
|
174
|
-
all_embeddings.extend(batch_embeddings)
|
|
175
|
-
except Exception as e:
|
|
176
|
-
logger.warning(f"Error in batch embedding: {e}")
|
|
177
|
-
# Fallback to individual calls for this batch
|
|
178
|
-
for text in batch_texts:
|
|
179
|
-
try:
|
|
180
|
-
embedding = self.get_embedding(text)
|
|
181
|
-
all_embeddings.append(embedding)
|
|
182
|
-
except Exception as e2:
|
|
183
|
-
logger.warning(f"Error in individual embedding fallback: {e2}")
|
|
184
|
-
all_embeddings.append([])
|
|
185
|
-
|
|
186
|
-
return all_embeddings
|
|
187
|
-
|
|
188
|
-
async def async_get_embeddings_batch(self, texts: List[str], batch_size: int = 100) -> List[List[float]]:
|
|
189
|
-
"""
|
|
190
|
-
Get embeddings for multiple texts in batches (async version).
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
texts: List of text strings to embed
|
|
194
|
-
batch_size: Number of texts to process in each API call (max ~2048)
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
List of embedding vectors
|
|
198
|
-
"""
|
|
199
|
-
all_embeddings = []
|
|
200
|
-
|
|
201
|
-
for i in range(0, len(texts), batch_size):
|
|
202
|
-
batch_texts = texts[i : i + batch_size]
|
|
159
|
+
for i in range(0, len(texts), self.batch_size):
|
|
160
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
203
161
|
|
|
204
162
|
req: Dict[str, Any] = {
|
|
205
163
|
"input": batch_texts,
|
|
@@ -217,15 +175,21 @@ class OpenAIEmbedder(Embedder):
|
|
|
217
175
|
response: CreateEmbeddingResponse = await self.aclient.embeddings.create(**req)
|
|
218
176
|
batch_embeddings = [data.embedding for data in response.data]
|
|
219
177
|
all_embeddings.extend(batch_embeddings)
|
|
178
|
+
|
|
179
|
+
# For each embedding in the batch, add the same usage information
|
|
180
|
+
usage_dict = response.usage.model_dump() if response.usage else None
|
|
181
|
+
all_usage.extend([usage_dict] * len(batch_embeddings))
|
|
220
182
|
except Exception as e:
|
|
221
183
|
logger.warning(f"Error in async batch embedding: {e}")
|
|
222
|
-
# Fallback to individual
|
|
184
|
+
# Fallback to individual calls for this batch
|
|
223
185
|
for text in batch_texts:
|
|
224
186
|
try:
|
|
225
|
-
embedding = await self.
|
|
187
|
+
embedding, usage = await self.async_get_embedding_and_usage(text)
|
|
226
188
|
all_embeddings.append(embedding)
|
|
189
|
+
all_usage.append(usage)
|
|
227
190
|
except Exception as e2:
|
|
228
191
|
logger.warning(f"Error in individual async embedding fallback: {e2}")
|
|
229
192
|
all_embeddings.append([])
|
|
193
|
+
all_usage.append(None)
|
|
230
194
|
|
|
231
|
-
return all_embeddings
|
|
195
|
+
return all_embeddings, all_usage
|
|
@@ -30,12 +30,13 @@ class VoyageAIEmbedder(Embedder):
|
|
|
30
30
|
if self.voyage_client:
|
|
31
31
|
return self.voyage_client
|
|
32
32
|
|
|
33
|
-
_client_params = {
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
_client_params: Dict[str, Any] = {}
|
|
34
|
+
if self.api_key is not None:
|
|
35
|
+
_client_params["api_key"] = self.api_key
|
|
36
|
+
if self.max_retries is not None:
|
|
37
|
+
_client_params["max_retries"] = self.max_retries
|
|
38
|
+
if self.timeout is not None:
|
|
39
|
+
_client_params["timeout"] = self.timeout
|
|
39
40
|
if self.client_params:
|
|
40
41
|
_client_params.update(self.client_params)
|
|
41
42
|
self.voyage_client = VoyageClient(**_client_params)
|
|
@@ -46,12 +47,13 @@ class VoyageAIEmbedder(Embedder):
|
|
|
46
47
|
if self.async_client:
|
|
47
48
|
return self.async_client
|
|
48
49
|
|
|
49
|
-
_client_params = {
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
_client_params: Dict[str, Any] = {}
|
|
51
|
+
if self.api_key is not None:
|
|
52
|
+
_client_params["api_key"] = self.api_key
|
|
53
|
+
if self.max_retries is not None:
|
|
54
|
+
_client_params["max_retries"] = self.max_retries
|
|
55
|
+
if self.timeout is not None:
|
|
56
|
+
_client_params["timeout"] = self.timeout
|
|
55
57
|
if self.client_params:
|
|
56
58
|
_client_params.update(self.client_params)
|
|
57
59
|
self.async_client = AsyncVoyageClient(**_client_params)
|
|
@@ -69,7 +71,8 @@ class VoyageAIEmbedder(Embedder):
|
|
|
69
71
|
def get_embedding(self, text: str) -> List[float]:
|
|
70
72
|
response: EmbeddingsObject = self._response(text=text)
|
|
71
73
|
try:
|
|
72
|
-
|
|
74
|
+
embedding = response.embeddings[0]
|
|
75
|
+
return [float(x) for x in embedding] # Ensure all values are float
|
|
73
76
|
except Exception as e:
|
|
74
77
|
logger.warning(e)
|
|
75
78
|
return []
|
|
@@ -79,7 +82,7 @@ class VoyageAIEmbedder(Embedder):
|
|
|
79
82
|
|
|
80
83
|
embedding = response.embeddings[0]
|
|
81
84
|
usage = {"total_tokens": response.total_tokens}
|
|
82
|
-
return embedding, usage
|
|
85
|
+
return [float(x) for x in embedding], usage
|
|
83
86
|
|
|
84
87
|
async def _async_response(self, text: str) -> EmbeddingsObject:
|
|
85
88
|
"""Async version of _response using AsyncVoyageClient."""
|
|
@@ -95,7 +98,8 @@ class VoyageAIEmbedder(Embedder):
|
|
|
95
98
|
"""Async version of get_embedding."""
|
|
96
99
|
try:
|
|
97
100
|
response: EmbeddingsObject = await self._async_response(text=text)
|
|
98
|
-
|
|
101
|
+
embedding = response.embeddings[0]
|
|
102
|
+
return [float(x) for x in embedding] # Ensure all values are float
|
|
99
103
|
except Exception as e:
|
|
100
104
|
logger.warning(f"Error getting embedding: {e}")
|
|
101
105
|
return []
|
|
@@ -106,7 +110,56 @@ class VoyageAIEmbedder(Embedder):
|
|
|
106
110
|
response: EmbeddingsObject = await self._async_response(text=text)
|
|
107
111
|
embedding = response.embeddings[0]
|
|
108
112
|
usage = {"total_tokens": response.total_tokens}
|
|
109
|
-
return embedding, usage
|
|
113
|
+
return [float(x) for x in embedding], usage
|
|
110
114
|
except Exception as e:
|
|
111
115
|
logger.warning(f"Error getting embedding and usage: {e}")
|
|
112
116
|
return [], None
|
|
117
|
+
|
|
118
|
+
async def async_get_embeddings_batch_and_usage(
|
|
119
|
+
self, texts: List[str]
|
|
120
|
+
) -> Tuple[List[List[float]], List[Optional[Dict]]]:
|
|
121
|
+
"""
|
|
122
|
+
Get embeddings and usage for multiple texts in batches.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
texts: List of text strings to embed
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
129
|
+
"""
|
|
130
|
+
all_embeddings: List[List[float]] = []
|
|
131
|
+
all_usage: List[Optional[Dict]] = []
|
|
132
|
+
logger.info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size}")
|
|
133
|
+
|
|
134
|
+
for i in range(0, len(texts), self.batch_size):
|
|
135
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
136
|
+
|
|
137
|
+
req: Dict[str, Any] = {
|
|
138
|
+
"texts": batch_texts,
|
|
139
|
+
"model": self.id,
|
|
140
|
+
}
|
|
141
|
+
if self.request_params:
|
|
142
|
+
req.update(self.request_params)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
response: EmbeddingsObject = await self.aclient.embed(**req)
|
|
146
|
+
batch_embeddings = [[float(x) for x in emb] for emb in response.embeddings]
|
|
147
|
+
all_embeddings.extend(batch_embeddings)
|
|
148
|
+
|
|
149
|
+
# For each embedding in the batch, add the same usage information
|
|
150
|
+
usage_dict = {"total_tokens": response.total_tokens}
|
|
151
|
+
all_usage.extend([usage_dict] * len(batch_embeddings))
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.warning(f"Error in async batch embedding: {e}")
|
|
154
|
+
# Fallback to individual calls for this batch
|
|
155
|
+
for text in batch_texts:
|
|
156
|
+
try:
|
|
157
|
+
embedding, usage = await self.async_get_embedding_and_usage(text)
|
|
158
|
+
all_embeddings.append(embedding)
|
|
159
|
+
all_usage.append(usage)
|
|
160
|
+
except Exception as e2:
|
|
161
|
+
logger.warning(f"Error in individual async embedding fallback: {e2}")
|
|
162
|
+
all_embeddings.append([])
|
|
163
|
+
all_usage.append(None)
|
|
164
|
+
|
|
165
|
+
return all_embeddings, all_usage
|
agno/knowledge/knowledge.py
CHANGED
|
@@ -89,7 +89,7 @@ class Knowledge:
|
|
|
89
89
|
url=argument.get("url"),
|
|
90
90
|
metadata=argument.get("metadata"),
|
|
91
91
|
topics=argument.get("topics"),
|
|
92
|
-
|
|
92
|
+
text_content=argument.get("text_content"),
|
|
93
93
|
reader=argument.get("reader"),
|
|
94
94
|
include=argument.get("include"),
|
|
95
95
|
exclude=argument.get("exclude"),
|
|
@@ -251,7 +251,9 @@ class Knowledge:
|
|
|
251
251
|
) -> None:
|
|
252
252
|
# Validation: At least one of the parameters must be provided
|
|
253
253
|
if all(argument is None for argument in [path, url, text_content, topics, remote_content]):
|
|
254
|
-
|
|
254
|
+
log_warning(
|
|
255
|
+
"At least one of 'path', 'url', 'text_content', 'topics', or 'remote_content' must be provided."
|
|
256
|
+
)
|
|
255
257
|
return
|
|
256
258
|
|
|
257
259
|
if not skip_if_exists:
|
|
@@ -534,7 +536,6 @@ class Knowledge:
|
|
|
534
536
|
reader = content.reader
|
|
535
537
|
name = content.name if content.name else content.url
|
|
536
538
|
# Else select based on file extension
|
|
537
|
-
|
|
538
539
|
if reader is None:
|
|
539
540
|
if file_extension == ".csv":
|
|
540
541
|
name = basename(parsed_url.path) or "data.csv"
|
|
@@ -570,6 +571,7 @@ class Knowledge:
|
|
|
570
571
|
read_documents = reader.read(bytes_content, name=name)
|
|
571
572
|
else:
|
|
572
573
|
read_documents = reader.read(content.url, name=name)
|
|
574
|
+
|
|
573
575
|
except Exception as e:
|
|
574
576
|
log_error(f"Error reading URL: {content.url} - {str(e)}")
|
|
575
577
|
content.status = ContentStatus.FAILED
|
|
@@ -580,7 +582,6 @@ class Knowledge:
|
|
|
580
582
|
# 6. Chunk documents if needed
|
|
581
583
|
if reader and not reader.chunk:
|
|
582
584
|
read_documents = await reader.chunk_documents_async(read_documents)
|
|
583
|
-
|
|
584
585
|
# 7. Prepare and insert the content in the vector database
|
|
585
586
|
file_size = 0
|
|
586
587
|
if read_documents:
|
|
@@ -1356,6 +1357,12 @@ class Knowledge:
|
|
|
1356
1357
|
log_error(f"Error searching for documents: {e}")
|
|
1357
1358
|
return []
|
|
1358
1359
|
|
|
1360
|
+
def get_valid_filters(self) -> Set[str]:
|
|
1361
|
+
if self.valid_metadata_filters is None:
|
|
1362
|
+
self.valid_metadata_filters = set()
|
|
1363
|
+
self.valid_metadata_filters.update(self._get_filters_from_db)
|
|
1364
|
+
return self.valid_metadata_filters
|
|
1365
|
+
|
|
1359
1366
|
def validate_filters(self, filters: Optional[Dict[str, Any]]) -> Tuple[Dict[str, Any], List[str]]:
|
|
1360
1367
|
if self.valid_metadata_filters is None:
|
|
1361
1368
|
self.valid_metadata_filters = set()
|
|
@@ -117,6 +117,10 @@ def _clean_page_numbers(
|
|
|
117
117
|
page_numbers = [find_page_number(content) for content in page_content_list]
|
|
118
118
|
if all(x is None or x > 5 for x in page_numbers):
|
|
119
119
|
# This approach won't work reliably for higher page numbers.
|
|
120
|
+
page_content_list = [
|
|
121
|
+
f"\n{page_content_list[i]}\n{extra_content[i]}" if extra_content else page_content_list[i]
|
|
122
|
+
for i in range(len(page_content_list))
|
|
123
|
+
]
|
|
120
124
|
return page_content_list, None
|
|
121
125
|
|
|
122
126
|
# Possible range shifts to detect page numbering
|
|
@@ -261,7 +265,6 @@ class BasePDFReader(Reader):
|
|
|
261
265
|
|
|
262
266
|
if self.chunk:
|
|
263
267
|
return self._build_chunked_documents(documents)
|
|
264
|
-
|
|
265
268
|
return documents
|
|
266
269
|
|
|
267
270
|
def _pdf_reader_to_documents(
|
|
@@ -339,8 +342,6 @@ class PDFReader(BasePDFReader):
|
|
|
339
342
|
except Exception:
|
|
340
343
|
doc_name = "pdf"
|
|
341
344
|
|
|
342
|
-
log_info(f"Reading: {doc_name}")
|
|
343
|
-
|
|
344
345
|
try:
|
|
345
346
|
DocumentReader(pdf)
|
|
346
347
|
except PdfStreamError as e:
|
|
@@ -112,7 +112,8 @@ class WebsiteReader(Reader):
|
|
|
112
112
|
if tag.name in ["article", "main", "section"]:
|
|
113
113
|
return True
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
classes_attr = tag.get("class")
|
|
116
|
+
classes: List[str] = classes_attr if isinstance(classes_attr, list) else []
|
|
116
117
|
content_classes = ["content", "main-content", "post-content", "entry-content", "article-body"]
|
|
117
118
|
if any(cls in content_classes for cls in classes):
|
|
118
119
|
return True
|
|
@@ -126,7 +127,7 @@ class WebsiteReader(Reader):
|
|
|
126
127
|
|
|
127
128
|
# Try to find main content element
|
|
128
129
|
element = soup.find(match)
|
|
129
|
-
if element:
|
|
130
|
+
if element and hasattr(element, "find_all"):
|
|
130
131
|
# Remove common unwanted elements from the found content
|
|
131
132
|
for unwanted in element.find_all(["script", "style", "nav", "header", "footer"]):
|
|
132
133
|
unwanted.decompose()
|
agno/models/base.py
CHANGED
|
@@ -715,6 +715,7 @@ class Model(ABC):
|
|
|
715
715
|
assistant_message = Message(role=self.assistant_message_role)
|
|
716
716
|
# Create assistant message and stream data
|
|
717
717
|
stream_data = MessageData()
|
|
718
|
+
model_response = ModelResponse()
|
|
718
719
|
if stream_model_response:
|
|
719
720
|
# Generate response
|
|
720
721
|
yield from self.process_response_stream(
|
|
@@ -744,7 +745,6 @@ class Model(ABC):
|
|
|
744
745
|
assistant_message.tool_calls = self.parse_tool_calls(stream_data.response_tool_calls)
|
|
745
746
|
|
|
746
747
|
else:
|
|
747
|
-
model_response = ModelResponse()
|
|
748
748
|
self._process_model_response(
|
|
749
749
|
messages=messages,
|
|
750
750
|
assistant_message=assistant_message,
|
|
@@ -784,6 +784,10 @@ class Model(ABC):
|
|
|
784
784
|
self.format_function_call_results(
|
|
785
785
|
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
786
786
|
)
|
|
787
|
+
elif model_response and model_response.extra is not None:
|
|
788
|
+
self.format_function_call_results(
|
|
789
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra
|
|
790
|
+
)
|
|
787
791
|
else:
|
|
788
792
|
self.format_function_call_results(messages=messages, function_call_results=function_call_results)
|
|
789
793
|
|
|
@@ -879,9 +883,10 @@ class Model(ABC):
|
|
|
879
883
|
# Create assistant message and stream data
|
|
880
884
|
assistant_message = Message(role=self.assistant_message_role)
|
|
881
885
|
stream_data = MessageData()
|
|
886
|
+
model_response = ModelResponse()
|
|
882
887
|
if stream_model_response:
|
|
883
888
|
# Generate response
|
|
884
|
-
async for
|
|
889
|
+
async for model_response in self.aprocess_response_stream(
|
|
885
890
|
messages=messages,
|
|
886
891
|
assistant_message=assistant_message,
|
|
887
892
|
stream_data=stream_data,
|
|
@@ -890,7 +895,7 @@ class Model(ABC):
|
|
|
890
895
|
tool_choice=tool_choice or self._tool_choice,
|
|
891
896
|
run_response=run_response,
|
|
892
897
|
):
|
|
893
|
-
yield
|
|
898
|
+
yield model_response
|
|
894
899
|
|
|
895
900
|
# Populate assistant message from stream data
|
|
896
901
|
if stream_data.response_content:
|
|
@@ -907,7 +912,6 @@ class Model(ABC):
|
|
|
907
912
|
assistant_message.tool_calls = self.parse_tool_calls(stream_data.response_tool_calls)
|
|
908
913
|
|
|
909
914
|
else:
|
|
910
|
-
model_response = ModelResponse()
|
|
911
915
|
await self._aprocess_model_response(
|
|
912
916
|
messages=messages,
|
|
913
917
|
assistant_message=assistant_message,
|
|
@@ -948,6 +952,10 @@ class Model(ABC):
|
|
|
948
952
|
self.format_function_call_results(
|
|
949
953
|
messages=messages, function_call_results=function_call_results, **stream_data.extra
|
|
950
954
|
)
|
|
955
|
+
elif model_response and model_response.extra is not None:
|
|
956
|
+
self.format_function_call_results(
|
|
957
|
+
messages=messages, function_call_results=function_call_results, **model_response.extra or {}
|
|
958
|
+
)
|
|
951
959
|
else:
|
|
952
960
|
self.format_function_call_results(messages=messages, function_call_results=function_call_results)
|
|
953
961
|
|
|
@@ -1573,30 +1581,35 @@ class Model(ABC):
|
|
|
1573
1581
|
*(self.arun_function_call(fc) for fc in function_calls_to_run), return_exceptions=True
|
|
1574
1582
|
)
|
|
1575
1583
|
|
|
1576
|
-
#
|
|
1584
|
+
# Separate async generators from other results for concurrent processing
|
|
1585
|
+
async_generator_results: List[Any] = []
|
|
1586
|
+
non_async_generator_results: List[Any] = []
|
|
1587
|
+
|
|
1577
1588
|
for result in results:
|
|
1578
|
-
# If result is an exception, skip processing it
|
|
1579
1589
|
if isinstance(result, BaseException):
|
|
1580
|
-
|
|
1581
|
-
|
|
1590
|
+
non_async_generator_results.append(result)
|
|
1591
|
+
continue
|
|
1582
1592
|
|
|
1583
|
-
# Unpack result
|
|
1584
1593
|
function_call_success, function_call_timer, function_call, function_execution_result = result
|
|
1585
1594
|
|
|
1586
|
-
|
|
1595
|
+
# Check if this result contains an async generator
|
|
1596
|
+
if isinstance(function_call.result, (AsyncGeneratorType, AsyncIterator)):
|
|
1597
|
+
async_generator_results.append(result)
|
|
1598
|
+
else:
|
|
1599
|
+
non_async_generator_results.append(result)
|
|
1587
1600
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
_handle_agent_exception(a_exc, additional_input)
|
|
1593
|
-
# Set function call success to False if an exception occurred
|
|
1594
|
-
function_call_success = False
|
|
1601
|
+
# Process async generators with real-time event streaming using asyncio.Queue
|
|
1602
|
+
async_generator_outputs: Dict[int, Tuple[Any, str, Optional[BaseException]]] = {}
|
|
1603
|
+
event_queue: asyncio.Queue = asyncio.Queue()
|
|
1604
|
+
active_generators_count: int = len(async_generator_results)
|
|
1595
1605
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1606
|
+
# Create background tasks for each async generator
|
|
1607
|
+
async def process_async_generator(result, generator_id):
|
|
1608
|
+
function_call_success, function_call_timer, function_call, function_execution_result = result
|
|
1609
|
+
function_call_output = ""
|
|
1610
|
+
|
|
1611
|
+
try:
|
|
1612
|
+
async for item in function_call.result:
|
|
1600
1613
|
# This function yields agent/team run events
|
|
1601
1614
|
if isinstance(item, tuple(get_args(RunOutputEvent))) or isinstance(
|
|
1602
1615
|
item, tuple(get_args(TeamRunOutputEvent))
|
|
@@ -1610,17 +1623,102 @@ class Model(ABC):
|
|
|
1610
1623
|
function_call_output += item.content or ""
|
|
1611
1624
|
|
|
1612
1625
|
if function_call.function.show_result:
|
|
1613
|
-
|
|
1626
|
+
await event_queue.put(ModelResponse(content=item.content))
|
|
1614
1627
|
continue
|
|
1615
1628
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1629
|
+
if isinstance(item, CustomEvent):
|
|
1630
|
+
function_call_output += str(item)
|
|
1631
|
+
|
|
1632
|
+
# Put the event into the queue to be yielded
|
|
1633
|
+
await event_queue.put(item)
|
|
1634
|
+
|
|
1635
|
+
# Yield custom events emitted by the tool
|
|
1618
1636
|
else:
|
|
1619
1637
|
function_call_output += str(item)
|
|
1620
1638
|
if function_call.function.show_result:
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1639
|
+
await event_queue.put(ModelResponse(content=str(item)))
|
|
1640
|
+
|
|
1641
|
+
# Store the final output for this generator
|
|
1642
|
+
async_generator_outputs[generator_id] = (result, function_call_output, None)
|
|
1643
|
+
|
|
1644
|
+
except Exception as e:
|
|
1645
|
+
# Store the exception
|
|
1646
|
+
async_generator_outputs[generator_id] = (result, "", e)
|
|
1647
|
+
|
|
1648
|
+
# Signal that this generator is done
|
|
1649
|
+
await event_queue.put(("GENERATOR_DONE", generator_id))
|
|
1650
|
+
|
|
1651
|
+
# Start all async generator tasks
|
|
1652
|
+
generator_tasks = []
|
|
1653
|
+
for i, result in enumerate(async_generator_results):
|
|
1654
|
+
task = asyncio.create_task(process_async_generator(result, i))
|
|
1655
|
+
generator_tasks.append(task)
|
|
1656
|
+
|
|
1657
|
+
# Stream events from the queue as they arrive
|
|
1658
|
+
completed_generators_count = 0
|
|
1659
|
+
while completed_generators_count < active_generators_count:
|
|
1660
|
+
try:
|
|
1661
|
+
event = await event_queue.get()
|
|
1662
|
+
|
|
1663
|
+
# Check if this is a completion signal
|
|
1664
|
+
if isinstance(event, tuple) and event[0] == "GENERATOR_DONE":
|
|
1665
|
+
completed_generators_count += 1
|
|
1666
|
+
continue
|
|
1667
|
+
|
|
1668
|
+
# Yield the actual event
|
|
1669
|
+
yield event
|
|
1670
|
+
|
|
1671
|
+
except Exception as e:
|
|
1672
|
+
log_error(f"Error processing async generator event: {e}")
|
|
1673
|
+
break
|
|
1674
|
+
|
|
1675
|
+
# Now process all results (non-async generators and completed async generators)
|
|
1676
|
+
for i, original_result in enumerate(results):
|
|
1677
|
+
# If result is an exception, skip processing it
|
|
1678
|
+
if isinstance(original_result, BaseException):
|
|
1679
|
+
log_error(f"Error during function call: {original_result}")
|
|
1680
|
+
raise original_result
|
|
1681
|
+
|
|
1682
|
+
# Unpack result
|
|
1683
|
+
function_call_success, function_call_timer, function_call, function_execution_result = original_result
|
|
1684
|
+
|
|
1685
|
+
# Check if this was an async generator that was already processed
|
|
1686
|
+
async_function_call_output = None
|
|
1687
|
+
if isinstance(function_call.result, (AsyncGeneratorType, collections.abc.AsyncIterator)):
|
|
1688
|
+
# Find the corresponding processed result
|
|
1689
|
+
async_gen_index = 0
|
|
1690
|
+
for j, result in enumerate(results[: i + 1]):
|
|
1691
|
+
if not isinstance(result, BaseException):
|
|
1692
|
+
_, _, fc, _ = result
|
|
1693
|
+
if isinstance(fc.result, (AsyncGeneratorType, collections.abc.AsyncIterator)):
|
|
1694
|
+
if j == i: # This is our async generator
|
|
1695
|
+
if async_gen_index in async_generator_outputs:
|
|
1696
|
+
_, async_function_call_output, error = async_generator_outputs[async_gen_index]
|
|
1697
|
+
if error:
|
|
1698
|
+
log_error(f"Error in async generator: {error}")
|
|
1699
|
+
raise error
|
|
1700
|
+
break
|
|
1701
|
+
async_gen_index += 1
|
|
1702
|
+
|
|
1703
|
+
updated_session_state = function_execution_result.updated_session_state
|
|
1704
|
+
|
|
1705
|
+
# Handle AgentRunException
|
|
1706
|
+
if isinstance(function_call_success, AgentRunException):
|
|
1707
|
+
a_exc = function_call_success
|
|
1708
|
+
# Update additional messages from function call
|
|
1709
|
+
_handle_agent_exception(a_exc, additional_input)
|
|
1710
|
+
# Set function call success to False if an exception occurred
|
|
1711
|
+
function_call_success = False
|
|
1712
|
+
|
|
1713
|
+
# Process function call output
|
|
1714
|
+
function_call_output: str = ""
|
|
1715
|
+
|
|
1716
|
+
# Check if this was an async generator that was already processed
|
|
1717
|
+
if async_function_call_output is not None:
|
|
1718
|
+
function_call_output = async_function_call_output
|
|
1719
|
+
# Events from async generators were already yielded in real-time above
|
|
1720
|
+
elif isinstance(function_call.result, (GeneratorType, collections.abc.Iterator)):
|
|
1721
|
+
for item in function_call.result:
|
|
1624
1722
|
# This function yields agent/team run events
|
|
1625
1723
|
if isinstance(item, tuple(get_args(RunOutputEvent))) or isinstance(
|
|
1626
1724
|
item, tuple(get_args(TeamRunOutputEvent))
|
|
@@ -1637,13 +1735,8 @@ class Model(ABC):
|
|
|
1637
1735
|
yield ModelResponse(content=item.content)
|
|
1638
1736
|
continue
|
|
1639
1737
|
|
|
1640
|
-
if isinstance(item, CustomEvent):
|
|
1641
|
-
function_call_output += str(item)
|
|
1642
|
-
|
|
1643
1738
|
# Yield the event itself to bubble it up
|
|
1644
1739
|
yield item
|
|
1645
|
-
|
|
1646
|
-
# Yield custom events emitted by the tool
|
|
1647
1740
|
else:
|
|
1648
1741
|
function_call_output += str(item)
|
|
1649
1742
|
if function_call.function.show_result:
|
agno/models/cerebras/cerebras.py
CHANGED
|
@@ -25,6 +25,7 @@ class CerebrasOpenAI(OpenAILike):
|
|
|
25
25
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
26
26
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
27
27
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
28
|
+
**kwargs: Any,
|
|
28
29
|
) -> Dict[str, Any]:
|
|
29
30
|
"""
|
|
30
31
|
Returns keyword arguments for API requests.
|
|
@@ -73,6 +73,7 @@ class DashScope(OpenAILike):
|
|
|
73
73
|
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
74
74
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
75
75
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
76
|
+
**kwargs: Any,
|
|
76
77
|
) -> Dict[str, Any]:
|
|
77
78
|
params = super().get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice)
|
|
78
79
|
|