agno 2.3.10__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.
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 []
agno/db/mongo/mongo.py CHANGED
@@ -2028,8 +2028,45 @@ class MongoDb(BaseDb):
2028
2028
  log_info(f"Migrated {len(memories)} memories to collection: {self.memory_table_name}")
2029
2029
 
2030
2030
  # --- Traces ---
2031
- def create_trace(self, trace: "Trace") -> None:
2032
- """Create a single trace record in the database.
2031
+ def _get_component_level(
2032
+ self, workflow_id: Optional[str], team_id: Optional[str], agent_id: Optional[str], name: str
2033
+ ) -> int:
2034
+ """Get the component level for a trace based on its context.
2035
+
2036
+ Component levels (higher = more important):
2037
+ - 3: Workflow root (.run or .arun with workflow_id)
2038
+ - 2: Team root (.run or .arun with team_id)
2039
+ - 1: Agent root (.run or .arun with agent_id)
2040
+ - 0: Child span (not a root)
2041
+
2042
+ Args:
2043
+ workflow_id: The workflow ID of the trace.
2044
+ team_id: The team ID of the trace.
2045
+ agent_id: The agent ID of the trace.
2046
+ name: The name of the trace.
2047
+
2048
+ Returns:
2049
+ int: The component level (0-3).
2050
+ """
2051
+ # Check if name indicates a root span
2052
+ is_root_name = ".run" in name or ".arun" in name
2053
+
2054
+ if not is_root_name:
2055
+ return 0 # Child span (not a root)
2056
+ elif workflow_id:
2057
+ return 3 # Workflow root
2058
+ elif team_id:
2059
+ return 2 # Team root
2060
+ elif agent_id:
2061
+ return 1 # Agent root
2062
+ else:
2063
+ return 0 # Unknown
2064
+
2065
+ def upsert_trace(self, trace: "Trace") -> None:
2066
+ """Create or update a single trace record in the database.
2067
+
2068
+ Uses MongoDB's update_one with upsert=True and aggregation pipeline
2069
+ to handle concurrent inserts atomically and avoid race conditions.
2033
2070
 
2034
2071
  Args:
2035
2072
  trace: The Trace object to store (one per trace_id).
@@ -2039,83 +2076,140 @@ class MongoDb(BaseDb):
2039
2076
  if collection is None:
2040
2077
  return
2041
2078
 
2042
- # Check if trace already exists
2043
- existing = collection.find_one({"trace_id": trace.trace_id})
2044
-
2045
- if existing:
2046
- # workflow (level 3) > team (level 2) > agent (level 1) > child/unknown (level 0)
2047
- def get_component_level(
2048
- workflow_id: Optional[str], team_id: Optional[str], agent_id: Optional[str], name: str
2049
- ) -> int:
2050
- # Check if name indicates a root span
2051
- is_root_name = ".run" in name or ".arun" in name
2052
-
2053
- if not is_root_name:
2054
- return 0 # Child span (not a root)
2055
- elif workflow_id:
2056
- return 3 # Workflow root
2057
- elif team_id:
2058
- return 2 # Team root
2059
- elif agent_id:
2060
- return 1 # Agent root
2061
- else:
2062
- return 0 # Unknown
2063
-
2064
- existing_level = get_component_level(
2065
- existing.get("workflow_id"),
2066
- existing.get("team_id"),
2067
- existing.get("agent_id"),
2068
- existing.get("name", ""),
2069
- )
2070
- new_level = get_component_level(trace.workflow_id, trace.team_id, trace.agent_id, trace.name)
2071
-
2072
- # Only update name if new trace is from a higher or equal level
2073
- should_update_name = new_level > existing_level
2074
-
2075
- # Parse existing start_time to calculate correct duration
2076
- existing_start_time_str = existing.get("start_time")
2077
- if isinstance(existing_start_time_str, str):
2078
- existing_start_time = datetime.fromisoformat(existing_start_time_str.replace("Z", "+00:00"))
2079
- else:
2080
- existing_start_time = trace.start_time
2079
+ trace_dict = trace.to_dict()
2080
+ trace_dict.pop("total_spans", None)
2081
+ trace_dict.pop("error_count", None)
2081
2082
 
2082
- recalculated_duration_ms = int((trace.end_time - existing_start_time).total_seconds() * 1000)
2083
+ # Calculate the component level for the new trace
2084
+ new_level = self._get_component_level(trace.workflow_id, trace.team_id, trace.agent_id, trace.name)
2083
2085
 
2084
- update_values: Dict[str, Any] = {
2085
- "end_time": trace.end_time.isoformat(),
2086
- "duration_ms": recalculated_duration_ms,
2087
- "status": trace.status,
2088
- "name": trace.name if should_update_name else existing.get("name"),
2089
- }
2090
-
2091
- # Update context fields ONLY if new value is not None (preserve non-null values)
2092
- if trace.run_id is not None:
2093
- update_values["run_id"] = trace.run_id
2094
- if trace.session_id is not None:
2095
- update_values["session_id"] = trace.session_id
2096
- if trace.user_id is not None:
2097
- update_values["user_id"] = trace.user_id
2098
- if trace.agent_id is not None:
2099
- update_values["agent_id"] = trace.agent_id
2100
- if trace.team_id is not None:
2101
- update_values["team_id"] = trace.team_id
2102
- if trace.workflow_id is not None:
2103
- update_values["workflow_id"] = trace.workflow_id
2104
-
2105
- log_debug(
2106
- f" Updating trace with context: run_id={update_values.get('run_id', 'unchanged')}, "
2107
- f"session_id={update_values.get('session_id', 'unchanged')}, "
2108
- f"user_id={update_values.get('user_id', 'unchanged')}, "
2109
- f"agent_id={update_values.get('agent_id', 'unchanged')}, "
2110
- f"team_id={update_values.get('team_id', 'unchanged')}, "
2111
- )
2086
+ # Use MongoDB aggregation pipeline update for atomic upsert
2087
+ # This allows conditional logic within a single atomic operation
2088
+ pipeline: List[Dict[str, Any]] = [
2089
+ {
2090
+ "$set": {
2091
+ # Always update these fields
2092
+ "status": trace.status,
2093
+ "created_at": {"$ifNull": ["$created_at", trace_dict.get("created_at")]},
2094
+ # Use $min for start_time (keep earliest)
2095
+ "start_time": {
2096
+ "$cond": {
2097
+ "if": {"$eq": [{"$type": "$start_time"}, "missing"]},
2098
+ "then": trace_dict.get("start_time"),
2099
+ "else": {"$min": ["$start_time", trace_dict.get("start_time")]},
2100
+ }
2101
+ },
2102
+ # Use $max for end_time (keep latest)
2103
+ "end_time": {
2104
+ "$cond": {
2105
+ "if": {"$eq": [{"$type": "$end_time"}, "missing"]},
2106
+ "then": trace_dict.get("end_time"),
2107
+ "else": {"$max": ["$end_time", trace_dict.get("end_time")]},
2108
+ }
2109
+ },
2110
+ # Preserve existing non-null context values using $ifNull
2111
+ "run_id": {"$ifNull": [trace.run_id, "$run_id"]},
2112
+ "session_id": {"$ifNull": [trace.session_id, "$session_id"]},
2113
+ "user_id": {"$ifNull": [trace.user_id, "$user_id"]},
2114
+ "agent_id": {"$ifNull": [trace.agent_id, "$agent_id"]},
2115
+ "team_id": {"$ifNull": [trace.team_id, "$team_id"]},
2116
+ "workflow_id": {"$ifNull": [trace.workflow_id, "$workflow_id"]},
2117
+ }
2118
+ },
2119
+ {
2120
+ "$set": {
2121
+ # Calculate duration_ms from the (potentially updated) start_time and end_time
2122
+ # MongoDB stores dates as strings in ISO format, so we need to parse them
2123
+ "duration_ms": {
2124
+ "$cond": {
2125
+ "if": {
2126
+ "$and": [
2127
+ {"$ne": [{"$type": "$start_time"}, "missing"]},
2128
+ {"$ne": [{"$type": "$end_time"}, "missing"]},
2129
+ ]
2130
+ },
2131
+ "then": {
2132
+ "$subtract": [
2133
+ {"$toLong": {"$toDate": "$end_time"}},
2134
+ {"$toLong": {"$toDate": "$start_time"}},
2135
+ ]
2136
+ },
2137
+ "else": trace_dict.get("duration_ms", 0),
2138
+ }
2139
+ },
2140
+ # Update name based on component level priority
2141
+ # Only update if new trace is from a higher-level component
2142
+ "name": {
2143
+ "$cond": {
2144
+ "if": {"$eq": [{"$type": "$name"}, "missing"]},
2145
+ "then": trace.name,
2146
+ "else": {
2147
+ "$cond": {
2148
+ "if": {
2149
+ "$gt": [
2150
+ new_level,
2151
+ {
2152
+ "$switch": {
2153
+ "branches": [
2154
+ # Check if existing name is a root span
2155
+ {
2156
+ "case": {
2157
+ "$not": {
2158
+ "$or": [
2159
+ {
2160
+ "$regexMatch": {
2161
+ "input": {"$ifNull": ["$name", ""]},
2162
+ "regex": "\\.run",
2163
+ }
2164
+ },
2165
+ {
2166
+ "$regexMatch": {
2167
+ "input": {"$ifNull": ["$name", ""]},
2168
+ "regex": "\\.arun",
2169
+ }
2170
+ },
2171
+ ]
2172
+ }
2173
+ },
2174
+ "then": 0,
2175
+ },
2176
+ # Workflow root (level 3)
2177
+ {
2178
+ "case": {"$ne": ["$workflow_id", None]},
2179
+ "then": 3,
2180
+ },
2181
+ # Team root (level 2)
2182
+ {
2183
+ "case": {"$ne": ["$team_id", None]},
2184
+ "then": 2,
2185
+ },
2186
+ # Agent root (level 1)
2187
+ {
2188
+ "case": {"$ne": ["$agent_id", None]},
2189
+ "then": 1,
2190
+ },
2191
+ ],
2192
+ "default": 0,
2193
+ }
2194
+ },
2195
+ ]
2196
+ },
2197
+ "then": trace.name,
2198
+ "else": "$name",
2199
+ }
2200
+ },
2201
+ }
2202
+ },
2203
+ }
2204
+ },
2205
+ ]
2112
2206
 
2113
- collection.update_one({"trace_id": trace.trace_id}, {"$set": update_values})
2114
- else:
2115
- trace_dict = trace.to_dict()
2116
- trace_dict.pop("total_spans", None)
2117
- trace_dict.pop("error_count", None)
2118
- collection.insert_one(trace_dict)
2207
+ # Perform atomic upsert using aggregation pipeline
2208
+ collection.update_one(
2209
+ {"trace_id": trace.trace_id},
2210
+ pipeline,
2211
+ upsert=True,
2212
+ )
2119
2213
 
2120
2214
  except Exception as e:
2121
2215
  log_error(f"Error creating trace: {e}")
@@ -2500,4 +2594,4 @@ class MongoDb(BaseDb):
2500
2594
 
2501
2595
  except Exception as e:
2502
2596
  log_error(f"Error getting spans: {e}")
2503
- return []
2597
+ return []