praisonaiagents 0.0.16__tar.gz → 0.0.17__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.
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/PKG-INFO +1 -1
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/agent/agent.py +151 -3
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/agents/agents.py +205 -4
- praisonaiagents-0.0.17/praisonaiagents/process/process.py +534 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/task/task.py +13 -3
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents.egg-info/PKG-INFO +1 -1
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/pyproject.toml +1 -1
- praisonaiagents-0.0.16/praisonaiagents/process/process.py +0 -263
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/agent/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/agents/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/agent/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/agent/agent.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/agents/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/agents/agents.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/main.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/task/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/build/lib/praisonaiagents/task/task.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/main.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/process/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents/task/__init__.py +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents.egg-info/SOURCES.txt +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents.egg-info/dependency_links.txt +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents.egg-info/requires.txt +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/praisonaiagents.egg-info/top_level.txt +0 -0
- {praisonaiagents-0.0.16 → praisonaiagents-0.0.17}/setup.cfg +0 -0
@@ -1,9 +1,12 @@
|
|
1
|
-
import
|
2
|
-
import json
|
1
|
+
import os
|
3
2
|
import time
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
import asyncio
|
4
6
|
from typing import List, Optional, Any, Dict, Union, Literal
|
5
7
|
from rich.console import Console
|
6
8
|
from rich.live import Live
|
9
|
+
from openai import AsyncOpenAI
|
7
10
|
from ..main import (
|
8
11
|
display_error,
|
9
12
|
display_tool_call,
|
@@ -192,6 +195,12 @@ class Agent:
|
|
192
195
|
self.min_reflect = min_reflect
|
193
196
|
self.reflect_llm = reflect_llm
|
194
197
|
self.console = Console() # Create a single console instance for the agent
|
198
|
+
|
199
|
+
# Initialize system prompt
|
200
|
+
self.system_prompt = f"""{self.backstory}\n
|
201
|
+
Your Role: {self.role}\n
|
202
|
+
Your Goal: {self.goal}
|
203
|
+
"""
|
195
204
|
|
196
205
|
def execute_tool(self, function_name, arguments):
|
197
206
|
"""
|
@@ -536,4 +545,143 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
|
536
545
|
cleaned = cleaned[len("```"):].strip()
|
537
546
|
if cleaned.endswith("```"):
|
538
547
|
cleaned = cleaned[:-3].strip()
|
539
|
-
return cleaned
|
548
|
+
return cleaned
|
549
|
+
|
550
|
+
async def achat(self, prompt, temperature=0.2, tools=None, output_json=None):
|
551
|
+
"""Async version of chat method"""
|
552
|
+
try:
|
553
|
+
# Build system prompt
|
554
|
+
system_prompt = self.system_prompt
|
555
|
+
if output_json:
|
556
|
+
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
|
557
|
+
|
558
|
+
# Build messages
|
559
|
+
if isinstance(prompt, str):
|
560
|
+
messages = [
|
561
|
+
{"role": "system", "content": system_prompt},
|
562
|
+
{"role": "user", "content": prompt + ("\nReturn ONLY a valid JSON object. No other text or explanation." if output_json else "")}
|
563
|
+
]
|
564
|
+
else:
|
565
|
+
# For multimodal prompts
|
566
|
+
messages = [
|
567
|
+
{"role": "system", "content": system_prompt},
|
568
|
+
{"role": "user", "content": prompt}
|
569
|
+
]
|
570
|
+
if output_json:
|
571
|
+
# Add JSON instruction to text content
|
572
|
+
for item in messages[-1]["content"]:
|
573
|
+
if item["type"] == "text":
|
574
|
+
item["text"] += "\nReturn ONLY a valid JSON object. No other text or explanation."
|
575
|
+
break
|
576
|
+
|
577
|
+
# Format tools if provided
|
578
|
+
formatted_tools = []
|
579
|
+
if tools:
|
580
|
+
for tool in tools:
|
581
|
+
if isinstance(tool, str):
|
582
|
+
tool_def = self._generate_tool_definition(tool)
|
583
|
+
if tool_def:
|
584
|
+
formatted_tools.append(tool_def)
|
585
|
+
elif isinstance(tool, dict):
|
586
|
+
formatted_tools.append(tool)
|
587
|
+
elif hasattr(tool, "to_openai_tool"):
|
588
|
+
formatted_tools.append(tool.to_openai_tool())
|
589
|
+
elif callable(tool):
|
590
|
+
formatted_tools.append(self._generate_tool_definition(tool.__name__))
|
591
|
+
|
592
|
+
# Create async OpenAI client
|
593
|
+
async_client = AsyncOpenAI()
|
594
|
+
|
595
|
+
# Make the API call based on the type of request
|
596
|
+
if tools:
|
597
|
+
response = await async_client.chat.completions.create(
|
598
|
+
model=self.llm,
|
599
|
+
messages=messages,
|
600
|
+
temperature=temperature,
|
601
|
+
tools=formatted_tools
|
602
|
+
)
|
603
|
+
return await self._achat_completion(response, tools)
|
604
|
+
elif output_json:
|
605
|
+
response = await async_client.chat.completions.create(
|
606
|
+
model=self.llm,
|
607
|
+
messages=messages,
|
608
|
+
temperature=temperature,
|
609
|
+
response_format={"type": "json_object"}
|
610
|
+
)
|
611
|
+
result = response.choices[0].message.content
|
612
|
+
# Clean and parse the JSON response
|
613
|
+
cleaned_json = self.clean_json_output(result)
|
614
|
+
try:
|
615
|
+
parsed = json.loads(cleaned_json)
|
616
|
+
return output_json(**parsed)
|
617
|
+
except Exception as e:
|
618
|
+
display_error(f"Error parsing JSON response: {e}")
|
619
|
+
return None
|
620
|
+
else:
|
621
|
+
response = await async_client.chat.completions.create(
|
622
|
+
model=self.llm,
|
623
|
+
messages=messages,
|
624
|
+
temperature=temperature
|
625
|
+
)
|
626
|
+
return response.choices[0].message.content
|
627
|
+
except Exception as e:
|
628
|
+
display_error(f"Error in chat completion: {e}")
|
629
|
+
return None
|
630
|
+
|
631
|
+
async def _achat_completion(self, response, tools):
|
632
|
+
"""Async version of _chat_completion method"""
|
633
|
+
try:
|
634
|
+
message = response.choices[0].message
|
635
|
+
if not hasattr(message, 'tool_calls') or not message.tool_calls:
|
636
|
+
return message.content
|
637
|
+
|
638
|
+
results = []
|
639
|
+
for tool_call in message.tool_calls:
|
640
|
+
try:
|
641
|
+
function_name = tool_call.function.name
|
642
|
+
arguments = json.loads(tool_call.function.arguments)
|
643
|
+
|
644
|
+
# Find the matching tool
|
645
|
+
tool = next((t for t in tools if t.__name__ == function_name), None)
|
646
|
+
if not tool:
|
647
|
+
display_error(f"Tool {function_name} not found")
|
648
|
+
continue
|
649
|
+
|
650
|
+
# Check if the tool is async
|
651
|
+
if asyncio.iscoroutinefunction(tool):
|
652
|
+
result = await tool(**arguments)
|
653
|
+
else:
|
654
|
+
# Run sync function in executor to avoid blocking
|
655
|
+
loop = asyncio.get_event_loop()
|
656
|
+
result = await loop.run_in_executor(None, lambda: tool(**arguments))
|
657
|
+
|
658
|
+
results.append(result)
|
659
|
+
except Exception as e:
|
660
|
+
display_error(f"Error executing tool {function_name}: {e}")
|
661
|
+
results.append(None)
|
662
|
+
|
663
|
+
# If we have results, format them into a response
|
664
|
+
if results:
|
665
|
+
formatted_results = "\n".join([str(r) for r in results if r is not None])
|
666
|
+
if formatted_results:
|
667
|
+
messages = [
|
668
|
+
{"role": "system", "content": self.system_prompt},
|
669
|
+
{"role": "assistant", "content": "Here are the tool results:"},
|
670
|
+
{"role": "user", "content": formatted_results + "\nPlease process these results and provide a final response."}
|
671
|
+
]
|
672
|
+
try:
|
673
|
+
async_client = AsyncOpenAI()
|
674
|
+
final_response = await async_client.chat.completions.create(
|
675
|
+
model=self.llm,
|
676
|
+
messages=messages,
|
677
|
+
temperature=0.2
|
678
|
+
)
|
679
|
+
return final_response.choices[0].message.content
|
680
|
+
except Exception as e:
|
681
|
+
display_error(f"Error in final chat completion: {e}")
|
682
|
+
return formatted_results
|
683
|
+
return formatted_results
|
684
|
+
return None
|
685
|
+
except Exception as e:
|
686
|
+
display_error(f"Error in _achat_completion: {e}")
|
687
|
+
return None
|
@@ -11,6 +11,7 @@ from ..main import display_error, TaskOutput, error_logs, client
|
|
11
11
|
from ..agent.agent import Agent
|
12
12
|
from ..task.task import Task
|
13
13
|
from ..process.process import Process, LoopItems
|
14
|
+
import asyncio
|
14
15
|
|
15
16
|
def encode_file_to_base64(file_path: str) -> str:
|
16
17
|
"""Base64-encode a file."""
|
@@ -82,7 +83,8 @@ class PraisonAIAgents:
|
|
82
83
|
return True
|
83
84
|
return len(agent_output.strip()) > 0
|
84
85
|
|
85
|
-
def
|
86
|
+
async def aexecute_task(self, task_id):
|
87
|
+
"""Async version of execute_task method"""
|
86
88
|
if task_id not in self.tasks:
|
87
89
|
display_error(f"Error: Task with ID {task_id} does not exist")
|
88
90
|
return
|
@@ -159,12 +161,12 @@ Expected Output: {task.expected_output}.
|
|
159
161
|
})
|
160
162
|
return content
|
161
163
|
|
162
|
-
agent_output = executor_agent.
|
164
|
+
agent_output = await executor_agent.achat(
|
163
165
|
_get_multimodal_message(task_prompt, task.images),
|
164
166
|
tools=task.tools
|
165
167
|
)
|
166
168
|
else:
|
167
|
-
agent_output = executor_agent.
|
169
|
+
agent_output = await executor_agent.achat(task_prompt, tools=task.tools)
|
168
170
|
|
169
171
|
if agent_output:
|
170
172
|
task_output = TaskOutput(
|
@@ -202,6 +204,83 @@ Expected Output: {task.expected_output}.
|
|
202
204
|
task.status = "failed"
|
203
205
|
return None
|
204
206
|
|
207
|
+
async def arun_task(self, task_id):
|
208
|
+
"""Async version of run_task method"""
|
209
|
+
if task_id not in self.tasks:
|
210
|
+
display_error(f"Error: Task with ID {task_id} does not exist")
|
211
|
+
return
|
212
|
+
task = self.tasks[task_id]
|
213
|
+
if task.status == "completed":
|
214
|
+
logging.info(f"Task with ID {task_id} is already completed")
|
215
|
+
return
|
216
|
+
|
217
|
+
retries = 0
|
218
|
+
while task.status != "completed" and retries < self.max_retries:
|
219
|
+
logging.debug(f"Attempt {retries+1} for task {task_id}")
|
220
|
+
if task.status in ["not started", "in progress"]:
|
221
|
+
task_output = await self.aexecute_task(task_id)
|
222
|
+
if task_output and self.completion_checker(task, task_output.raw):
|
223
|
+
task.status = "completed"
|
224
|
+
if task.callback:
|
225
|
+
await task.execute_callback(task_output)
|
226
|
+
self.save_output_to_file(task, task_output)
|
227
|
+
if self.verbose >= 1:
|
228
|
+
logging.info(f"Task {task_id} completed successfully.")
|
229
|
+
else:
|
230
|
+
task.status = "in progress"
|
231
|
+
if self.verbose >= 1:
|
232
|
+
logging.info(f"Task {task_id} not completed, retrying")
|
233
|
+
await asyncio.sleep(1)
|
234
|
+
retries += 1
|
235
|
+
else:
|
236
|
+
if task.status == "failed":
|
237
|
+
logging.info("Task is failed, resetting to in-progress for another try...")
|
238
|
+
task.status = "in progress"
|
239
|
+
else:
|
240
|
+
logging.info("Invalid Task status")
|
241
|
+
break
|
242
|
+
|
243
|
+
if retries == self.max_retries and task.status != "completed":
|
244
|
+
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
245
|
+
|
246
|
+
async def arun_all_tasks(self):
|
247
|
+
"""Async version of run_all_tasks method"""
|
248
|
+
process = Process(
|
249
|
+
tasks=self.tasks,
|
250
|
+
agents=self.agents,
|
251
|
+
manager_llm=self.manager_llm,
|
252
|
+
verbose=self.verbose
|
253
|
+
)
|
254
|
+
|
255
|
+
if self.process == "workflow":
|
256
|
+
async for task_id in process.aworkflow():
|
257
|
+
if self.tasks[task_id].async_execution:
|
258
|
+
await self.arun_task(task_id)
|
259
|
+
else:
|
260
|
+
self.run_task(task_id)
|
261
|
+
elif self.process == "sequential":
|
262
|
+
async for task_id in process.asequential():
|
263
|
+
if self.tasks[task_id].async_execution:
|
264
|
+
await self.arun_task(task_id)
|
265
|
+
else:
|
266
|
+
self.run_task(task_id)
|
267
|
+
elif self.process == "hierarchical":
|
268
|
+
async for task_id in process.ahierarchical():
|
269
|
+
if isinstance(task_id, Task):
|
270
|
+
task_id = self.add_task(task_id)
|
271
|
+
if self.tasks[task_id].async_execution:
|
272
|
+
await self.arun_task(task_id)
|
273
|
+
else:
|
274
|
+
self.run_task(task_id)
|
275
|
+
|
276
|
+
async def astart(self):
|
277
|
+
"""Async version of start method"""
|
278
|
+
await self.arun_all_tasks()
|
279
|
+
return {
|
280
|
+
"task_status": self.get_all_tasks_status(),
|
281
|
+
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
|
282
|
+
}
|
283
|
+
|
205
284
|
def save_output_to_file(self, task, task_output):
|
206
285
|
if task.output_file:
|
207
286
|
try:
|
@@ -214,7 +293,129 @@ Expected Output: {task.expected_output}.
|
|
214
293
|
except Exception as e:
|
215
294
|
display_error(f"Error saving task output to file: {e}")
|
216
295
|
|
296
|
+
def execute_task(self, task_id):
|
297
|
+
"""Synchronous version of execute_task method"""
|
298
|
+
if task_id not in self.tasks:
|
299
|
+
display_error(f"Error: Task with ID {task_id} does not exist")
|
300
|
+
return
|
301
|
+
task = self.tasks[task_id]
|
302
|
+
|
303
|
+
# Only import multimodal dependencies if task has images
|
304
|
+
if task.images and task.status == "not started":
|
305
|
+
try:
|
306
|
+
import cv2
|
307
|
+
import base64
|
308
|
+
from moviepy import VideoFileClip
|
309
|
+
except ImportError as e:
|
310
|
+
display_error(f"Error: Missing required dependencies for image/video processing: {e}")
|
311
|
+
display_error("Please install with: pip install opencv-python moviepy")
|
312
|
+
task.status = "failed"
|
313
|
+
return None
|
314
|
+
|
315
|
+
if task.status == "not started":
|
316
|
+
task.status = "in progress"
|
317
|
+
|
318
|
+
executor_agent = task.agent
|
319
|
+
|
320
|
+
task_prompt = f"""
|
321
|
+
You need to do the following task: {task.description}.
|
322
|
+
Expected Output: {task.expected_output}.
|
323
|
+
"""
|
324
|
+
if task.context:
|
325
|
+
context_results = ""
|
326
|
+
for context_task in task.context:
|
327
|
+
if context_task.result:
|
328
|
+
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
|
329
|
+
else:
|
330
|
+
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
|
331
|
+
task_prompt += f"""
|
332
|
+
Here are the results of previous tasks that might be useful:\n
|
333
|
+
{context_results}
|
334
|
+
"""
|
335
|
+
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
|
336
|
+
|
337
|
+
if self.verbose >= 2:
|
338
|
+
logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
339
|
+
logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
340
|
+
|
341
|
+
if task.images:
|
342
|
+
def _get_multimodal_message(text_prompt, images):
|
343
|
+
content = [{"type": "text", "text": text_prompt}]
|
344
|
+
|
345
|
+
for img in images:
|
346
|
+
# If local file path for a valid image
|
347
|
+
if os.path.exists(img):
|
348
|
+
ext = os.path.splitext(img)[1].lower()
|
349
|
+
# If it's a .mp4, convert to frames
|
350
|
+
if ext == ".mp4":
|
351
|
+
frames = process_video(img, seconds_per_frame=1)
|
352
|
+
content.append({"type": "text", "text": "These are frames from the video."})
|
353
|
+
for f in frames:
|
354
|
+
content.append({
|
355
|
+
"type": "image_url",
|
356
|
+
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
357
|
+
})
|
358
|
+
else:
|
359
|
+
encoded = encode_file_to_base64(img)
|
360
|
+
content.append({
|
361
|
+
"type": "image_url",
|
362
|
+
"image_url": {
|
363
|
+
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
364
|
+
}
|
365
|
+
})
|
366
|
+
else:
|
367
|
+
# Treat as a remote URL
|
368
|
+
content.append({
|
369
|
+
"type": "image_url",
|
370
|
+
"image_url": {"url": img}
|
371
|
+
})
|
372
|
+
return content
|
373
|
+
|
374
|
+
agent_output = executor_agent.chat(
|
375
|
+
_get_multimodal_message(task_prompt, task.images),
|
376
|
+
tools=task.tools
|
377
|
+
)
|
378
|
+
else:
|
379
|
+
agent_output = executor_agent.chat(task_prompt, tools=task.tools)
|
380
|
+
|
381
|
+
if agent_output:
|
382
|
+
task_output = TaskOutput(
|
383
|
+
description=task.description,
|
384
|
+
summary=task.description[:10],
|
385
|
+
raw=agent_output,
|
386
|
+
agent=executor_agent.name,
|
387
|
+
output_format="RAW"
|
388
|
+
)
|
389
|
+
|
390
|
+
if task.output_json:
|
391
|
+
cleaned = self.clean_json_output(agent_output)
|
392
|
+
try:
|
393
|
+
parsed = json.loads(cleaned)
|
394
|
+
task_output.json_dict = parsed
|
395
|
+
task_output.output_format = "JSON"
|
396
|
+
except:
|
397
|
+
logging.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
398
|
+
logging.debug(f"Output that failed JSON parsing: {agent_output}")
|
399
|
+
|
400
|
+
if task.output_pydantic:
|
401
|
+
cleaned = self.clean_json_output(agent_output)
|
402
|
+
try:
|
403
|
+
parsed = json.loads(cleaned)
|
404
|
+
pyd_obj = task.output_pydantic(**parsed)
|
405
|
+
task_output.pydantic = pyd_obj
|
406
|
+
task_output.output_format = "Pydantic"
|
407
|
+
except:
|
408
|
+
logging.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
409
|
+
logging.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
410
|
+
|
411
|
+
task.result = task_output
|
412
|
+
return task_output
|
413
|
+
else:
|
414
|
+
task.status = "failed"
|
415
|
+
return None
|
416
|
+
|
217
417
|
def run_task(self, task_id):
|
418
|
+
"""Synchronous version of run_task method"""
|
218
419
|
if task_id not in self.tasks:
|
219
420
|
display_error(f"Error: Task with ID {task_id} does not exist")
|
220
421
|
return
|
@@ -253,7 +454,7 @@ Expected Output: {task.expected_output}.
|
|
253
454
|
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
254
455
|
|
255
456
|
def run_all_tasks(self):
|
256
|
-
"""
|
457
|
+
"""Synchronous version of run_all_tasks method"""
|
257
458
|
process = Process(
|
258
459
|
tasks=self.tasks,
|
259
460
|
agents=self.agents,
|