agno 2.0.0a1__py3-none-any.whl → 2.0.0rc1__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 (50) hide show
  1. agno/agent/agent.py +390 -33
  2. agno/api/agent.py +2 -2
  3. agno/api/evals.py +2 -2
  4. agno/api/os.py +1 -1
  5. agno/api/settings.py +2 -2
  6. agno/api/team.py +2 -2
  7. agno/db/dynamo/dynamo.py +0 -6
  8. agno/db/firestore/firestore.py +0 -6
  9. agno/db/in_memory/in_memory_db.py +0 -6
  10. agno/db/json/json_db.py +0 -6
  11. agno/db/mongo/mongo.py +0 -6
  12. agno/db/mysql/utils.py +0 -1
  13. agno/db/postgres/postgres.py +0 -10
  14. agno/db/postgres/utils.py +0 -1
  15. agno/db/redis/redis.py +0 -4
  16. agno/db/singlestore/singlestore.py +0 -10
  17. agno/db/singlestore/utils.py +0 -1
  18. agno/db/sqlite/sqlite.py +0 -4
  19. agno/db/sqlite/utils.py +0 -1
  20. agno/integrations/discord/client.py +5 -1
  21. agno/knowledge/embedder/aws_bedrock.py +2 -2
  22. agno/models/anthropic/claude.py +2 -49
  23. agno/models/message.py +7 -6
  24. agno/os/app.py +158 -62
  25. agno/os/interfaces/agui/agui.py +1 -1
  26. agno/os/interfaces/agui/utils.py +16 -9
  27. agno/os/interfaces/slack/slack.py +2 -3
  28. agno/os/interfaces/whatsapp/whatsapp.py +2 -3
  29. agno/os/mcp.py +255 -0
  30. agno/os/router.py +33 -7
  31. agno/os/routers/evals/evals.py +9 -5
  32. agno/os/routers/knowledge/knowledge.py +30 -7
  33. agno/os/routers/memory/memory.py +17 -8
  34. agno/os/routers/metrics/metrics.py +4 -2
  35. agno/os/routers/session/session.py +8 -3
  36. agno/os/settings.py +0 -1
  37. agno/run/agent.py +87 -2
  38. agno/run/cancel.py +0 -2
  39. agno/team/team.py +2 -2
  40. agno/tools/function.py +65 -7
  41. agno/tools/linear.py +1 -1
  42. agno/utils/gemini.py +31 -1
  43. agno/utils/models/claude.py +49 -0
  44. agno/utils/streamlit.py +454 -0
  45. agno/workflow/workflow.py +8 -1
  46. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/METADATA +1 -1
  47. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/RECORD +50 -48
  48. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/WHEEL +0 -0
  49. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  50. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,454 @@
1
+ from datetime import datetime
2
+ from typing import Any, Callable, Dict, List, Optional
3
+
4
+ import streamlit as st
5
+
6
+ from agno.agent import Agent
7
+ from agno.db.base import SessionType
8
+ from agno.models.anthropic import Claude
9
+ from agno.models.google import Gemini
10
+ from agno.models.openai import OpenAIChat
11
+ from agno.utils.log import logger
12
+
13
+
14
+ def add_message(role: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None) -> None:
15
+ """Add a message to the session state."""
16
+ if "messages" not in st.session_state:
17
+ st.session_state["messages"] = []
18
+
19
+ message: Dict[str, Any] = {"role": role, "content": content}
20
+ if tool_calls:
21
+ message["tool_calls"] = tool_calls
22
+
23
+ st.session_state["messages"].append(message)
24
+
25
+
26
+ def display_tool_calls(container, tools: List[Any]):
27
+ """Display tool calls in expandable sections."""
28
+ if not tools:
29
+ return
30
+
31
+ with container.container():
32
+ for tool in tools:
33
+ if hasattr(tool, "tool_name"):
34
+ name = tool.tool_name or "Tool"
35
+ args = tool.tool_args or {}
36
+ result = tool.result or ""
37
+ else:
38
+ name = tool.get("tool_name") or tool.get("name") or "Tool"
39
+ args = tool.get("tool_args") or tool.get("args") or {}
40
+ result = tool.get("result") or tool.get("content") or ""
41
+
42
+ with st.expander(f"🛠️ {name.replace('_', ' ')}", expanded=False):
43
+ if args:
44
+ st.markdown("**Arguments:**")
45
+ st.json(args)
46
+ if result:
47
+ st.markdown("**Result:**")
48
+ st.json(result)
49
+
50
+
51
+ def session_selector_widget(agent: Agent, model_id: str, agent_creation_callback: Callable[[str, str], Agent]) -> None:
52
+ """Session selector widget"""
53
+ if not agent.db:
54
+ st.sidebar.info("💡 Database not configured. Sessions will not be saved.")
55
+ return
56
+
57
+ try:
58
+ sessions = agent.db.get_sessions(
59
+ session_type=SessionType.AGENT,
60
+ deserialize=True,
61
+ sort_by="created_at",
62
+ sort_order="desc",
63
+ )
64
+ except Exception as e:
65
+ logger.error(f"Error fetching sessions: {e}")
66
+ st.sidebar.error("Could not load sessions")
67
+ return
68
+
69
+ if not sessions:
70
+ st.sidebar.info("🆕 New Chat - Start your conversation!")
71
+ return
72
+
73
+ # Filter session data
74
+ session_options = []
75
+ session_dict = {}
76
+
77
+ for session in sessions:
78
+ if not hasattr(session, "session_id") or not session.session_id:
79
+ continue
80
+
81
+ session_id = session.session_id
82
+ session_name = None
83
+
84
+ # Extract session name from session_data
85
+ if hasattr(session, "session_data") and session.session_data:
86
+ session_name = session.session_data.get("session_name")
87
+
88
+ name = session_name or session_id
89
+ session_options.append(name)
90
+ session_dict[name] = session_id
91
+
92
+ current_session_id = st.session_state.get("session_id")
93
+ current_selection = None
94
+
95
+ if current_session_id and current_session_id not in [s_id for s_id in session_dict.values()]:
96
+ logger.info(f"New session: {current_session_id}")
97
+ if agent.get_session_name():
98
+ current_display_name = agent.get_session_name()
99
+ else:
100
+ current_display_name = f"{current_session_id[:8]}..."
101
+ session_options.insert(0, current_display_name)
102
+ session_dict[current_display_name] = current_session_id
103
+ current_selection = current_display_name
104
+ st.session_state["is_new_session"] = True
105
+
106
+ for display_name, session_id in session_dict.items():
107
+ if session_id == current_session_id:
108
+ current_selection = display_name
109
+ break
110
+
111
+ display_options = session_options
112
+ selected_index = (
113
+ session_options.index(current_selection)
114
+ if current_selection and current_selection in session_options
115
+ else 0
116
+ if session_options
117
+ else None
118
+ )
119
+
120
+ if not display_options:
121
+ st.sidebar.info("🆕 Start your first conversation!")
122
+ return
123
+
124
+ selected = st.sidebar.selectbox(
125
+ label="Session",
126
+ options=display_options,
127
+ index=selected_index,
128
+ help="Select a session to continue",
129
+ )
130
+
131
+ if selected and selected in session_dict:
132
+ selected_session_id = session_dict[selected]
133
+ if selected_session_id != current_session_id:
134
+ if not st.session_state.get("is_new_session", False):
135
+ st.session_state["is_loading_session"] = True
136
+ try:
137
+ _load_session(selected_session_id, model_id, agent_creation_callback)
138
+ finally:
139
+ # Always clear the loading flag, even if there's an error
140
+ st.session_state["is_loading_session"] = False
141
+ else:
142
+ # Clear the new session flag since we're done with initialization
143
+ st.session_state["is_new_session"] = False
144
+
145
+ # Rename session
146
+ if agent.session_id:
147
+ if "session_edit_mode" not in st.session_state:
148
+ st.session_state.session_edit_mode = False
149
+
150
+ current_name = agent.get_session_name() or agent.session_id
151
+
152
+ if not st.session_state.session_edit_mode:
153
+ col1, col2 = st.sidebar.columns([3, 1])
154
+ with col1:
155
+ st.write(f"**Session:** {current_name}")
156
+ with col2:
157
+ if st.button("✎", help="Rename session", key="rename_session_button"):
158
+ st.session_state.session_edit_mode = True
159
+ st.rerun()
160
+ else:
161
+ new_name = st.sidebar.text_input("Enter new name:", value=current_name, key="session_name_input")
162
+
163
+ col1, col2 = st.sidebar.columns([1, 1])
164
+ with col1:
165
+ if st.button(
166
+ "💾 Save",
167
+ type="primary",
168
+ use_container_width=True,
169
+ key="save_session_name",
170
+ ):
171
+ if new_name and new_name.strip():
172
+ try:
173
+ result = agent.set_session_name(session_name=new_name.strip())
174
+
175
+ if result:
176
+ logger.info(f"Session renamed to: {new_name.strip()}")
177
+ # Clear any cached session data to ensure fresh reload
178
+ if hasattr(agent, "_agent_session") and agent._agent_session:
179
+ agent._agent_session = None
180
+ st.session_state.session_edit_mode = False
181
+ st.sidebar.success("Session renamed!")
182
+ st.rerun()
183
+ except Exception as e:
184
+ logger.error(f"Error renaming session: {e}")
185
+ st.sidebar.error(f"Error: {str(e)}")
186
+ else:
187
+ st.sidebar.error("Please enter a valid name")
188
+
189
+ with col2:
190
+ if st.button("❌ Cancel", use_container_width=True, key="cancel_session_rename"):
191
+ st.session_state.session_edit_mode = False
192
+ st.rerun()
193
+
194
+
195
+ def _load_session(session_id: str, model_id: str, agent_creation_callback: Callable[[str, str], Agent]):
196
+ try:
197
+ logger.info(f"Creating agent with session_id: {session_id}")
198
+ new_agent = agent_creation_callback(model_id, session_id)
199
+
200
+ st.session_state["agent"] = new_agent
201
+ st.session_state["session_id"] = session_id
202
+ st.session_state["messages"] = []
203
+ st.session_state["current_model"] = model_id # Keep current_model in sync
204
+
205
+ try:
206
+ if new_agent.db:
207
+ selected_session = new_agent.db.get_session(
208
+ session_id=session_id, session_type=SessionType.AGENT, deserialize=True
209
+ )
210
+ else:
211
+ selected_session = None
212
+
213
+ # Recreate the chat history
214
+ if selected_session:
215
+ if hasattr(selected_session, "runs") and selected_session.runs:
216
+ for run_idx, run in enumerate(selected_session.runs):
217
+ messages = getattr(run, "messages", None)
218
+
219
+ if messages:
220
+ user_msg = None
221
+ assistant_msg = None
222
+ tool_calls = []
223
+
224
+ for msg_idx, message in enumerate(messages):
225
+ if not hasattr(message, "role") or not hasattr(message, "content"):
226
+ continue
227
+
228
+ role = message.role
229
+ content = str(message.content) if message.content else ""
230
+
231
+ if role == "user":
232
+ if content and content.strip():
233
+ user_msg = content.strip()
234
+ elif role == "assistant":
235
+ if content and content.strip() and content.strip().lower() != "none":
236
+ assistant_msg = content
237
+
238
+ # Display tool calls for this run
239
+ if hasattr(run, "tools") and run.tools:
240
+ tool_calls = run.tools
241
+
242
+ # Add messages to chat history
243
+ if user_msg:
244
+ add_message("user", user_msg)
245
+ if assistant_msg:
246
+ add_message("assistant", assistant_msg, tool_calls)
247
+
248
+ else:
249
+ logger.warning(f"No session found in database for session_id: {session_id}")
250
+
251
+ except Exception as e:
252
+ logger.warning(f"Could not load chat history: {e}")
253
+
254
+ st.rerun()
255
+
256
+ except Exception as e:
257
+ logger.error(f"Error loading session: {e}")
258
+ st.sidebar.error(f"Error loading session: {str(e)}")
259
+
260
+
261
+ def display_response(agent: Agent, question: str) -> None:
262
+ """Handle agent response with streaming and tool call display."""
263
+ with st.chat_message("assistant"):
264
+ tool_calls_container = st.empty()
265
+ resp_container = st.empty()
266
+ with st.spinner("🤔 Thinking..."):
267
+ response = ""
268
+ try:
269
+ # Run the agent and stream the response
270
+ run_response = agent.run(question, stream=True)
271
+ for resp_chunk in run_response:
272
+ try:
273
+ # Display tool calls if available
274
+ if hasattr(resp_chunk, "tool") and resp_chunk.tool:
275
+ display_tool_calls(tool_calls_container, [resp_chunk.tool])
276
+ except Exception as tool_error:
277
+ logger.warning(f"Error displaying tool calls: {tool_error}")
278
+
279
+ if resp_chunk.content is not None:
280
+ content = str(resp_chunk.content)
281
+
282
+ if not (
283
+ content.strip().endswith("completed in") or "completed in" in content and "s." in content
284
+ ):
285
+ response += content
286
+ resp_container.markdown(response)
287
+
288
+ try:
289
+ if hasattr(agent, "run_response") and agent.run_response and hasattr(agent.run_response, "tools"):
290
+ add_message("assistant", response, agent.run_response.tools)
291
+ else:
292
+ add_message("assistant", response)
293
+ except Exception as add_msg_error:
294
+ logger.warning(f"Error adding message with tools: {add_msg_error}")
295
+ add_message("assistant", response)
296
+
297
+ except Exception as e:
298
+ st.error(f"Sorry, I encountered an error: {str(e)}")
299
+
300
+
301
+ def display_chat_messages() -> None:
302
+ """Display all chat messages from session state."""
303
+ if "messages" not in st.session_state:
304
+ return
305
+
306
+ for message in st.session_state["messages"]:
307
+ if message["role"] in ["user", "assistant"]:
308
+ content = message["content"]
309
+ with st.chat_message(message["role"]):
310
+ # Display tool calls
311
+ if "tool_calls" in message and message["tool_calls"]:
312
+ display_tool_calls(st.container(), message["tool_calls"])
313
+
314
+ if content is not None and str(content).strip() and str(content).strip().lower() != "none":
315
+ st.markdown(content)
316
+
317
+
318
+ def initialize_agent(model_id: str, agent_creation_callback: Callable[[str, Optional[str]], Agent]) -> Agent:
319
+ """Initialize or get agent with proper session management."""
320
+ if "agent" not in st.session_state or st.session_state["agent"] is None:
321
+ # First time initialization - get existing session_id if any
322
+ session_id = st.session_state.get("session_id")
323
+ agent = agent_creation_callback(model_id, session_id)
324
+ st.session_state["agent"] = agent
325
+ st.session_state["current_model"] = model_id
326
+
327
+ return agent
328
+ else:
329
+ return st.session_state["agent"]
330
+
331
+
332
+ def reset_session_state(agent: Agent) -> None:
333
+ """Update session state."""
334
+ print(f"Resetting session state for agent: {agent.session_id}")
335
+ if agent.session_id is not None:
336
+ st.session_state["session_id"] = agent.session_id
337
+
338
+ if "messages" not in st.session_state:
339
+ st.session_state["messages"] = []
340
+
341
+
342
+ def knowledge_base_info_widget(agent: Agent) -> None:
343
+ """Display knowledge base information widget."""
344
+ if not agent.knowledge:
345
+ st.sidebar.info("No knowledge base configured")
346
+ return
347
+
348
+ vector_db = getattr(agent.knowledge, "vector_db", None)
349
+ if not vector_db:
350
+ st.sidebar.info("No vector db configured")
351
+ return
352
+
353
+ try:
354
+ doc_count = vector_db.get_count()
355
+ if doc_count == 0:
356
+ st.sidebar.info("💡 Upload documents to populate the knowledge base")
357
+ else:
358
+ st.sidebar.metric("Documents Loaded", doc_count)
359
+ except Exception as e:
360
+ logger.error(f"Error getting knowledge base info: {e}")
361
+ st.sidebar.warning("Could not retrieve knowledge base information")
362
+
363
+
364
+ def export_chat_history(app_name: str = "Chat") -> str:
365
+ """Export chat history to markdown."""
366
+ if "messages" not in st.session_state or not st.session_state["messages"]:
367
+ return "# Chat History\n\n*No messages to export*"
368
+
369
+ title = f"{app_name} Chat History"
370
+ for msg in st.session_state["messages"]:
371
+ if msg.get("role") == "user" and msg.get("content"):
372
+ title = msg["content"][:100]
373
+ if len(msg["content"]) > 100:
374
+ title += "..."
375
+ break
376
+
377
+ chat_text = f"# {title}\n\n"
378
+ chat_text += f"**Exported:** {datetime.now().strftime('%B %d, %Y at %I:%M %p')}\n\n"
379
+ chat_text += "---\n\n"
380
+
381
+ for msg in st.session_state["messages"]:
382
+ role = msg.get("role", "")
383
+ content = msg.get("content", "")
384
+
385
+ if not content or str(content).strip().lower() == "none":
386
+ continue
387
+
388
+ role_display = "## 🙋 User" if role == "user" else "## 🤖 Assistant"
389
+ chat_text += f"{role_display}\n\n{content}\n\n---\n\n"
390
+ return chat_text
391
+
392
+
393
+ def get_model_from_id(model_id: str):
394
+ """Get a model instance from a model ID string."""
395
+ if model_id.startswith("openai:"):
396
+ return OpenAIChat(id=model_id.split("openai:")[1])
397
+ elif model_id.startswith("anthropic:"):
398
+ return Claude(id=model_id.split("anthropic:")[1])
399
+ elif model_id.startswith("google:"):
400
+ return Gemini(id=model_id.split("google:")[1])
401
+ else:
402
+ return OpenAIChat(id="gpt-4o")
403
+
404
+
405
+ def about_section(description: str):
406
+ """About section"""
407
+ st.sidebar.markdown("---")
408
+ st.sidebar.markdown("### ℹ️ About")
409
+ st.sidebar.markdown(f"""
410
+ {description}
411
+
412
+ Built with:
413
+ - 🚀 Agno
414
+ - 💫 Streamlit
415
+ """)
416
+
417
+
418
+ MODELS = [
419
+ "gpt-4o",
420
+ "o3-mini",
421
+ "gpt-5",
422
+ "claude-4-sonnet",
423
+ "gemini-2.5-pro",
424
+ ]
425
+
426
+
427
+ COMMON_CSS = """
428
+ <style>
429
+ .main-title {
430
+ text-align: center;
431
+ background: linear-gradient(45deg, #FF4B2B, #FF416C);
432
+ -webkit-background-clip: text;
433
+ -webkit-text-fill-color: transparent;
434
+ font-size: 3em;
435
+ font-weight: bold;
436
+ padding: 1em 0;
437
+ }
438
+ .subtitle {
439
+ text-align: center;
440
+ color: #666;
441
+ margin-bottom: 2em;
442
+ }
443
+ .stButton button {
444
+ width: 100%;
445
+ border-radius: 20px;
446
+ margin: 0.2em 0;
447
+ transition: all 0.3s ease;
448
+ }
449
+ .stButton button:hover {
450
+ transform: translateY(-2px);
451
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
452
+ }
453
+ </style>
454
+ """
agno/workflow/workflow.py CHANGED
@@ -208,6 +208,13 @@ class Workflow:
208
208
 
209
209
  self._workflow_session: Optional[WorkflowSession] = None
210
210
 
211
+ def set_id(self) -> None:
212
+ if self.id is None:
213
+ if self.name is not None:
214
+ self.id = self.name.lower().replace(" ", "-")
215
+ else:
216
+ self.id = str(uuid4())
217
+
211
218
  def _validate_input(
212
219
  self, input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]]
213
220
  ) -> Optional[BaseModel]:
@@ -293,7 +300,7 @@ class Workflow:
293
300
 
294
301
  def initialize_workflow(self):
295
302
  if self.id is None:
296
- self.id = str(uuid4())
303
+ self.set_id()
297
304
  log_debug(f"Generated new workflow_id: {self.id}")
298
305
 
299
306
  def _initialize_session(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.0.0a1
3
+ Version: 2.0.0rc1
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  Project-URL: homepage, https://agno.com