PraisonAI 2.0.26__tar.gz → 2.0.28__tar.gz

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.

Potentially problematic release.


This version of PraisonAI might be problematic. Click here for more details.

Files changed (89) hide show
  1. {praisonai-2.0.26 → praisonai-2.0.28}/PKG-INFO +1 -1
  2. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/deploy.py +1 -1
  3. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/agents.py +110 -63
  4. praisonai-2.0.28/praisonai/ui/tools.md +133 -0
  5. {praisonai-2.0.26 → praisonai-2.0.28}/pyproject.toml +2 -2
  6. praisonai-2.0.26/praisonai/ui/together.py +0 -775
  7. {praisonai-2.0.26 → praisonai-2.0.28}/LICENSE +0 -0
  8. {praisonai-2.0.26 → praisonai-2.0.28}/README.md +0 -0
  9. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/__init__.py +0 -0
  10. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/__main__.py +0 -0
  11. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/agents_generator.py +0 -0
  12. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/api/call.py +0 -0
  13. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/auto.py +0 -0
  14. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/chainlit_ui.py +0 -0
  15. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/cli.py +0 -0
  16. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/inbuilt_tools/__init__.py +0 -0
  17. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  18. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/inc/__init__.py +0 -0
  19. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/inc/config.py +0 -0
  20. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/inc/models.py +0 -0
  21. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/android-chrome-192x192.png +0 -0
  22. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/android-chrome-512x512.png +0 -0
  23. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/apple-touch-icon.png +0 -0
  24. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/fantasy.svg +0 -0
  25. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/favicon-16x16.png +0 -0
  26. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/favicon-32x32.png +0 -0
  27. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/favicon.ico +0 -0
  28. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/game.svg +0 -0
  29. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/logo_dark.png +0 -0
  30. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/logo_light.png +0 -0
  31. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/movie.svg +0 -0
  32. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/praison-ai-agents-architecture-dark.png +0 -0
  33. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/praison-ai-agents-architecture.png +0 -0
  34. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/public/thriller.svg +0 -0
  35. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/__init__.py +0 -0
  36. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/build.py +0 -0
  37. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/config.yaml +0 -0
  38. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/post_install.py +0 -0
  39. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/setup_conda_env.py +0 -0
  40. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup/setup_conda_env.sh +0 -0
  41. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/setup.py +0 -0
  42. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/test.py +0 -0
  43. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/train.py +0 -0
  44. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/README.md +0 -0
  45. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/callbacks.py +0 -0
  46. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/chat.py +0 -0
  47. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/code.py +0 -0
  48. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/colab.py +0 -0
  49. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/colab_chainlit.py +0 -0
  50. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/components/aicoder.py +0 -0
  51. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/config.toml +0 -0
  52. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/bn.json +0 -0
  53. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/en-US.json +0 -0
  54. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/gu.json +0 -0
  55. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/he-IL.json +0 -0
  56. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/hi.json +0 -0
  57. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/kn.json +0 -0
  58. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/ml.json +0 -0
  59. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/mr.json +0 -0
  60. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/ta.json +0 -0
  61. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/te.json +0 -0
  62. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/.chainlit/translations/zh-CN.json +0 -0
  63. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/chainlit.md +0 -0
  64. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/bn.json +0 -0
  65. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/en-US.json +0 -0
  66. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/gu.json +0 -0
  67. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/he-IL.json +0 -0
  68. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/hi.json +0 -0
  69. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/kn.json +0 -0
  70. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/ml.json +0 -0
  71. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/mr.json +0 -0
  72. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/ta.json +0 -0
  73. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/te.json +0 -0
  74. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/config/translations/zh-CN.json +0 -0
  75. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/context.py +0 -0
  76. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/db.py +0 -0
  77. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/fantasy.svg +0 -0
  78. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/game.svg +0 -0
  79. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/logo_dark.png +0 -0
  80. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/logo_light.png +0 -0
  81. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/movie.svg +0 -0
  82. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/praison.css +0 -0
  83. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/public/thriller.svg +0 -0
  84. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/realtime.py +0 -0
  85. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/realtimeclient/__init__.py +0 -0
  86. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/realtimeclient/realtimedocs.txt +0 -0
  87. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/realtimeclient/tools.py +0 -0
  88. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/ui/sql_alchemy.py +0 -0
  89. {praisonai-2.0.26 → praisonai-2.0.28}/praisonai/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: PraisonAI
3
- Version: 2.0.26
3
+ Version: 2.0.28
4
4
  Summary: PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human–agent collaboration.
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10,<3.13
@@ -56,7 +56,7 @@ class CloudDeployer:
56
56
  file.write("FROM python:3.11-slim\n")
57
57
  file.write("WORKDIR /app\n")
58
58
  file.write("COPY . .\n")
59
- file.write("RUN pip install flask praisonai==2.0.26 gunicorn markdown\n")
59
+ file.write("RUN pip install flask praisonai==2.0.28 gunicorn markdown\n")
60
60
  file.write("EXPOSE 8080\n")
61
61
  file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
62
62
 
@@ -5,6 +5,8 @@ import yaml
5
5
  import logging
6
6
  import inspect
7
7
  import chainlit as cl
8
+ from praisonaiagents import Agent, Task, PraisonAIAgents, register_display_callback
9
+
8
10
  framework = "praisonai"
9
11
  config_list = [
10
12
  {
@@ -14,12 +16,19 @@ config_list = [
14
16
  }
15
17
  ]
16
18
 
17
- agent_file = "test.yaml"
18
-
19
- actions=[
19
+ actions = [
20
20
  cl.Action(name="run", value="run", label="✅ Run"),
21
21
  cl.Action(name="modify", value="modify", label="🔧 Modify"),
22
22
  ]
23
+
24
+ @cl.action_callback("run")
25
+ async def on_run(action):
26
+ await main(cl.Message(content=""))
27
+
28
+ @cl.action_callback("modify")
29
+ async def on_modify(action):
30
+ await cl.Message(content="Modify the agents and tools from below settings").send()
31
+
23
32
  import os
24
33
  import sys
25
34
  import yaml
@@ -37,9 +46,6 @@ import chainlit as cl
37
46
  from chainlit.types import ThreadDict
38
47
  import chainlit.data as cl_data
39
48
 
40
- # External imports from your local packages (adjust if needed)
41
- from praisonaiagents import Agent, Task, PraisonAIAgents
42
-
43
49
  # -----------------------------------------------------------------------------
44
50
  # Global Setup
45
51
  # -----------------------------------------------------------------------------
@@ -53,14 +59,12 @@ message_queue = Queue() # Queue to handle messages sent to Chainlit UI
53
59
  agent_file = "agents.yaml"
54
60
 
55
61
  # -----------------------------------------------------------------------------
56
- # Database and Settings Logic (untouched logic with minimal additions for Chainlit)
62
+ # Database and Settings Logic
57
63
  # -----------------------------------------------------------------------------
58
64
 
59
65
  MAX_RETRIES = 3
60
66
  RETRY_DELAY = 1 # seconds
61
67
 
62
- # Original DatabaseManager from chainlit_ui.py, unmodified logic, plus minimal
63
- # placeholder methods for Chainlit's data layer calls (create_user, get_user, etc.)
64
68
  from db import DatabaseManager
65
69
 
66
70
  async def init_database_with_retry():
@@ -118,7 +122,7 @@ async def update_thread_metadata(thread_id: str, metadata: dict):
118
122
  raise
119
123
 
120
124
  # -----------------------------------------------------------------------------
121
- # Callback Manager (untouched from colab_combined.py)
125
+ # Callback Manager
122
126
  # -----------------------------------------------------------------------------
123
127
 
124
128
  class CallbackManager:
@@ -161,7 +165,50 @@ def callback(name: str, is_async: bool = False):
161
165
  return decorator
162
166
 
163
167
  # -----------------------------------------------------------------------------
164
- # Tools Loader (untouched logic but returning dict for tools)
168
+ # ADDITIONAL CALLBACKS
169
+ # -----------------------------------------------------------------------------
170
+ def interaction_callback(message=None, response=None, **kwargs):
171
+ logger.debug(f"[CALLBACK: interaction] Message: {message} | Response: {response}")
172
+ message_queue.put({
173
+ "content": f"[CALLBACK: interaction] Message: {message} | Response: {response}",
174
+ "author": "Callback"
175
+ })
176
+
177
+ def error_callback(message=None, **kwargs):
178
+ logger.error(f"[CALLBACK: error] Message: {message}")
179
+ message_queue.put({
180
+ "content": f"[CALLBACK: error] Message: {message}",
181
+ "author": "Callback"
182
+ })
183
+
184
+ def tool_call_callback(message=None, **kwargs):
185
+ logger.debug(f"[CALLBACK: tool_call] Tool used: {message}")
186
+ message_queue.put({
187
+ "content": f"[CALLBACK: tool_call] Tool used: {message}",
188
+ "author": "Callback"
189
+ })
190
+
191
+ def instruction_callback(message=None, **kwargs):
192
+ logger.debug(f"[CALLBACK: instruction] Instruction: {message}")
193
+ message_queue.put({
194
+ "content": f"[CALLBACK: instruction] Instruction: {message}",
195
+ "author": "Callback"
196
+ })
197
+
198
+ def self_reflection_callback(message=None, **kwargs):
199
+ logger.debug(f"[CALLBACK: self_reflection] Reflection: {message}")
200
+ message_queue.put({
201
+ "content": f"[CALLBACK: self_reflection] Reflection: {message}",
202
+ "author": "Callback"
203
+ })
204
+
205
+ register_display_callback('error', error_callback)
206
+ register_display_callback('tool_call', tool_call_callback)
207
+ register_display_callback('instruction', instruction_callback)
208
+ register_display_callback('self_reflection', self_reflection_callback)
209
+
210
+ # -----------------------------------------------------------------------------
211
+ # Tools Loader
165
212
  # -----------------------------------------------------------------------------
166
213
 
167
214
  def load_tools_from_tools_py():
@@ -172,7 +219,6 @@ def load_tools_from_tools_py():
172
219
  tools_dict = {}
173
220
  try:
174
221
  spec = importlib.util.spec_from_file_location("tools", "tools.py")
175
- logger.info(f"Spec: {spec}")
176
222
  if spec is None:
177
223
  logger.info("tools.py not found in current directory")
178
224
  return tools_dict
@@ -182,7 +228,10 @@ def load_tools_from_tools_py():
182
228
 
183
229
  for name, obj in inspect.getmembers(module):
184
230
  if not name.startswith('_') and callable(obj) and not inspect.isclass(obj):
231
+ # Store the function in globals
185
232
  globals()[name] = obj
233
+
234
+ # Build the function definition
186
235
  tool_def = {
187
236
  "type": "function",
188
237
  "function": {
@@ -190,16 +239,14 @@ def load_tools_from_tools_py():
190
239
  "description": obj.__doc__ or f"Function to {name.replace('_', ' ')}",
191
240
  "parameters": {
192
241
  "type": "object",
193
- "properties": {
194
- "query": {
195
- "type": "string",
196
- "description": "The search query to look up information about"
197
- }
198
- },
199
- "required": ["query"]
242
+ "properties": {},
243
+ "required": []
200
244
  }
201
- }
245
+ },
246
+ # Keep the actual callable as well
247
+ "callable": obj,
202
248
  }
249
+
203
250
  tools_dict[name] = tool_def
204
251
  logger.info(f"Loaded and globalized tool function: {name}")
205
252
 
@@ -209,7 +256,7 @@ def load_tools_from_tools_py():
209
256
  return tools_dict
210
257
 
211
258
  # -----------------------------------------------------------------------------
212
- # Async Queue Processor (untouched logic)
259
+ # Async Queue Processor
213
260
  # -----------------------------------------------------------------------------
214
261
 
215
262
  async def process_message_queue():
@@ -223,7 +270,7 @@ async def process_message_queue():
223
270
  logger.error(f"Error processing message queue: {e}")
224
271
 
225
272
  # -----------------------------------------------------------------------------
226
- # Step & Task Callbacks (Async & Sync) (untouched logic)
273
+ # Step & Task Callbacks
227
274
  # -----------------------------------------------------------------------------
228
275
 
229
276
  async def step_callback(step_details):
@@ -351,9 +398,8 @@ def sync_step_callback_wrapper(step_details):
351
398
  logger.error(f"Error in sync_step_callback_wrapper: {e}", exc_info=True)
352
399
 
353
400
  # -----------------------------------------------------------------------------
354
- # Main PraisonAI Runner (untouched logic)
401
+ # Main PraisonAI Runner
355
402
  # -----------------------------------------------------------------------------
356
-
357
403
  async def ui_run_praisonai(config, topic, tools_dict):
358
404
  logger.info("Starting ui_run_praisonai")
359
405
  agents_map = {}
@@ -370,8 +416,6 @@ async def ui_run_praisonai(config, topic, tools_dict):
370
416
  goal_filled = details['goal'].format(topic=topic)
371
417
  backstory_filled = details['backstory'].format(topic=topic)
372
418
 
373
- await cl.Message(content=f"[DEBUG] Creating agent: {role_name}", author="System").send()
374
-
375
419
  def step_callback_sync(step_details):
376
420
  step_details["agent_name"] = role_name
377
421
  try:
@@ -394,7 +438,8 @@ async def ui_run_praisonai(config, topic, tools_dict):
394
438
  max_rpm=details.get('max_rpm'),
395
439
  max_execution_time=details.get('max_execution_time'),
396
440
  cache=details.get('cache', True),
397
- step_callback=step_callback_sync
441
+ step_callback=step_callback_sync,
442
+ self_reflect=details.get('self_reflect', False)
398
443
  )
399
444
  agents_map[role] = agent
400
445
 
@@ -402,17 +447,35 @@ async def ui_run_praisonai(config, topic, tools_dict):
402
447
  for role, details in config['roles'].items():
403
448
  agent = agents_map[role]
404
449
  role_name = agent.name
450
+
451
+ # -------------------------------------------------------------
452
+ # FIX: Skip empty or invalid tool names to avoid null tool objects
453
+ # -------------------------------------------------------------
405
454
  role_tools = []
455
+ task_tools = [] # Initialize task_tools outside the loop
456
+
406
457
  for tool_name in details.get('tools', []):
458
+ if not tool_name or not tool_name.strip():
459
+ logger.warning("Skipping empty tool name.")
460
+ continue
407
461
  if tool_name in tools_dict:
408
- role_tools.append(tools_dict[tool_name])
462
+ # Create a copy of the tool definition
463
+ tool_def = tools_dict[tool_name].copy()
464
+ # Store the callable separately and remove from definition
465
+ callable_func = tool_def.pop("callable")
466
+ # Add callable to role_tools for task execution
467
+ role_tools.append(callable_func)
468
+ # Add API tool definition to task's tools
469
+ task_tools.append(tool_def)
470
+ # Also set the agent's tools to include both
471
+ agent.tools = role_tools
472
+ else:
473
+ logger.warning(f"Tool '{tool_name}' not found. Skipping.")
409
474
 
410
475
  for tname, tdetails in details.get('tasks', {}).items():
411
476
  description_filled = tdetails['description'].format(topic=topic)
412
477
  expected_output_filled = tdetails['expected_output'].format(topic=topic)
413
478
 
414
- await cl.Message(content=f"[DEBUG] Created task: {tname} for {role_name}", author="System").send()
415
-
416
479
  def task_callback_sync(task_output):
417
480
  try:
418
481
  loop_ = asyncio.new_event_loop()
@@ -426,7 +489,7 @@ async def ui_run_praisonai(config, topic, tools_dict):
426
489
  description=description_filled,
427
490
  expected_output=expected_output_filled,
428
491
  agent=agent,
429
- tools=role_tools,
492
+ tools=task_tools, # Pass API tool definitions
430
493
  async_execution=True,
431
494
  context=[],
432
495
  config=tdetails.get('config', {}),
@@ -454,6 +517,7 @@ async def ui_run_praisonai(config, topic, tools_dict):
454
517
 
455
518
  await cl.Message(content="Starting PraisonAI agents execution...", author="System").send()
456
519
 
520
+ # Decide how to process tasks
457
521
  if config.get('process') == 'hierarchical':
458
522
  prai_agents = PraisonAIAgents(
459
523
  agents=list(agents_map.values()),
@@ -493,10 +557,11 @@ async def ui_run_praisonai(config, topic, tools_dict):
493
557
  raise
494
558
 
495
559
  # -----------------------------------------------------------------------------
496
- # Chainlit Handlers + logic from chainlit_ui.py (no changes in logic)
560
+ # Chainlit Handlers + logic
497
561
  # -----------------------------------------------------------------------------
498
562
 
499
563
  tools_dict = load_tools_from_tools_py()
564
+ print(f"[DEBUG] tools_dict: {tools_dict}")
500
565
 
501
566
  # Load agent config (default) from 'agents.yaml'
502
567
  with open(agent_file, 'r') as f:
@@ -520,9 +585,6 @@ if AUTH_PASSWORD_ENABLED:
520
585
 
521
586
  @cl.set_chat_profiles
522
587
  async def set_profiles(current_user: cl.User):
523
- """
524
- Keep all the same starter logic from chainlit_ui.py.
525
- """
526
588
  return [
527
589
  cl.ChatProfile(
528
590
  name="Auto",
@@ -577,7 +639,6 @@ async def set_profiles(current_user: cl.User):
577
639
  @cl.on_chat_start
578
640
  async def start_chat():
579
641
  try:
580
- # Load model name from database
581
642
  model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
582
643
  cl.user_session.set("model_name", model_name)
583
644
  logger.debug(f"Model name: {model_name}")
@@ -587,16 +648,19 @@ async def start_chat():
587
648
  [{"role": "system", "content": "You are a helpful assistant."}],
588
649
  )
589
650
 
590
- # Create tools.py if it doesn't exist
591
651
  if not os.path.exists("tools.py"):
592
652
  with open("tools.py", "w") as f:
593
653
  f.write("# Add your custom tools here\n")
594
654
 
655
+ if not os.path.exists("agents.yaml"):
656
+ with open("agents.yaml", "w") as f:
657
+ f.write("# Add your custom agents here\n")
658
+
595
659
  settings = await cl.ChatSettings(
596
660
  [
597
661
  TextInput(id="Model", label="OpenAI - Model", initial=model_name),
598
662
  TextInput(id="BaseUrl", label="OpenAI - Base URL", initial=config_list[0]['base_url']),
599
- TextInput(id="ApiKey", label="OpenAI - API Key", initial=config_list[0]['api_key']),
663
+ TextInput(id="ApiKey", label="OpenAI - API Key", initial=config_list[0]['api_key']),
600
664
  Select(
601
665
  id="Framework",
602
666
  label="Framework",
@@ -608,7 +672,7 @@ async def start_chat():
608
672
  cl.user_session.set("settings", settings)
609
673
  chat_profile = cl.user_session.get("chat_profile")
610
674
 
611
- if chat_profile=="Manual":
675
+ if chat_profile == "Manual":
612
676
  agent_file = "agents.yaml"
613
677
  full_agent_file_path = os.path.abspath(agent_file)
614
678
  if os.path.exists(full_agent_file_path):
@@ -628,7 +692,7 @@ async def start_chat():
628
692
  [
629
693
  TextInput(id="Model", label="OpenAI - Model", initial=model_name),
630
694
  TextInput(id="BaseUrl", label="OpenAI - Base URL", initial=config_list[0]['base_url']),
631
- TextInput(id="ApiKey", label="OpenAI - API Key", initial=config_list[0]['api_key']),
695
+ TextInput(id="ApiKey", label="OpenAI - API Key", initial=config_list[0]['api_key']),
632
696
  Select(
633
697
  id="Framework",
634
698
  label="Framework",
@@ -655,13 +719,8 @@ async def start_chat():
655
719
  logger.error(f"Error in start_chat: {str(e)}")
656
720
  await cl.Message(content=f"An error occurred while starting the chat: {str(e)}").send()
657
721
 
658
-
659
722
  @cl.on_chat_resume
660
723
  async def on_chat_resume(thread: ThreadDict):
661
- """
662
- Logic from chainlit_ui.py for chat resume:
663
- - Restore message history from thread steps
664
- """
665
724
  try:
666
725
  message_history = cl.user_session.get("message_history", [])
667
726
  root_messages = [m for m in thread["steps"] if m["parentId"] is None]
@@ -676,27 +735,19 @@ async def on_chat_resume(thread: ThreadDict):
676
735
 
677
736
  @cl.on_message
678
737
  async def main(message: cl.Message):
679
- """
680
- Merged logic from colab_combined.py and chainlit_ui.py for main message:
681
- - Use existing ui_run_praisonai to process user message with PraisonAI
682
- - Keep message history, no changes to logic
683
- """
684
738
  try:
685
739
  logger.info(f"User message: {message.content}")
686
- await cl.Message(
687
- content=f"🔄 Processing your request: {message.content}...",
688
- author="System"
689
- ).send()
690
-
740
+ msg = cl.Message(content="")
741
+ await msg.stream_token(f"🔄 Processing your request: {message.content}...")
742
+
691
743
  # Run PraisonAI
692
744
  result = await ui_run_praisonai(config, message.content, tools_dict)
693
745
 
694
- # Update message history
695
746
  message_history = cl.user_session.get("message_history", [])
696
747
  message_history.append({"role": "user", "content": message.content})
697
748
  message_history.append({"role": "assistant", "content": str(result)})
698
749
  cl.user_session.set("message_history", message_history)
699
-
750
+ await msg.send()
700
751
  except Exception as e:
701
752
  error_msg = f"Error running PraisonAI agents: {str(e)}"
702
753
  logger.error(error_msg, exc_info=True)
@@ -704,14 +755,12 @@ async def main(message: cl.Message):
704
755
 
705
756
  @cl.on_settings_update
706
757
  async def on_settings_update(settings):
707
- """Handle updates to the ChatSettings form."""
708
758
  try:
709
759
  global config_list, framework
710
760
  config_list[0]['model'] = settings["Model"]
711
761
  config_list[0]['base_url'] = settings["BaseUrl"]
712
762
  config_list[0]['api_key'] = settings["ApiKey"]
713
763
 
714
- # Save settings to database with retry
715
764
  for attempt in range(MAX_RETRIES):
716
765
  try:
717
766
  await save_setting_with_retry("model_name", config_list[0]['model'])
@@ -724,7 +773,6 @@ async def on_settings_update(settings):
724
773
  continue
725
774
  raise
726
775
 
727
- # Save to environment variables for compatibility
728
776
  os.environ["OPENAI_API_KEY"] = config_list[0]['api_key']
729
777
  os.environ["OPENAI_MODEL_NAME"] = config_list[0]['model']
730
778
  os.environ["OPENAI_API_BASE"] = config_list[0]['base_url']
@@ -739,7 +787,6 @@ async def on_settings_update(settings):
739
787
  with open("tools.py", "w") as f:
740
788
  f.write(settings["tools"])
741
789
 
742
- # Update thread metadata if exists with retry
743
790
  thread_id = cl.user_session.get("thread_id")
744
791
  if thread_id:
745
792
  for attempt in range(MAX_RETRIES):
@@ -749,6 +796,7 @@ async def on_settings_update(settings):
749
796
  metadata = thread.get("metadata", {})
750
797
  if isinstance(metadata, str):
751
798
  try:
799
+ import json
752
800
  metadata = json.loads(metadata)
753
801
  except json.JSONDecodeError:
754
802
  metadata = {}
@@ -766,10 +814,9 @@ async def on_settings_update(settings):
766
814
  except Exception as e:
767
815
  logger.error(f"Error updating settings: {str(e)}")
768
816
  await cl.Message(content=f"An error occurred while updating settings: {str(e)}. Retrying...").send()
769
- # One final retry after a longer delay
770
817
  try:
771
818
  await asyncio.sleep(RETRY_DELAY * 2)
772
819
  await on_settings_update(settings)
773
820
  except Exception as e:
774
821
  logger.error(f"Final retry failed: {str(e)}")
775
- await cl.Message(content=f"Failed to update settings after retries: {str(e)}").send()
822
+ await cl.Message(content=f"Failed to update settings after retries: {str(e)}").send()
@@ -0,0 +1,133 @@
1
+ # Understanding Tool Integration in AI Agents - A Beginner's Guide
2
+
3
+ ## Overview
4
+ This guide explains how to properly integrate tools (functions) that an AI agent can use, making them both understandable to the OpenAI API and executable by your code.
5
+
6
+ ## Key Components
7
+
8
+ ### 1. Tool Definition Structure
9
+ ```python
10
+ # Example tool definition in tools.py
11
+ def search_tool(query: str) -> list:
12
+ """
13
+ Perform a web search using DuckDuckGo.
14
+
15
+ Args:
16
+ query (str): The search query string.
17
+
18
+ Returns:
19
+ list: Search results with title, url, and snippet.
20
+ """
21
+ # Function implementation...
22
+ ```
23
+
24
+ ### 2. Tool Dictionary Format
25
+ ```python
26
+ tools_dict = {
27
+ 'search_tool': {
28
+ 'type': 'function',
29
+ 'function': {
30
+ 'name': 'search_tool',
31
+ 'description': '...',
32
+ 'parameters': {
33
+ 'type': 'object',
34
+ 'properties': {
35
+ 'query': {'type': 'string'}
36
+ }
37
+ }
38
+ },
39
+ 'callable': search_tool # The actual Python function
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## The Two-Part System
45
+
46
+ ### Part 1: OpenAI API Communication
47
+ ```python
48
+ # task_tools: What OpenAI understands
49
+ task_tools = []
50
+ tool_def = tools_dict[tool_name].copy()
51
+ callable_func = tool_def.pop("callable") # Remove the Python function
52
+ task_tools.append(tool_def) # Add clean JSON-serializable definition
53
+ ```
54
+
55
+ ### Part 2: Function Execution
56
+ ```python
57
+ # role_tools: What your code executes
58
+ role_tools = []
59
+ role_tools.append(callable_func) # Store the actual function
60
+ agent.tools = role_tools # Give agent access to executable functions
61
+ ```
62
+
63
+ ## Putting It All Together
64
+
65
+ ```python
66
+ # Initialize empty lists
67
+ role_tools = [] # For executable functions
68
+ task_tools = [] # For OpenAI API definitions
69
+
70
+ # Process each tool
71
+ for tool_name in tools_list:
72
+ if tool_name in tools_dict:
73
+ # 1. Get the tool definition
74
+ tool_def = tools_dict[tool_name].copy()
75
+
76
+ # 2. Separate the callable function
77
+ callable_func = tool_def.pop("callable")
78
+
79
+ # 3. Store the function for execution
80
+ role_tools.append(callable_func)
81
+
82
+ # 4. Store the API definition
83
+ task_tools.append(tool_def)
84
+
85
+ # 5. Give agent access to functions
86
+ agent.tools = role_tools
87
+
88
+ # Create task with API definitions
89
+ task = Task(
90
+ description="...",
91
+ tools=task_tools, # OpenAI API will use these
92
+ agent=agent, # Agent has access to callable functions
93
+ # ... other parameters ...
94
+ )
95
+ ```
96
+
97
+ ## Why This Works
98
+
99
+ 1. **API Communication**
100
+ - OpenAI API receives clean JSON tool definitions
101
+ - No Python functions that would cause serialization errors
102
+
103
+ 2. **Function Execution**
104
+ - Agent has access to actual Python functions
105
+ - Can execute tools when OpenAI decides to use them
106
+
107
+ 3. **Separation of Concerns**
108
+ - `task_tools`: Describes what tools can do (for OpenAI)
109
+ - `role_tools`: Actually does the work (for Python)
110
+
111
+ ## Common Errors and Solutions
112
+
113
+ 1. **"Invalid type for 'tools[0]'"**
114
+ - Cause: Sending null or invalid tool definition to OpenAI
115
+ - Solution: Use proper tool definition format in `task_tools`
116
+
117
+ 2. **"Object of type function is not JSON serializable"**
118
+ - Cause: Trying to send Python function to OpenAI API
119
+ - Solution: Remove callable function from API definition
120
+
121
+ 3. **"Tool is not callable"**
122
+ - Cause: Agent doesn't have access to executable functions
123
+ - Solution: Set `agent.tools = role_tools`
124
+
125
+ ## Best Practices
126
+
127
+ 1. Always initialize both `task_tools` and `role_tools` lists
128
+ 2. Make clean copies of tool definitions to avoid modifying originals
129
+ 3. Keep tool definitions JSON-serializable for API communication
130
+ 4. Ensure agents have access to callable functions
131
+ 5. Document tool parameters and return values clearly
132
+
133
+ This structure maintains clean separation between API communication and actual function execution, making your AI agent system both reliable and maintainable.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PraisonAI"
3
- version = "2.0.26"
3
+ version = "2.0.28"
4
4
  description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human–agent collaboration."
5
5
  readme = "README.md"
6
6
  license = ""
@@ -84,7 +84,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.7", "crewai"]
84
84
 
85
85
  [tool.poetry]
86
86
  name = "PraisonAI"
87
- version = "2.0.26"
87
+ version = "2.0.28"
88
88
  description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human–agent collaboration."
89
89
  authors = ["Mervin Praison"]
90
90
  license = ""