agno 2.3.9__py3-none-any.whl → 2.3.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.
Files changed (43) hide show
  1. agno/agent/agent.py +0 -12
  2. agno/db/base.py +5 -5
  3. agno/db/dynamo/dynamo.py +2 -2
  4. agno/db/firestore/firestore.py +2 -2
  5. agno/db/gcs_json/gcs_json_db.py +2 -2
  6. agno/db/in_memory/in_memory_db.py +2 -2
  7. agno/db/json/json_db.py +2 -2
  8. agno/db/mongo/async_mongo.py +171 -69
  9. agno/db/mongo/mongo.py +171 -77
  10. agno/db/mysql/async_mysql.py +93 -69
  11. agno/db/mysql/mysql.py +93 -68
  12. agno/db/postgres/async_postgres.py +104 -78
  13. agno/db/postgres/postgres.py +97 -69
  14. agno/db/redis/redis.py +2 -2
  15. agno/db/singlestore/singlestore.py +91 -66
  16. agno/db/sqlite/async_sqlite.py +102 -79
  17. agno/db/sqlite/sqlite.py +97 -69
  18. agno/db/surrealdb/surrealdb.py +2 -2
  19. agno/eval/accuracy.py +11 -8
  20. agno/eval/agent_as_judge.py +9 -8
  21. agno/knowledge/chunking/fixed.py +4 -1
  22. agno/knowledge/embedder/openai.py +1 -1
  23. agno/knowledge/knowledge.py +22 -4
  24. agno/knowledge/utils.py +52 -7
  25. agno/models/base.py +34 -1
  26. agno/models/google/gemini.py +69 -40
  27. agno/models/message.py +3 -0
  28. agno/models/openai/chat.py +21 -0
  29. agno/os/routers/evals/utils.py +15 -37
  30. agno/os/routers/knowledge/knowledge.py +21 -9
  31. agno/team/team.py +14 -8
  32. agno/tools/function.py +37 -23
  33. agno/tools/shopify.py +1519 -0
  34. agno/tools/spotify.py +2 -5
  35. agno/tracing/exporter.py +2 -2
  36. agno/vectordb/base.py +15 -2
  37. agno/vectordb/pgvector/pgvector.py +8 -8
  38. agno/workflow/parallel.py +2 -0
  39. {agno-2.3.9.dist-info → agno-2.3.11.dist-info}/METADATA +1 -1
  40. {agno-2.3.9.dist-info → agno-2.3.11.dist-info}/RECORD +43 -42
  41. {agno-2.3.9.dist-info → agno-2.3.11.dist-info}/WHEEL +0 -0
  42. {agno-2.3.9.dist-info → agno-2.3.11.dist-info}/licenses/LICENSE +0 -0
  43. {agno-2.3.9.dist-info → agno-2.3.11.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -1785,9 +1785,6 @@ class Agent:
1785
1785
  if stream_events is None:
1786
1786
  stream_events = False if self.stream_events is None else self.stream_events
1787
1787
 
1788
- self.stream = self.stream or stream
1789
- self.stream_events = self.stream_events or stream_events
1790
-
1791
1788
  # Prepare arguments for the model
1792
1789
  response_format = (
1793
1790
  self._get_response_format(run_context=run_context) if self.parser_model is None else None
@@ -2711,9 +2708,6 @@ class Agent:
2711
2708
  if stream_events is None:
2712
2709
  stream_events = False if self.stream_events is None else self.stream_events
2713
2710
 
2714
- self.stream = self.stream or stream
2715
- self.stream_events = self.stream_events or stream_events
2716
-
2717
2711
  self.model = cast(Model, self.model)
2718
2712
 
2719
2713
  # Get knowledge filters
@@ -3020,9 +3014,6 @@ class Agent:
3020
3014
  if stream is False:
3021
3015
  stream_events = False
3022
3016
 
3023
- self.stream = self.stream or stream
3024
- self.stream_events = self.stream_events or stream_events
3025
-
3026
3017
  # Run can be continued from previous run response or from passed run_response context
3027
3018
  if run_response is not None:
3028
3019
  # The run is continued from a provided run_response. This contains the updated tools.
@@ -3589,9 +3580,6 @@ class Agent:
3589
3580
  if stream is False:
3590
3581
  stream_events = False
3591
3582
 
3592
- self.stream = self.stream or stream
3593
- self.stream_events = self.stream_events or stream_events
3594
-
3595
3583
  # Get knowledge filters
3596
3584
  knowledge_filters = knowledge_filters
3597
3585
  if self.knowledge_filters or knowledge_filters:
agno/db/base.py CHANGED
@@ -308,8 +308,8 @@ class BaseDb(ABC):
308
308
 
309
309
  # --- Traces ---
310
310
  @abstractmethod
311
- def create_trace(self, trace: "Trace") -> None:
312
- """Create a single trace record in the database.
311
+ def upsert_trace(self, trace: "Trace") -> None:
312
+ """Create or update a single trace record in the database.
313
313
 
314
314
  Args:
315
315
  trace: The Trace object to store (one per trace_id).
@@ -759,11 +759,11 @@ class AsyncBaseDb(ABC):
759
759
 
760
760
  # --- Traces ---
761
761
  @abstractmethod
762
- async def create_trace(self, trace) -> None:
763
- """Create a single trace record in the database.
762
+ async def upsert_trace(self, trace) -> None:
763
+ """Create or update a single trace record in the database.
764
764
 
765
765
  Args:
766
- trace: The Trace object to store (one per trace_id).
766
+ trace: The Trace object to update (one per trace_id).
767
767
  """
768
768
  raise NotImplementedError
769
769
 
agno/db/dynamo/dynamo.py CHANGED
@@ -2076,8 +2076,8 @@ class DynamoDb(BaseDb):
2076
2076
  raise e
2077
2077
 
2078
2078
  # --- Traces ---
2079
- def create_trace(self, trace: "Trace") -> None:
2080
- """Create a single trace record in the database.
2079
+ def upsert_trace(self, trace: "Trace") -> None:
2080
+ """Create or update a single trace record in the database.
2081
2081
 
2082
2082
  Args:
2083
2083
  trace: The Trace object to store (one per trace_id).
@@ -1841,8 +1841,8 @@ class FirestoreDb(BaseDb):
1841
1841
  raise e
1842
1842
 
1843
1843
  # --- Traces ---
1844
- def create_trace(self, trace: "Trace") -> None:
1845
- """Create a single trace record in the database.
1844
+ def upsert_trace(self, trace: "Trace") -> None:
1845
+ """Create or update a single trace record in the database.
1846
1846
 
1847
1847
  Args:
1848
1848
  trace: The Trace object to store (one per trace_id).
@@ -1355,8 +1355,8 @@ class GcsJsonDb(BaseDb):
1355
1355
  raise e
1356
1356
 
1357
1357
  # --- Traces ---
1358
- def create_trace(self, trace: "Trace") -> None:
1359
- """Create a single trace record in the database.
1358
+ def upsert_trace(self, trace: "Trace") -> None:
1359
+ """Create or update a single trace record in the database.
1360
1360
 
1361
1361
  Args:
1362
1362
  trace: The Trace object to store (one per trace_id).
@@ -1173,8 +1173,8 @@ class InMemoryDb(BaseDb):
1173
1173
  raise e
1174
1174
 
1175
1175
  # --- Traces ---
1176
- def create_trace(self, trace: "Trace") -> None:
1177
- """Create a single trace record in the database.
1176
+ def upsert_trace(self, trace: "Trace") -> None:
1177
+ """Create or update a single trace record in the database.
1178
1178
 
1179
1179
  Args:
1180
1180
  trace: The Trace object to store (one per trace_id).
agno/db/json/json_db.py CHANGED
@@ -1349,8 +1349,8 @@ class JsonDb(BaseDb):
1349
1349
  raise e
1350
1350
 
1351
1351
  # --- Traces ---
1352
- def create_trace(self, trace: "Trace") -> None:
1353
- """Create a single trace record in the database.
1352
+ def upsert_trace(self, trace: "Trace") -> None:
1353
+ """Create or update a single trace record in the database.
1354
1354
 
1355
1355
  Args:
1356
1356
  trace: The Trace object to store (one per trace_id).
@@ -2191,8 +2191,45 @@ class AsyncMongoDb(AsyncBaseDb):
2191
2191
  raise e
2192
2192
 
2193
2193
  # --- Traces ---
2194
- async def create_trace(self, trace: "Trace") -> None:
2195
- """Create a single trace record in the database.
2194
+ def _get_component_level(
2195
+ self, workflow_id: Optional[str], team_id: Optional[str], agent_id: Optional[str], name: str
2196
+ ) -> int:
2197
+ """Get the component level for a trace based on its context.
2198
+
2199
+ Component levels (higher = more important):
2200
+ - 3: Workflow root (.run or .arun with workflow_id)
2201
+ - 2: Team root (.run or .arun with team_id)
2202
+ - 1: Agent root (.run or .arun with agent_id)
2203
+ - 0: Child span (not a root)
2204
+
2205
+ Args:
2206
+ workflow_id: The workflow ID of the trace.
2207
+ team_id: The team ID of the trace.
2208
+ agent_id: The agent ID of the trace.
2209
+ name: The name of the trace.
2210
+
2211
+ Returns:
2212
+ int: The component level (0-3).
2213
+ """
2214
+ # Check if name indicates a root span
2215
+ is_root_name = ".run" in name or ".arun" in name
2216
+
2217
+ if not is_root_name:
2218
+ return 0 # Child span (not a root)
2219
+ elif workflow_id:
2220
+ return 3 # Workflow root
2221
+ elif team_id:
2222
+ return 2 # Team root
2223
+ elif agent_id:
2224
+ return 1 # Agent root
2225
+ else:
2226
+ return 0 # Unknown
2227
+
2228
+ async def upsert_trace(self, trace: "Trace") -> None:
2229
+ """Create or update a single trace record in the database.
2230
+
2231
+ Uses MongoDB's update_one with upsert=True and aggregation pipeline
2232
+ to handle concurrent inserts atomically and avoid race conditions.
2196
2233
 
2197
2234
  Args:
2198
2235
  trace: The Trace object to store (one per trace_id).
@@ -2202,75 +2239,140 @@ class AsyncMongoDb(AsyncBaseDb):
2202
2239
  if collection is None:
2203
2240
  return
2204
2241
 
2205
- # Check if trace already exists
2206
- existing = await collection.find_one({"trace_id": trace.trace_id})
2207
-
2208
- if existing:
2209
- # workflow (level 3) > team (level 2) > agent (level 1) > child/unknown (level 0)
2210
- def get_component_level(
2211
- workflow_id: Optional[str], team_id: Optional[str], agent_id: Optional[str], name: str
2212
- ) -> int:
2213
- # Check if name indicates a root span
2214
- is_root_name = ".run" in name or ".arun" in name
2215
-
2216
- if not is_root_name:
2217
- return 0 # Child span (not a root)
2218
- elif workflow_id:
2219
- return 3 # Workflow root
2220
- elif team_id:
2221
- return 2 # Team root
2222
- elif agent_id:
2223
- return 1 # Agent root
2224
- else:
2225
- return 0 # Unknown
2242
+ trace_dict = trace.to_dict()
2243
+ trace_dict.pop("total_spans", None)
2244
+ trace_dict.pop("error_count", None)
2226
2245
 
2227
- existing_level = get_component_level(
2228
- existing.get("workflow_id"),
2229
- existing.get("team_id"),
2230
- existing.get("agent_id"),
2231
- existing.get("name", ""),
2232
- )
2233
- new_level = get_component_level(trace.workflow_id, trace.team_id, trace.agent_id, trace.name)
2234
-
2235
- # Only update name if new trace is from a higher or equal level
2236
- should_update_name = new_level > existing_level
2237
-
2238
- # Parse existing start_time to calculate correct duration
2239
- existing_start_time_str = existing.get("start_time")
2240
- if isinstance(existing_start_time_str, str):
2241
- existing_start_time = datetime.fromisoformat(existing_start_time_str.replace("Z", "+00:00"))
2242
- else:
2243
- existing_start_time = trace.start_time
2246
+ # Calculate the component level for the new trace
2247
+ new_level = self._get_component_level(trace.workflow_id, trace.team_id, trace.agent_id, trace.name)
2244
2248
 
2245
- recalculated_duration_ms = int((trace.end_time - existing_start_time).total_seconds() * 1000)
2246
-
2247
- update_values: Dict[str, Any] = {
2248
- "end_time": trace.end_time.isoformat(),
2249
- "duration_ms": recalculated_duration_ms,
2250
- "status": trace.status,
2251
- "name": trace.name if should_update_name else existing.get("name"),
2252
- }
2249
+ # Use MongoDB aggregation pipeline update for atomic upsert
2250
+ # This allows conditional logic within a single atomic operation
2251
+ pipeline: List[Dict[str, Any]] = [
2252
+ {
2253
+ "$set": {
2254
+ # Always update these fields
2255
+ "status": trace.status,
2256
+ "created_at": {"$ifNull": ["$created_at", trace_dict.get("created_at")]},
2257
+ # Use $min for start_time (keep earliest)
2258
+ "start_time": {
2259
+ "$cond": {
2260
+ "if": {"$eq": [{"$type": "$start_time"}, "missing"]},
2261
+ "then": trace_dict.get("start_time"),
2262
+ "else": {"$min": ["$start_time", trace_dict.get("start_time")]},
2263
+ }
2264
+ },
2265
+ # Use $max for end_time (keep latest)
2266
+ "end_time": {
2267
+ "$cond": {
2268
+ "if": {"$eq": [{"$type": "$end_time"}, "missing"]},
2269
+ "then": trace_dict.get("end_time"),
2270
+ "else": {"$max": ["$end_time", trace_dict.get("end_time")]},
2271
+ }
2272
+ },
2273
+ # Preserve existing non-null context values using $ifNull
2274
+ "run_id": {"$ifNull": [trace.run_id, "$run_id"]},
2275
+ "session_id": {"$ifNull": [trace.session_id, "$session_id"]},
2276
+ "user_id": {"$ifNull": [trace.user_id, "$user_id"]},
2277
+ "agent_id": {"$ifNull": [trace.agent_id, "$agent_id"]},
2278
+ "team_id": {"$ifNull": [trace.team_id, "$team_id"]},
2279
+ "workflow_id": {"$ifNull": [trace.workflow_id, "$workflow_id"]},
2280
+ }
2281
+ },
2282
+ {
2283
+ "$set": {
2284
+ # Calculate duration_ms from the (potentially updated) start_time and end_time
2285
+ # MongoDB stores dates as strings in ISO format, so we need to parse them
2286
+ "duration_ms": {
2287
+ "$cond": {
2288
+ "if": {
2289
+ "$and": [
2290
+ {"$ne": [{"$type": "$start_time"}, "missing"]},
2291
+ {"$ne": [{"$type": "$end_time"}, "missing"]},
2292
+ ]
2293
+ },
2294
+ "then": {
2295
+ "$subtract": [
2296
+ {"$toLong": {"$toDate": "$end_time"}},
2297
+ {"$toLong": {"$toDate": "$start_time"}},
2298
+ ]
2299
+ },
2300
+ "else": trace_dict.get("duration_ms", 0),
2301
+ }
2302
+ },
2303
+ # Update name based on component level priority
2304
+ # Only update if new trace is from a higher-level component
2305
+ "name": {
2306
+ "$cond": {
2307
+ "if": {"$eq": [{"$type": "$name"}, "missing"]},
2308
+ "then": trace.name,
2309
+ "else": {
2310
+ "$cond": {
2311
+ "if": {
2312
+ "$gt": [
2313
+ new_level,
2314
+ {
2315
+ "$switch": {
2316
+ "branches": [
2317
+ # Check if existing name is a root span
2318
+ {
2319
+ "case": {
2320
+ "$not": {
2321
+ "$or": [
2322
+ {
2323
+ "$regexMatch": {
2324
+ "input": {"$ifNull": ["$name", ""]},
2325
+ "regex": "\\.run",
2326
+ }
2327
+ },
2328
+ {
2329
+ "$regexMatch": {
2330
+ "input": {"$ifNull": ["$name", ""]},
2331
+ "regex": "\\.arun",
2332
+ }
2333
+ },
2334
+ ]
2335
+ }
2336
+ },
2337
+ "then": 0,
2338
+ },
2339
+ # Workflow root (level 3)
2340
+ {
2341
+ "case": {"$ne": ["$workflow_id", None]},
2342
+ "then": 3,
2343
+ },
2344
+ # Team root (level 2)
2345
+ {
2346
+ "case": {"$ne": ["$team_id", None]},
2347
+ "then": 2,
2348
+ },
2349
+ # Agent root (level 1)
2350
+ {
2351
+ "case": {"$ne": ["$agent_id", None]},
2352
+ "then": 1,
2353
+ },
2354
+ ],
2355
+ "default": 0,
2356
+ }
2357
+ },
2358
+ ]
2359
+ },
2360
+ "then": trace.name,
2361
+ "else": "$name",
2362
+ }
2363
+ },
2364
+ }
2365
+ },
2366
+ }
2367
+ },
2368
+ ]
2253
2369
 
2254
- # Update context fields ONLY if new value is not None (preserve non-null values)
2255
- if trace.run_id is not None:
2256
- update_values["run_id"] = trace.run_id
2257
- if trace.session_id is not None:
2258
- update_values["session_id"] = trace.session_id
2259
- if trace.user_id is not None:
2260
- update_values["user_id"] = trace.user_id
2261
- if trace.agent_id is not None:
2262
- update_values["agent_id"] = trace.agent_id
2263
- if trace.team_id is not None:
2264
- update_values["team_id"] = trace.team_id
2265
- if trace.workflow_id is not None:
2266
- update_values["workflow_id"] = trace.workflow_id
2267
-
2268
- await collection.update_one({"trace_id": trace.trace_id}, {"$set": update_values})
2269
- else:
2270
- trace_dict = trace.to_dict()
2271
- trace_dict.pop("total_spans", None)
2272
- trace_dict.pop("error_count", None)
2273
- await collection.insert_one(trace_dict)
2370
+ # Perform atomic upsert using aggregation pipeline
2371
+ await collection.update_one(
2372
+ {"trace_id": trace.trace_id},
2373
+ pipeline,
2374
+ upsert=True,
2375
+ )
2274
2376
 
2275
2377
  except Exception as e:
2276
2378
  log_error(f"Error creating trace: {e}")
@@ -2655,4 +2757,4 @@ class AsyncMongoDb(AsyncBaseDb):
2655
2757
 
2656
2758
  except Exception as e:
2657
2759
  log_error(f"Error getting spans: {e}")
2658
- return []
2760
+ return []