agno 2.0.4__py3-none-any.whl → 2.0.5__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 +74 -85
- agno/db/dynamo/dynamo.py +2 -2
- agno/db/firestore/firestore.py +3 -2
- agno/db/gcs_json/gcs_json_db.py +2 -2
- agno/db/json/json_db.py +2 -2
- agno/db/migrations/v1_to_v2.py +191 -23
- agno/db/mongo/mongo.py +61 -2
- agno/db/mysql/mysql.py +5 -5
- agno/db/mysql/schemas.py +27 -27
- agno/db/postgres/postgres.py +5 -5
- agno/db/redis/redis.py +2 -2
- agno/db/singlestore/singlestore.py +2 -2
- agno/db/sqlite/sqlite.py +6 -5
- agno/db/utils.py +0 -14
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/knowledge.py +7 -7
- agno/knowledge/reader/reader_factory.py +7 -3
- agno/knowledge/reader/web_search_reader.py +12 -6
- agno/models/message.py +109 -0
- agno/models/openai/responses.py +6 -0
- agno/os/app.py +162 -42
- agno/os/interfaces/agui/utils.py +98 -134
- agno/os/routers/health.py +0 -1
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +2 -2
- agno/os/schema.py +21 -0
- agno/os/utils.py +0 -8
- agno/run/agent.py +3 -3
- agno/run/team.py +3 -3
- agno/team/team.py +33 -38
- agno/tools/duckduckgo.py +15 -11
- agno/tools/googlesearch.py +1 -1
- agno/utils/string.py +32 -0
- agno/utils/tools.py +1 -1
- agno/workflow/step.py +4 -3
- {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/METADATA +6 -5
- {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/RECORD +40 -40
- agno/knowledge/reader/url_reader.py +0 -128
- {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/WHEEL +0 -0
- {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.4.dist-info → agno-2.0.5.dist-info}/top_level.txt +0 -0
agno/os/interfaces/agui/utils.py
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import uuid
|
|
5
|
-
from collections import deque
|
|
6
5
|
from collections.abc import Iterator
|
|
7
6
|
from dataclasses import dataclass
|
|
8
|
-
from typing import AsyncIterator,
|
|
7
|
+
from typing import AsyncIterator, List, Set, Tuple, Union
|
|
9
8
|
|
|
10
9
|
from ag_ui.core import (
|
|
11
10
|
BaseEvent,
|
|
@@ -34,39 +33,22 @@ from agno.utils.message import get_text_from_message
|
|
|
34
33
|
class EventBuffer:
|
|
35
34
|
"""Buffer to manage event ordering constraints, relevant when mapping Agno responses to AG-UI events."""
|
|
36
35
|
|
|
37
|
-
buffer: Deque[BaseEvent]
|
|
38
|
-
blocking_tool_call_id: Optional[str] # The tool call that's currently blocking the buffer
|
|
39
36
|
active_tool_call_ids: Set[str] # All currently active tool calls
|
|
40
37
|
ended_tool_call_ids: Set[str] # All tool calls that have ended
|
|
41
38
|
|
|
42
39
|
def __init__(self):
|
|
43
|
-
self.buffer = deque()
|
|
44
|
-
self.blocking_tool_call_id = None
|
|
45
40
|
self.active_tool_call_ids = set()
|
|
46
41
|
self.ended_tool_call_ids = set()
|
|
47
42
|
|
|
48
|
-
def is_blocked(self) -> bool:
|
|
49
|
-
"""Check if the buffer is currently blocked by an active tool call."""
|
|
50
|
-
return self.blocking_tool_call_id is not None
|
|
51
|
-
|
|
52
43
|
def start_tool_call(self, tool_call_id: str) -> None:
|
|
53
|
-
"""Start a new tool call
|
|
44
|
+
"""Start a new tool call."""
|
|
54
45
|
self.active_tool_call_ids.add(tool_call_id)
|
|
55
|
-
if self.blocking_tool_call_id is None:
|
|
56
|
-
self.blocking_tool_call_id = tool_call_id
|
|
57
46
|
|
|
58
|
-
def end_tool_call(self, tool_call_id: str) ->
|
|
59
|
-
"""End a tool call
|
|
47
|
+
def end_tool_call(self, tool_call_id: str) -> None:
|
|
48
|
+
"""End a tool call."""
|
|
60
49
|
self.active_tool_call_ids.discard(tool_call_id)
|
|
61
50
|
self.ended_tool_call_ids.add(tool_call_id)
|
|
62
51
|
|
|
63
|
-
# Unblock the buffer if the current blocking tool call is the one ending
|
|
64
|
-
if tool_call_id == self.blocking_tool_call_id:
|
|
65
|
-
self.blocking_tool_call_id = None
|
|
66
|
-
return True
|
|
67
|
-
|
|
68
|
-
return False
|
|
69
|
-
|
|
70
52
|
|
|
71
53
|
def convert_agui_messages_to_agno_messages(messages: List[AGUIMessage]) -> List[Message]:
|
|
72
54
|
"""Convert AG-UI messages to Agno messages."""
|
|
@@ -169,6 +151,12 @@ def _create_events_from_chunk(
|
|
|
169
151
|
|
|
170
152
|
# Handle starting a new tool call
|
|
171
153
|
elif chunk.event == RunEvent.tool_call_started:
|
|
154
|
+
# End the current text message if one is active before starting tool calls
|
|
155
|
+
if message_started:
|
|
156
|
+
end_message_event = TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)
|
|
157
|
+
events_to_emit.append(end_message_event)
|
|
158
|
+
message_started = False # Reset message_started state
|
|
159
|
+
|
|
172
160
|
if chunk.tool is not None: # type: ignore
|
|
173
161
|
tool_call = chunk.tool # type: ignore
|
|
174
162
|
start_event = ToolCallStartEvent(
|
|
@@ -195,7 +183,7 @@ def _create_events_from_chunk(
|
|
|
195
183
|
type=EventType.TOOL_CALL_END,
|
|
196
184
|
tool_call_id=tool_call.tool_call_id, # type: ignore
|
|
197
185
|
)
|
|
198
|
-
events_to_emit.append(end_event)
|
|
186
|
+
events_to_emit.append(end_event)
|
|
199
187
|
|
|
200
188
|
if tool_call.result is not None:
|
|
201
189
|
result_event = ToolCallResultEvent(
|
|
@@ -205,27 +193,17 @@ def _create_events_from_chunk(
|
|
|
205
193
|
role="tool",
|
|
206
194
|
message_id=str(uuid.uuid4()),
|
|
207
195
|
)
|
|
208
|
-
events_to_emit.append(result_event)
|
|
209
|
-
|
|
210
|
-
if tool_call.result is not None:
|
|
211
|
-
result_event = ToolCallResultEvent(
|
|
212
|
-
type=EventType.TOOL_CALL_RESULT,
|
|
213
|
-
tool_call_id=tool_call.tool_call_id, # type: ignore
|
|
214
|
-
content=str(tool_call.result),
|
|
215
|
-
role="tool",
|
|
216
|
-
message_id=str(uuid.uuid4()),
|
|
217
|
-
)
|
|
218
|
-
events_to_emit.append(result_event) # type: ignore
|
|
196
|
+
events_to_emit.append(result_event)
|
|
219
197
|
|
|
220
198
|
# Handle reasoning
|
|
221
199
|
elif chunk.event == RunEvent.reasoning_started:
|
|
222
|
-
step_started_event = StepStartedEvent(type=EventType.STEP_STARTED, step_name="reasoning")
|
|
223
|
-
events_to_emit.append(step_started_event)
|
|
200
|
+
step_started_event = StepStartedEvent(type=EventType.STEP_STARTED, step_name="reasoning")
|
|
201
|
+
events_to_emit.append(step_started_event)
|
|
224
202
|
elif chunk.event == RunEvent.reasoning_completed:
|
|
225
|
-
|
|
226
|
-
events_to_emit.append(
|
|
203
|
+
step_finished_event = StepFinishedEvent(type=EventType.STEP_FINISHED, step_name="reasoning")
|
|
204
|
+
events_to_emit.append(step_finished_event)
|
|
227
205
|
|
|
228
|
-
return events_to_emit, message_started
|
|
206
|
+
return events_to_emit, message_started
|
|
229
207
|
|
|
230
208
|
|
|
231
209
|
def _create_completion_events(
|
|
@@ -251,7 +229,7 @@ def _create_completion_events(
|
|
|
251
229
|
# End the message and run, denoting the end of the session
|
|
252
230
|
if message_started:
|
|
253
231
|
end_message_event = TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)
|
|
254
|
-
events_to_emit.append(end_message_event)
|
|
232
|
+
events_to_emit.append(end_message_event)
|
|
255
233
|
|
|
256
234
|
# emit frontend tool calls, i.e. external_execution=True
|
|
257
235
|
if isinstance(chunk, RunPausedEvent) and chunk.tools is not None:
|
|
@@ -265,14 +243,14 @@ def _create_completion_events(
|
|
|
265
243
|
tool_call_name=tool.tool_name,
|
|
266
244
|
parent_message_id=message_id,
|
|
267
245
|
)
|
|
268
|
-
events_to_emit.append(start_event)
|
|
246
|
+
events_to_emit.append(start_event)
|
|
269
247
|
|
|
270
248
|
args_event = ToolCallArgsEvent(
|
|
271
249
|
type=EventType.TOOL_CALL_ARGS,
|
|
272
250
|
tool_call_id=tool.tool_call_id,
|
|
273
251
|
delta=json.dumps(tool.tool_args),
|
|
274
252
|
)
|
|
275
|
-
events_to_emit.append(args_event)
|
|
253
|
+
events_to_emit.append(args_event)
|
|
276
254
|
|
|
277
255
|
end_event = ToolCallEndEvent(
|
|
278
256
|
type=EventType.TOOL_CALL_END,
|
|
@@ -280,85 +258,25 @@ def _create_completion_events(
|
|
|
280
258
|
)
|
|
281
259
|
events_to_emit.append(end_event)
|
|
282
260
|
|
|
283
|
-
# emit frontend tool calls, i.e. external_execution=True
|
|
284
|
-
if isinstance(chunk, RunPausedEvent) and chunk.tools is not None:
|
|
285
|
-
for tool in chunk.tools:
|
|
286
|
-
if tool.tool_call_id is None or tool.tool_name is None:
|
|
287
|
-
continue
|
|
288
|
-
|
|
289
|
-
start_event = ToolCallStartEvent(
|
|
290
|
-
type=EventType.TOOL_CALL_START,
|
|
291
|
-
tool_call_id=tool.tool_call_id,
|
|
292
|
-
tool_call_name=tool.tool_name,
|
|
293
|
-
parent_message_id=message_id,
|
|
294
|
-
)
|
|
295
|
-
events_to_emit.append(start_event) # type: ignore
|
|
296
|
-
|
|
297
|
-
args_event = ToolCallArgsEvent(
|
|
298
|
-
type=EventType.TOOL_CALL_ARGS,
|
|
299
|
-
tool_call_id=tool.tool_call_id,
|
|
300
|
-
delta=json.dumps(tool.tool_args),
|
|
301
|
-
)
|
|
302
|
-
events_to_emit.append(args_event) # type: ignore
|
|
303
|
-
|
|
304
|
-
end_event = ToolCallEndEvent(
|
|
305
|
-
type=EventType.TOOL_CALL_END,
|
|
306
|
-
tool_call_id=tool.tool_call_id,
|
|
307
|
-
)
|
|
308
|
-
events_to_emit.append(end_event) # type: ignore
|
|
309
|
-
|
|
310
261
|
run_finished_event = RunFinishedEvent(type=EventType.RUN_FINISHED, thread_id=thread_id, run_id=run_id)
|
|
311
|
-
events_to_emit.append(run_finished_event)
|
|
262
|
+
events_to_emit.append(run_finished_event)
|
|
312
263
|
|
|
313
|
-
return events_to_emit
|
|
264
|
+
return events_to_emit
|
|
314
265
|
|
|
315
266
|
|
|
316
267
|
def _emit_event_logic(event: BaseEvent, event_buffer: EventBuffer) -> List[BaseEvent]:
|
|
317
|
-
"""Process an event
|
|
318
|
-
events_to_emit: List[BaseEvent] = []
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if tool_call_id and tool_call_id == event_buffer.blocking_tool_call_id:
|
|
330
|
-
events_to_emit.append(event)
|
|
331
|
-
event_buffer.end_tool_call(tool_call_id)
|
|
332
|
-
# Flush buffered events after ending the blocking tool call
|
|
333
|
-
while event_buffer.buffer:
|
|
334
|
-
buffered_event = event_buffer.buffer.popleft()
|
|
335
|
-
# Recursively process buffered events
|
|
336
|
-
nested_events = _emit_event_logic(buffered_event, event_buffer)
|
|
337
|
-
events_to_emit.extend(nested_events)
|
|
338
|
-
elif tool_call_id and tool_call_id in event_buffer.active_tool_call_ids:
|
|
339
|
-
event_buffer.buffer.append(event)
|
|
340
|
-
event_buffer.end_tool_call(tool_call_id)
|
|
341
|
-
else:
|
|
342
|
-
event_buffer.buffer.append(event)
|
|
343
|
-
# Handle all other events
|
|
344
|
-
elif event.type == EventType.TOOL_CALL_START:
|
|
345
|
-
event_buffer.buffer.append(event)
|
|
346
|
-
else:
|
|
347
|
-
event_buffer.buffer.append(event)
|
|
348
|
-
# If the buffer is not blocked, emit the events normally
|
|
349
|
-
else:
|
|
350
|
-
if event.type == EventType.TOOL_CALL_START:
|
|
351
|
-
tool_call_id = getattr(event, "tool_call_id", None)
|
|
352
|
-
if tool_call_id:
|
|
353
|
-
event_buffer.start_tool_call(tool_call_id)
|
|
354
|
-
events_to_emit.append(event)
|
|
355
|
-
elif event.type == EventType.TOOL_CALL_END:
|
|
356
|
-
tool_call_id = getattr(event, "tool_call_id", None)
|
|
357
|
-
if tool_call_id:
|
|
358
|
-
event_buffer.end_tool_call(tool_call_id)
|
|
359
|
-
events_to_emit.append(event)
|
|
360
|
-
else:
|
|
361
|
-
events_to_emit.append(event)
|
|
268
|
+
"""Process an event and return events to actually emit."""
|
|
269
|
+
events_to_emit: List[BaseEvent] = [event]
|
|
270
|
+
|
|
271
|
+
# Update the event buffer state for tracking purposes
|
|
272
|
+
if event.type == EventType.TOOL_CALL_START:
|
|
273
|
+
tool_call_id = getattr(event, "tool_call_id", None)
|
|
274
|
+
if tool_call_id:
|
|
275
|
+
event_buffer.start_tool_call(tool_call_id)
|
|
276
|
+
elif event.type == EventType.TOOL_CALL_END:
|
|
277
|
+
tool_call_id = getattr(event, "tool_call_id", None)
|
|
278
|
+
if tool_call_id:
|
|
279
|
+
event_buffer.end_tool_call(tool_call_id)
|
|
362
280
|
|
|
363
281
|
return events_to_emit
|
|
364
282
|
|
|
@@ -370,23 +288,22 @@ def stream_agno_response_as_agui_events(
|
|
|
370
288
|
message_id = str(uuid.uuid4())
|
|
371
289
|
message_started = False
|
|
372
290
|
event_buffer = EventBuffer()
|
|
291
|
+
stream_completed = False
|
|
292
|
+
|
|
293
|
+
completion_chunk = None
|
|
373
294
|
|
|
374
295
|
for chunk in response_stream:
|
|
375
|
-
#
|
|
296
|
+
# Check if this is a completion event
|
|
376
297
|
if (
|
|
377
298
|
chunk.event == RunEvent.run_completed
|
|
378
299
|
or chunk.event == TeamRunEvent.run_completed
|
|
379
300
|
or chunk.event == RunEvent.run_paused
|
|
380
301
|
):
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
for event in completion_events:
|
|
385
|
-
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
386
|
-
for emit_event in events_to_emit:
|
|
387
|
-
yield emit_event
|
|
302
|
+
# Store completion chunk but don't process it yet
|
|
303
|
+
completion_chunk = chunk
|
|
304
|
+
stream_completed = True
|
|
388
305
|
else:
|
|
389
|
-
# Process regular chunk
|
|
306
|
+
# Process regular chunk immediately
|
|
390
307
|
events_from_chunk, message_started = _create_events_from_chunk(
|
|
391
308
|
chunk, message_id, message_started, event_buffer
|
|
392
309
|
)
|
|
@@ -396,6 +313,30 @@ def stream_agno_response_as_agui_events(
|
|
|
396
313
|
for emit_event in events_to_emit:
|
|
397
314
|
yield emit_event
|
|
398
315
|
|
|
316
|
+
# Process ONLY completion cleanup events, not content from completion chunk
|
|
317
|
+
if completion_chunk:
|
|
318
|
+
completion_events = _create_completion_events(
|
|
319
|
+
completion_chunk, event_buffer, message_started, message_id, thread_id, run_id
|
|
320
|
+
)
|
|
321
|
+
for event in completion_events:
|
|
322
|
+
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
323
|
+
for emit_event in events_to_emit:
|
|
324
|
+
yield emit_event
|
|
325
|
+
|
|
326
|
+
# Ensure completion events are always emitted even when stream ends naturally
|
|
327
|
+
if not stream_completed:
|
|
328
|
+
# Create a synthetic completion event to ensure proper cleanup
|
|
329
|
+
from agno.run.agent import RunCompletedEvent
|
|
330
|
+
|
|
331
|
+
synthetic_completion = RunCompletedEvent()
|
|
332
|
+
completion_events = _create_completion_events(
|
|
333
|
+
synthetic_completion, event_buffer, message_started, message_id, thread_id, run_id
|
|
334
|
+
)
|
|
335
|
+
for event in completion_events:
|
|
336
|
+
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
337
|
+
for emit_event in events_to_emit:
|
|
338
|
+
yield emit_event
|
|
339
|
+
|
|
399
340
|
|
|
400
341
|
# Async version - thin wrapper
|
|
401
342
|
async def async_stream_agno_response_as_agui_events(
|
|
@@ -407,23 +348,22 @@ async def async_stream_agno_response_as_agui_events(
|
|
|
407
348
|
message_id = str(uuid.uuid4())
|
|
408
349
|
message_started = False
|
|
409
350
|
event_buffer = EventBuffer()
|
|
351
|
+
stream_completed = False
|
|
352
|
+
|
|
353
|
+
completion_chunk = None
|
|
410
354
|
|
|
411
355
|
async for chunk in response_stream:
|
|
412
|
-
#
|
|
356
|
+
# Check if this is a completion event
|
|
413
357
|
if (
|
|
414
358
|
chunk.event == RunEvent.run_completed
|
|
415
359
|
or chunk.event == TeamRunEvent.run_completed
|
|
416
360
|
or chunk.event == RunEvent.run_paused
|
|
417
361
|
):
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
for event in completion_events:
|
|
422
|
-
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
423
|
-
for emit_event in events_to_emit:
|
|
424
|
-
yield emit_event
|
|
362
|
+
# Store completion chunk but don't process it yet
|
|
363
|
+
completion_chunk = chunk
|
|
364
|
+
stream_completed = True
|
|
425
365
|
else:
|
|
426
|
-
# Process regular chunk
|
|
366
|
+
# Process regular chunk immediately
|
|
427
367
|
events_from_chunk, message_started = _create_events_from_chunk(
|
|
428
368
|
chunk, message_id, message_started, event_buffer
|
|
429
369
|
)
|
|
@@ -432,3 +372,27 @@ async def async_stream_agno_response_as_agui_events(
|
|
|
432
372
|
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
433
373
|
for emit_event in events_to_emit:
|
|
434
374
|
yield emit_event
|
|
375
|
+
|
|
376
|
+
# Process ONLY completion cleanup events, not content from completion chunk
|
|
377
|
+
if completion_chunk:
|
|
378
|
+
completion_events = _create_completion_events(
|
|
379
|
+
completion_chunk, event_buffer, message_started, message_id, thread_id, run_id
|
|
380
|
+
)
|
|
381
|
+
for event in completion_events:
|
|
382
|
+
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
383
|
+
for emit_event in events_to_emit:
|
|
384
|
+
yield emit_event
|
|
385
|
+
|
|
386
|
+
# Ensure completion events are always emitted even when stream ends naturally
|
|
387
|
+
if not stream_completed:
|
|
388
|
+
# Create a synthetic completion event to ensure proper cleanup
|
|
389
|
+
from agno.run.agent import RunCompletedEvent
|
|
390
|
+
|
|
391
|
+
synthetic_completion = RunCompletedEvent()
|
|
392
|
+
completion_events = _create_completion_events(
|
|
393
|
+
synthetic_completion, event_buffer, message_started, message_id, thread_id, run_id
|
|
394
|
+
)
|
|
395
|
+
for event in completion_events:
|
|
396
|
+
events_to_emit = _emit_event_logic(event_buffer=event_buffer, event=event)
|
|
397
|
+
for emit_event in events_to_emit:
|
|
398
|
+
yield emit_event
|
agno/os/routers/health.py
CHANGED
agno/os/routers/home.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from agno.os.app import AgentOS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_home_router(os: "AgentOS") -> APIRouter:
|
|
10
|
+
router = APIRouter(tags=["Home"])
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/",
|
|
14
|
+
operation_id="get_api_info",
|
|
15
|
+
summary="API Information",
|
|
16
|
+
description=(
|
|
17
|
+
"Get basic information about this AgentOS API instance, including:\n\n"
|
|
18
|
+
"- API metadata and version\n"
|
|
19
|
+
"- Available capabilities overview\n"
|
|
20
|
+
"- Links to key endpoints and documentation"
|
|
21
|
+
),
|
|
22
|
+
responses={
|
|
23
|
+
200: {
|
|
24
|
+
"description": "API information retrieved successfully",
|
|
25
|
+
"content": {
|
|
26
|
+
"application/json": {
|
|
27
|
+
"examples": {
|
|
28
|
+
"home": {
|
|
29
|
+
"summary": "Example home response",
|
|
30
|
+
"value": {
|
|
31
|
+
"name": "AgentOS API",
|
|
32
|
+
"description": "AI Agent Operating System API",
|
|
33
|
+
"os_id": "demo-os",
|
|
34
|
+
"version": "1.0.0",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
async def get_api_info():
|
|
44
|
+
"""Get basic API information and available capabilities"""
|
|
45
|
+
return {
|
|
46
|
+
"name": "AgentOS API",
|
|
47
|
+
"description": os.description or "AI Agent Operating System API",
|
|
48
|
+
"os_id": os.os_id or "agno-agentos",
|
|
49
|
+
"version": os.version or "1.0.0",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return router
|
|
@@ -5,7 +5,6 @@ from typing import Dict, List, Optional
|
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
|
|
7
7
|
|
|
8
|
-
from agno.db.utils import generate_deterministic_id
|
|
9
8
|
from agno.knowledge.content import Content, FileData
|
|
10
9
|
from agno.knowledge.knowledge import Knowledge
|
|
11
10
|
from agno.knowledge.reader import ReaderFactory
|
|
@@ -34,6 +33,7 @@ from agno.os.schema import (
|
|
|
34
33
|
from agno.os.settings import AgnoAPISettings
|
|
35
34
|
from agno.os.utils import get_knowledge_instance_by_db_id
|
|
36
35
|
from agno.utils.log import log_debug, log_info
|
|
36
|
+
from agno.utils.string import generate_id
|
|
37
37
|
|
|
38
38
|
logger = logging.getLogger(__name__)
|
|
39
39
|
|
|
@@ -167,7 +167,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
167
167
|
)
|
|
168
168
|
content_hash = knowledge._build_content_hash(content)
|
|
169
169
|
content.content_hash = content_hash
|
|
170
|
-
content.id =
|
|
170
|
+
content.id = generate_id(content_hash)
|
|
171
171
|
|
|
172
172
|
background_tasks.add_task(process_content, knowledge, content, reader_id, chunker)
|
|
173
173
|
|
agno/os/schema.py
CHANGED
|
@@ -765,6 +765,7 @@ class TeamSessionDetailSchema(BaseModel):
|
|
|
765
765
|
team_data: Optional[dict]
|
|
766
766
|
created_at: Optional[datetime]
|
|
767
767
|
updated_at: Optional[datetime]
|
|
768
|
+
total_tokens: Optional[int]
|
|
768
769
|
|
|
769
770
|
@classmethod
|
|
770
771
|
def from_session(cls, session: TeamSession) -> "TeamSessionDetailSchema":
|
|
@@ -833,11 +834,14 @@ class RunSchema(BaseModel):
|
|
|
833
834
|
content: Optional[Union[str, dict]]
|
|
834
835
|
run_response_format: Optional[str]
|
|
835
836
|
reasoning_content: Optional[str]
|
|
837
|
+
reasoning_steps: Optional[List[dict]]
|
|
836
838
|
metrics: Optional[dict]
|
|
837
839
|
messages: Optional[List[dict]]
|
|
838
840
|
tools: Optional[List[dict]]
|
|
839
841
|
events: Optional[List[dict]]
|
|
840
842
|
created_at: Optional[datetime]
|
|
843
|
+
references: Optional[List[dict]]
|
|
844
|
+
reasoning_messages: Optional[List[dict]]
|
|
841
845
|
|
|
842
846
|
@classmethod
|
|
843
847
|
def from_dict(cls, run_dict: Dict[str, Any]) -> "RunSchema":
|
|
@@ -852,10 +856,13 @@ class RunSchema(BaseModel):
|
|
|
852
856
|
content=run_dict.get("content", ""),
|
|
853
857
|
run_response_format=run_response_format,
|
|
854
858
|
reasoning_content=run_dict.get("reasoning_content", ""),
|
|
859
|
+
reasoning_steps=run_dict.get("reasoning_steps", []),
|
|
855
860
|
metrics=run_dict.get("metrics", {}),
|
|
856
861
|
messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
|
|
857
862
|
tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
|
|
858
863
|
events=[event for event in run_dict["events"]] if run_dict.get("events") else None,
|
|
864
|
+
references=run_dict.get("references", []),
|
|
865
|
+
reasoning_messages=run_dict.get("reasoning_messages", []),
|
|
859
866
|
created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
|
|
860
867
|
if run_dict.get("created_at") is not None
|
|
861
868
|
else None,
|
|
@@ -868,6 +875,7 @@ class TeamRunSchema(BaseModel):
|
|
|
868
875
|
team_id: Optional[str]
|
|
869
876
|
content: Optional[Union[str, dict]]
|
|
870
877
|
reasoning_content: Optional[str]
|
|
878
|
+
reasoning_steps: Optional[List[dict]]
|
|
871
879
|
run_input: Optional[str]
|
|
872
880
|
run_response_format: Optional[str]
|
|
873
881
|
metrics: Optional[dict]
|
|
@@ -875,6 +883,8 @@ class TeamRunSchema(BaseModel):
|
|
|
875
883
|
messages: Optional[List[dict]]
|
|
876
884
|
events: Optional[List[dict]]
|
|
877
885
|
created_at: Optional[datetime]
|
|
886
|
+
references: Optional[List[dict]]
|
|
887
|
+
reasoning_messages: Optional[List[dict]]
|
|
878
888
|
|
|
879
889
|
@classmethod
|
|
880
890
|
def from_dict(cls, run_dict: Dict[str, Any]) -> "TeamRunSchema":
|
|
@@ -888,6 +898,7 @@ class TeamRunSchema(BaseModel):
|
|
|
888
898
|
content=run_dict.get("content", ""),
|
|
889
899
|
run_response_format=run_response_format,
|
|
890
900
|
reasoning_content=run_dict.get("reasoning_content", ""),
|
|
901
|
+
reasoning_steps=run_dict.get("reasoning_steps", []),
|
|
891
902
|
metrics=run_dict.get("metrics", {}),
|
|
892
903
|
messages=[message for message in run_dict.get("messages", [])] if run_dict.get("messages") else None,
|
|
893
904
|
tools=[tool for tool in run_dict.get("tools", [])] if run_dict.get("tools") else None,
|
|
@@ -895,6 +906,8 @@ class TeamRunSchema(BaseModel):
|
|
|
895
906
|
created_at=datetime.fromtimestamp(run_dict.get("created_at", 0), tz=timezone.utc)
|
|
896
907
|
if run_dict.get("created_at") is not None
|
|
897
908
|
else None,
|
|
909
|
+
references=run_dict.get("references", []),
|
|
910
|
+
reasoning_messages=run_dict.get("reasoning_messages", []),
|
|
898
911
|
)
|
|
899
912
|
|
|
900
913
|
|
|
@@ -910,6 +923,10 @@ class WorkflowRunSchema(BaseModel):
|
|
|
910
923
|
step_executor_runs: Optional[list[dict]]
|
|
911
924
|
metrics: Optional[dict]
|
|
912
925
|
created_at: Optional[int]
|
|
926
|
+
reasoning_content: Optional[str]
|
|
927
|
+
reasoning_steps: Optional[List[dict]]
|
|
928
|
+
references: Optional[List[dict]]
|
|
929
|
+
reasoning_messages: Optional[List[dict]]
|
|
913
930
|
|
|
914
931
|
@classmethod
|
|
915
932
|
def from_dict(cls, run_response: Dict[str, Any]) -> "WorkflowRunSchema":
|
|
@@ -926,6 +943,10 @@ class WorkflowRunSchema(BaseModel):
|
|
|
926
943
|
step_results=run_response.get("step_results", []),
|
|
927
944
|
step_executor_runs=run_response.get("step_executor_runs", []),
|
|
928
945
|
created_at=run_response["created_at"],
|
|
946
|
+
reasoning_content=run_response.get("reasoning_content", ""),
|
|
947
|
+
reasoning_steps=run_response.get("reasoning_steps", []),
|
|
948
|
+
references=run_response.get("references", []),
|
|
949
|
+
reasoning_messages=run_response.get("reasoning_messages", []),
|
|
929
950
|
)
|
|
930
951
|
|
|
931
952
|
|
agno/os/utils.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import Any, Callable, Dict, List, Optional, Union
|
|
2
|
-
from uuid import uuid4
|
|
3
2
|
|
|
4
3
|
from fastapi import HTTPException, UploadFile
|
|
5
4
|
|
|
@@ -261,10 +260,3 @@ def _generate_schema_from_params(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
261
260
|
schema["required"] = required
|
|
262
261
|
|
|
263
262
|
return schema
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def generate_id(name: Optional[str] = None) -> str:
|
|
267
|
-
if name:
|
|
268
|
-
return name.lower().replace(" ", "-").replace("_", "-")
|
|
269
|
-
else:
|
|
270
|
-
return str(uuid4())
|
agno/run/agent.py
CHANGED
|
@@ -559,7 +559,7 @@ class RunOutput:
|
|
|
559
559
|
events = [run_output_event_from_dict(event) for event in events] if events else None
|
|
560
560
|
|
|
561
561
|
messages = data.pop("messages", None)
|
|
562
|
-
messages = [Message.
|
|
562
|
+
messages = [Message.from_dict(message) for message in messages] if messages else None
|
|
563
563
|
|
|
564
564
|
citations = data.pop("citations", None)
|
|
565
565
|
citations = Citations.model_validate(citations) if citations else None
|
|
@@ -591,7 +591,7 @@ class RunOutput:
|
|
|
591
591
|
additional_input = data.pop("additional_input", None)
|
|
592
592
|
|
|
593
593
|
if additional_input is not None:
|
|
594
|
-
additional_input = [Message.
|
|
594
|
+
additional_input = [Message.from_dict(message) for message in additional_input]
|
|
595
595
|
|
|
596
596
|
reasoning_steps = data.pop("reasoning_steps", None)
|
|
597
597
|
if reasoning_steps is not None:
|
|
@@ -599,7 +599,7 @@ class RunOutput:
|
|
|
599
599
|
|
|
600
600
|
reasoning_messages = data.pop("reasoning_messages", None)
|
|
601
601
|
if reasoning_messages is not None:
|
|
602
|
-
reasoning_messages = [Message.
|
|
602
|
+
reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
|
|
603
603
|
|
|
604
604
|
references = data.pop("references", None)
|
|
605
605
|
if references is not None:
|
agno/run/team.py
CHANGED
|
@@ -519,7 +519,7 @@ class TeamRunOutput:
|
|
|
519
519
|
events = final_events
|
|
520
520
|
|
|
521
521
|
messages = data.pop("messages", None)
|
|
522
|
-
messages = [Message.
|
|
522
|
+
messages = [Message.from_dict(message) for message in messages] if messages else None
|
|
523
523
|
|
|
524
524
|
member_responses = data.pop("member_responses", [])
|
|
525
525
|
parsed_member_responses: List[Union["TeamRunOutput", RunOutput]] = []
|
|
@@ -532,7 +532,7 @@ class TeamRunOutput:
|
|
|
532
532
|
|
|
533
533
|
additional_input = data.pop("additional_input", None)
|
|
534
534
|
if additional_input is not None:
|
|
535
|
-
additional_input = [Message.
|
|
535
|
+
additional_input = [Message.from_dict(message) for message in additional_input]
|
|
536
536
|
|
|
537
537
|
reasoning_steps = data.pop("reasoning_steps", None)
|
|
538
538
|
if reasoning_steps is not None:
|
|
@@ -540,7 +540,7 @@ class TeamRunOutput:
|
|
|
540
540
|
|
|
541
541
|
reasoning_messages = data.pop("reasoning_messages", None)
|
|
542
542
|
if reasoning_messages is not None:
|
|
543
|
-
reasoning_messages = [Message.
|
|
543
|
+
reasoning_messages = [Message.from_dict(message) for message in reasoning_messages]
|
|
544
544
|
|
|
545
545
|
references = data.pop("references", None)
|
|
546
546
|
if references is not None:
|