Compare commits

...
29 Commits
Author SHA1 Message Date
Mervin Praison 9f62e9201a Merge pull request #339 from MervinPraison/develop
2.0.60
2025-01-29 05:59:25 +00:00
MervinPraison 1b69360d9f Bump PraisonAI version to 2.0.60 and update dependencies
- Updated version to 2.0.60 in Homebrew formula, pyproject.toml, and Docker files
- Upgraded praisonaiagents dependency from 0.0.56 to 0.0.60
- Added new documentation for generating reasoning data
- Included example script for chain-of-thought data generation
- Enhanced workflow task management and logging in process modules

The changes improve the framework's version management, documentation, and provide new tools for synthetic reasoning data generation.
2025-01-29 05:58:58 +00:00
MervinPraison 9bdc6545ad Enhance Workflow Task Management with Workflow Finished Flag and Improved Routing
- Added `workflow_finished` flag to `Process` class to enable early workflow termination
- Implemented more robust task routing logic in `process.py` for decision and loop tasks
- Updated task reset mechanism to respect `rerun` parameter
- Improved context clearing and task status management
- Enhanced logging for workflow progression and task transitions

The changes provide more flexible and controlled workflow execution, with better handling of task conditions and early termination scenarios.
2025-01-28 16:40:22 +00:00
MervinPraison 06cd909367 Add max_iter parameter and enhanced workflow task management with fallback mechanism
- Updated `orchestrator-workers.py` to include `max_iter=10` parameter in workflow initialization
- Enhanced `process.py` with a new `_find_next_not_started_task()` method to provide fallback task selection
- Implemented comprehensive logging for workflow cycles, task status, and iteration tracking
- Added retry counter and fallback mechanism to handle scenarios with incomplete or stalled tasks
- Improved workflow termination logic with detailed final summary logging

The changes improve workflow reliability, prevent potential infinite loops, and provide more robust task management in complex multi-agent scenarios.
2025-01-28 11:26:32 +00:00
MervinPraison 67aeece7fc Bump praisonaiagents package version to 0.0.58
- Updated version in `pyproject.toml` and `uv.lock`
- Incremented package version for continued development and improvements
2025-01-27 19:19:40 +00:00
MervinPraison ef67d0753c Enhance Workflow Task Management with Detailed Logging and Loop Task Handling
- Updated `.cursorrules` with comprehensive technical overview of workflow execution challenges
- Refactored `cot-agents.py` to:
  - Add Pydantic model for structured output
  - Improve task description and condition handling
  - Add output validation for chain of thought generation
- Enhanced `process.py` with:
  - Extensive debug logging for task execution
  - Improved loop task and subtask management
  - More detailed logging of task status and progression

The changes improve workflow reliability, provide deeper insights into task execution, and prevent infinite loops in complex task workflows.
2025-01-27 19:18:49 +00:00
MervinPraison f374f9f791 Enhance COT Agent Workflow with Improved Task Management and Logging
- Refactored `cot-agents.py` to include:
  - New `write_csv` and `count_questions` functions
  - Added `TotalQuestionsEvaluator` agent for tracking question count
  - Improved task workflow with decision and loop task types
- Updated `process.py` to support:
  - Enhanced logging for task execution
  - Better handling of loop and decision tasks
  - Improved subtask creation and management
- Modified `csv_tools.py` to support more flexible CSV writing with string input
- Implemented more robust task progression and status tracking

The changes improve the flexibility, logging, and task management of the Chain of Thought agent workflow.
2025-01-27 13:59:59 +00:00
MervinPraison 492fb29af9 Enhance Chain of Thought (COT) agent workflow with batch processing and Hugging Face integration
- Added `cot-agents-batch.py` to support batch processing of Q&A pairs
- Updated `cot-agents.py` with a more comprehensive workflow including:
  - Question generation
  - Chain of Thought solution generation
  - Hugging Face dataset upload
- Refined `generatecot.py` with improvements to solution generation:
  - Reduced max attempts
  - Adjusted scoring thresholds
  - Added direct QA pair tracking

The changes improve the flexibility and automation of Chain of Thought solution generation and dataset management.
2025-01-27 02:52:14 +00:00
MervinPraison cd56ebd5cf Add Chain of Thought (COT) generation tools and update project structure
- Introduced comprehensive `generatecot.py` in `praisonaiagents/tools/train/data/` with advanced Chain of Thought solution generation capabilities
- Added multiple COT-related tools and methods:
  - Solution generation and improvement
  - Answer verification
  - Error detection and correction
  - Export to various formats (JSON, CSV, Alpaca)
  - HuggingFace dataset upload support
- Updated tool mappings in `__init__.py` to include COT-related tools
- Moved existing `generatecot.py` from `train/data/` to `tools/train/data/`
- Bumped project version to 0.0.57 in relevant files

The changes enhance the framework's capabilities for generating and managing step-by-step problem-solving solutions with AI-powered reasoning.
2025-01-26 07:22:32 +00:00
MervinPraison 08e99f6ef7 Enhance GenerateCOT with dictionary-based solution generation and Alpaca format export
- Added new methods to generate solutions in dictionary format:
  - `get_solution_dict()` for step-by-step problem solving
  - `improve_solution_dict()` to refine existing solutions
  - `start_dict()` to generate and validate solutions
- Implemented `export_alpaca_format()` to convert solutions to Alpaca training format
- Updated type hints and added example usage for new methods
- Extended the script with additional QA data processing and export demonstration

The changes improve the flexibility and usability of the GenerateCOT class for solution generation and data preparation.
2025-01-25 08:22:08 +00:00
MervinPraison 670d653ae9 Add GenerateCOT class for step-by-step solution generation and verification
- Created a new Python script `generatecot.py` in the train/data directory
- Implemented a comprehensive solution generation class with features like:
  - Step-by-step problem solving
  - Answer verification
  - Solution improvement
  - Error detection and correction
- Added support for loading and saving solutions with JSON
- Included an example usage demonstrating the class functionality

The GenerateCOT (Chain of Thought) class provides a robust mechanism for generating and refining solutions to various problems using AI-powered reasoning.
2025-01-25 07:47:20 +00:00
Mervin Praison faa014b8b0 Merge pull request #333 from MervinPraison/develop
2.0.59
2025-01-24 17:17:53 +00:00
MervinPraison 6d454d0c6e Update PraisonAI to version 2.0.59 and enhance dependencies
- Bumped PraisonAI version from 2.0.58 to 2.0.59 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonaiagents` dependency version from `0.0.53` to `0.0.56` across relevant files for improved functionality.
- Modified agent task descriptions in `repetitive-agents.py` to clarify expected outputs.
- Enhanced the `process.py` file to improve task handling and expected output management.

These changes aim to refine the PraisonAI framework's capabilities and ensure compatibility with the latest dependencies.
2025-01-24 17:17:32 +00:00
MervinPraison 0ec3fe3510 Add new agent scripts and update version to 0.0.55
- Introduced three new Python scripts: `deepseek-reasoning-output-tokens.py`, `llm-deepseek-reasoning-steps.py`, and `llm-langchain-toolcall.py` to enhance reasoning capabilities and multi-source information retrieval.
- Updated `pyproject.toml` and `uv.lock` to reflect the new version 0.0.55.
- Enhanced `wikipedia-agent.py` to utilize the new `gpt-4o-mini` model for improved performance.
- Improved `agent.py` to support tool calls in chat completions, refining the overall functionality of the agent framework.

These changes aim to expand the capabilities of the PraisonAI framework by integrating new reasoning features and improving user experience with updated versioning.
2025-01-24 13:04:34 +00:00
MervinPraison 707888cdf9 Remove deprecated agent scripts and update version to 0.0.54
- Deleted `any-llm-agents.py` and `any-llm-agents-advanced.py` scripts to streamline the agent framework.
- Updated `pyproject.toml` and `uv.lock` to reflect the new version 0.0.54.
- Enhanced `agent.py` and `llm.py` to improve handling of reasoning steps by ensuring `reasoning_steps` is properly managed in API calls.

These changes aim to simplify the agent framework and improve the overall functionality of the PraisonAI system.
2025-01-24 10:35:42 +00:00
Mervin Praison 42051630bc Merge pull request #331 from MervinPraison/develop
Develop
2025-01-23 17:19:08 +00:00
MervinPraison bfcccb6355 Update PraisonAI to version 2.0.58 and enhance reasoning capabilities
- Bumped PraisonAI version from 2.0.57 to 2.0.58 in `Dockerfile`, `pyproject.toml`, and `uv.lock` to reflect the latest updates.
- Updated the `praisonaiagents` dependency version from `0.0.50` to `0.0.53` across relevant files for improved functionality.
- Enhanced reasoning features by modifying agent initialization parameters to use `reasoning_steps` instead of `show_reasoning`, improving clarity in agent behavior.
- Introduced a new script `deepseek-reasoning-multi-agents.py` for multi-agent reasoning tasks, showcasing the integration of reasoning agents.
- Added documentation for new reasoning features, including a detailed guide on creating reasoning extract agents.

These changes aim to refine the reasoning capabilities and improve the overall user experience within the PraisonAI framework.
2025-01-23 17:18:54 +00:00
MervinPraison e329afc394 Refactor reasoning capabilities and update version to 0.0.52
- Removed the `any-llm-think.py` script to streamline the agent framework.
- Enhanced `deepseek-reasoning-agents.py` to include reasoning display options and updated the method for starting agents.
- Updated `pyproject.toml` and `uv.lock` to reflect the new version 0.0.52.
- Improved `agent.py` and `llm.py` to support reasoning content in both synchronous and asynchronous calls, enhancing the overall functionality of the agent framework.

These changes aim to refine the reasoning features and improve the user experience within the PraisonAI framework.
2025-01-23 13:52:58 +00:00
MervinPraison 9417cc6457 Add new reasoning agents and update version to 0.0.51
- Introduced three new Python scripts: `any-llm-think.py`, `deepseek-reasoning-agents.py`, and `deepseek-think.py` for enhanced reasoning capabilities using the `deepseek-reasoner` model.
- Updated `pyproject.toml` and `uv.lock` to reflect the new version 0.0.51.
- Enhanced the `agent.py` file with new data classes and methods to support reasoning content in chat completions, improving the overall functionality of the agent framework.

These changes aim to expand the capabilities of the PraisonAI framework by integrating reasoning features and improving the user experience with updated versioning.
2025-01-23 10:52:15 +00:00
Mervin Praison 10a3d279ae Merge pull request #329 from MervinPraison/develop
Update index.mdx description to enhance clarity and detail
2025-01-23 06:40:49 +00:00
MervinPraison 2a916208e1 Update index.mdx description to enhance clarity and detail
- Revised the description of PraisonAI to emphasize its capabilities as a production-ready Multi-AI Agents framework with self-reflection.
- Highlighted the integration of PraisonAI Agents, AutoGen, and CrewAI as a low-code solution for automating tasks and solving complex challenges.
- Focused on the framework's strengths in simplicity, customization, and effective human-agent collaboration.

These changes aim to provide users with a clearer understanding of PraisonAI's functionalities and benefits.
2025-01-23 06:40:23 +00:00
Mervin Praison bcd0bbbd24 Merge pull request #328 from MervinPraison/develop
Refactor documentation for agent setup and usage
2025-01-23 06:38:06 +00:00
MervinPraison 5701a40f11 Refactor documentation for agent setup and usage
- Updated the introduction.mdx file to streamline the steps for installing the PraisonAI package and configuring the environment.
- Revised agent creation examples to simplify the process for both single and multiple agents, enhancing clarity and usability.
- Added a new section for creating an `agents.yaml` configuration file for no-code users, including automatic generation instructions.
- Improved the overall structure and readability of the documentation to facilitate a better user experience.

These changes aim to provide clearer guidance for users in setting up and utilizing the PraisonAI framework effectively.
2025-01-23 06:37:49 +00:00
Mervin Praison 3ffac395bb Merge pull request #327 from MervinPraison/develop
Update README and documentation with new video tutorials
2025-01-23 06:34:56 +00:00
MervinPraison 37119aa3ac Update README and documentation with new video tutorials
- Removed the License section from README.md to streamline content.
- Added a comprehensive list of video tutorials to docs/index.mdx, covering various AI agent functionalities such as self-reflection, reasoning, and multimodal capabilities.
- Enhanced the documentation's educational resources, providing users with visual aids to better understand the Praison AI framework.

These changes aim to improve user engagement and learning through accessible video content.
2025-01-23 06:34:36 +00:00
Mervin Praison 8b2d324d3b Merge pull request #326 from MervinPraison/develop
Enhance README with new AI agent video resources
2025-01-23 06:30:01 +00:00
MervinPraison 6d0b29e60d Enhance README with new AI agent video resources
- Added multiple new video links to the README.md, showcasing various AI agent functionalities including self-reflection, reasoning, multimodal capabilities, and more.
- Improved the documentation's educational value by providing visual resources for users to better understand the Praison AI framework.

These changes aim to enrich the user experience by offering accessible learning materials related to AI agents.
2025-01-23 06:29:44 +00:00
Mervin Praison 583cbc5efa Merge pull request #325 from MervinPraison/develop
Update documentation and examples for agent initialization and usage
2025-01-23 06:21:43 +00:00
MervinPraison 90dd84a152 Update documentation and examples for agent initialization and usage
- Revised the README.md to reflect the updated method for starting agents, changing `agent.chat` to `agent.start` for clarity and accuracy.
- Added a new section in the README.md for creating an `agents.yaml` file, enhancing the setup instructions for users.
- Updated the installation instructions in `docs/index.mdx` to clarify package names and improve the overall structure of the documentation.
- Streamlined code examples in `docs/concepts/agents.mdx` to focus on single and multiple agent initialization, ensuring users have clear and concise guidance.

These changes improve the clarity and usability of the documentation, making it easier for users to understand and implement the PraisonAI framework.
2025-01-23 06:21:25 +00:00
43 changed files with 3956 additions and 819 deletions

No files matched your search

+11 -5
View File
@@ -71,7 +71,7 @@ Create app.py file and add the code below:
```python
from praisonaiagents import Agent
agent = Agent(instructions="Your are a helpful AI assistant")
agent.chat("Write a movie script about a robot in Mars")
agent.start("Write a movie script about a robot in Mars")
```
Run:
@@ -472,6 +472,8 @@ export OPENAI_BASE_URL=https://api.groq.com/openai/v1
### Simple Playbook Example
Create `agents.yaml` file and add the code below:
```yaml
framework: praisonai
topic: Artificial Intelligence
@@ -544,14 +546,18 @@ uv pip install -r pyproject.toml --extra "crewai,autogen"
[![Star History Chart](https://api.star-history.com/svg?repos=MervinPraison/PraisonAI&type=Date)](https://docs.praison.ai)
## License
Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
## Video Tutorials
| Topic | Video |
|-------|--------|
| AI Agents with Self Reflection | [![Self Reflection](https://img.youtube.com/vi/vLXobEN2Vc8/0.jpg)](https://www.youtube.com/watch?v=vLXobEN2Vc8) |
| AI Agents with Reasoning | [![Reasoning](https://img.youtube.com/vi/KNDVWGN3TpM/0.jpg)](https://www.youtube.com/watch?v=KNDVWGN3TpM) |
| Multimodal AI Agents | [![Multimodal](https://img.youtube.com/vi/hjAWmUT1qqY/0.jpg)](https://www.youtube.com/watch?v=hjAWmUT1qqY) |
| AI Agents Workflow | [![Workflow](https://img.youtube.com/vi/yWTH44QPl2A/0.jpg)](https://www.youtube.com/watch?v=yWTH44QPl2A) |
| Async AI Agents | [![Async](https://img.youtube.com/vi/VhVQfgo00LE/0.jpg)](https://www.youtube.com/watch?v=VhVQfgo00LE) |
| Mini AI Agents | [![Mini](https://img.youtube.com/vi/OkvYp5aAGSg/0.jpg)](https://www.youtube.com/watch?v=OkvYp5aAGSg) |
| AI Agents with Memory | [![Memory](https://img.youtube.com/vi/1hVfVxvPnnQ/0.jpg)](https://www.youtube.com/watch?v=1hVfVxvPnnQ) |
| Repetitive Agents | [![Repetitive](https://img.youtube.com/vi/dAYGxsjDOPg/0.jpg)](https://www.youtube.com/watch?v=dAYGxsjDOPg) |
| Introduction | [![Introduction](https://img.youtube.com/vi/Fn1lQjC0GO0/0.jpg)](https://www.youtube.com/watch?v=Fn1lQjC0GO0) |
| Tools Overview | [![Tools Overview](https://img.youtube.com/vi/XaQRgRpV7jo/0.jpg)](https://www.youtube.com/watch?v=XaQRgRpV7jo) |
| Custom Tools | [![Custom Tools](https://img.youtube.com/vi/JSU2Rndh06c/0.jpg)](https://www.youtube.com/watch?v=JSU2Rndh06c) |
+143 -1
View File
@@ -5,4 +5,146 @@
5. Make it minimal change as possible
6. Firstly try not to make any modification to the existing code as possible
7. Only modify the existing code if its highly required, without that if it cant be done, then add new code section.
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
Below is a **detailed technical overview** of the issues that have been coming up in the workflow execution (specifically around loops, resetting tasks, and continuing on to subsequent tasks). It includes:
1. **How the workflow currently flows**
2. **What logic marks a task as “completed”**
3. **What issues arose in `loop` tasks**
4. **Why the workflow can end up “stuck” or “looping indefinitely”**
---
## 1. Overall Workflow Flow
### a. Building relationships among tasks
- At startup, the code iterates through all `tasks`.
- For each `task`:
- It looks at `task.next_tasks`, and for each `next_task_name`:
- Finds the corresponding `Task` object
- Appends the current task’s name to the found `next_task`'s `previous_tasks` list.
- This means if Task A has `next_tasks=["B"]`, then Task B’s `previous_tasks` will include `"A"`.
### b. Finding and starting with a “start task”
- The workflow code tries to locate a task with `is_start=True`.
- If no such task is found, it uses the first item in the tasks dictionary instead.
- That “start task” is what the workflow tries to run first.
### c. Execution loop in the method (e.g., `workflow()` or `aworkflow()`)
- There is a `while current_task:` loop that processes tasks in sequence, or conditionally, based on `task.condition`.
- Each time a task runs (if non-loop), it yields the `task_id` or triggers an agent to run. Once the agent finishes (with or without a final result), the workflow picks up again to see what to do next:
- If the `task` is a `loop` type, the code tries to create or manage sub-tasks for each row/line in an “input_file.”
- If the `task` is a normal (“decision” or “task” or “some-other-type”), it just executes once, sets `status="completed"`, and the code moves on.
### d. Condition-based branching
- If a task has a result that includes a “decision” (like `{"decision":"more"}` or `"done"`), the code checks `task.condition`. For example:
```python
condition = {
"more": "generate_task",
"done": "evaluate_total_questions"
}
```
- If the result’s decision is `"done"`, it jumps to the task named `"evaluate_total_questions"`.
- If the result’s decision is `"more"`, it jumps right back to `"generate_task"`.
- If no condition matches, it can fallback to the first item in `task.next_tasks`.
### e. Marking tasks as “completed”
- After a task’s execution (like a typical “non-loop” task), the code sets `task.status = "completed"`.
- Then the code has a snippet that says:
```python
if self.tasks[task_id].status == "completed":
# Possibly reset to "not started" so we can re-run if needed
```
- By default, the system tries to “reset” tasks to `"not started"`, **unless** it is a loop task or a subtask of a loop.
---
## 2. How a “completed” task is marked
Generally, tasks are marked `status="completed"` in two primary ways:
1. **Non-Loop Execution**
A normal task (like a “decision” or “task”) is executed once the code calls the agent, the agent returns a final result, and the system sets `task.status = "completed"`.
2. **Loop Execution**
A loop-type task is *programmatically* set to `status="completed"` when all of its sub-tasks have finished. That is:
- The code checks: “Have all child tasks of this loop finished?”
- If `True`, the loop task is set to `completed`.
---
## 3. Issues Specifically in `loop` Tasks
### a. Re-Entering the Loop
- Before, the same snippet that “resets completed tasks to ‘not started’ so they can re-run if needed” **also** tried to reset loop tasks or their subtasks.
- If a loop task got reset to `"not started"`, the code would eventually pick it back up again, leading to repeated creation of sub-tasks (or repeated attempts to re-run them).
- This caused an **infinite loop** or repeating the same steps in the workflow, never truly exiting the loop stage.
### b. Subtasks Not Marked or Overwritten
- Another tricky scenario: If sub-tasks themselves got reset, the parent loop would see them as “not started” again, and might wait for them to “complete,” or might re-run them. That can lead to indefinite re-running of sub-tasks.
### c. Not Proceeding to Next (e.g., “upload_to_huggingface”)
- If the loop kept “restarting,” the workflow never ended up hitting the next tasks. For example, if your workflow is:
1. `generate_task`
2. `evaluate_total_questions`
3. `generate_cot` (loop)
4. `upload_to_huggingface`
- The system might get stuck in step #3 indefinitely (the sub-tasks keep getting reset, so it never actually transitions to step #4).
### d. Condition Logic vs. Next Tasks
- Another subtlety: If the loop tasks had a `condition` that pointed them back to a prior step, it might cause unintentional re-entry. Typically, you only want loop tasks to proceed once, **unless** you explicitly want to re-visit. But if it’s a data ingestion process, you usually want to do it once, then move on to the next step.
---
## 4. Why the System Can End Up Stuck or “Looping Indefinitely”
1. **Reset Mechanism**
- The code tries to “reset tasks to ‘not started’ once they complete,” so they can be re-run in some dynamic multi-run scenario.
- But that same logic can cause loop tasks to revert back to “not started” the moment they end. The system sees “Oh, a task is ‘not started’? Let’s run it!” and you’re in a cycle.
2. **No Condition for Exit**
- If the loop has a condition that leads back to a prior step (like `"more" -> generate_cot`), it can keep re-running.
3. **Subtasks Not Marked**
- If the subtask or the loop tries to “reactivate” each other, it never exits.
---
## Summary of the “Core Problem”
1. We want to keep the resetting mechanism for **non-loop tasks** – because in some advanced workflows, we like re-running them from a different path or after some condition.
2. But we want **loop tasks** to remain `"completed"` once all sub-tasks are done, so the code can seamlessly proceed to the next major step.
3. Before the fix, loop tasks or their sub-tasks got reset. This triggered the system to re-enter the loop, re-run the sub-tasks, etc., causing an infinite loop and preventing the workflow from reaching tasks like “upload_to_huggingface.”
---
## Technical Highlights to Pass On
- **In the reset snippet**:
```python
if self.tasks[task_id].status == "completed":
# never reset if loop or subtask-of-loop
# else reset to "not started"
```
This is crucial to skipping re-runs on loop tasks.
- **Ensure** that once a loop’s sub-tasks are all “completed,” the loop’s status is set to “completed,” and it transitions to the next major tasks (like `upload_to_huggingface`).
- **Check** if the loop’s condition is correct. If you want a single pass, do not implement a condition that leads back to the same loop.
- Also, you can check you do not have “overlapping conditions” that cause re-entry.
---
### Conclusion
**Hence,** the main challenge is that the reset logic (meant to let normal tasks be re-run) conflicts with a loop task’s one-pass usage. Once you avoid resetting the loop tasks or sub-tasks, you can finish them once, mark them “completed,” and properly proceed to the next stage.
Dont remove any logging or debug statements, as it will help you to understand the flow of the code.
+73
View File
@@ -0,0 +1,73 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import cot_save, cot_upload_to_huggingface
# Create a COT agent for generating and managing chain of thought solutions
cot_agent = Agent(
name="COTGenerator",
role="Chain of Thought Specialist",
goal="Generate and manage chain of thought solutions for Q&A pairs",
backstory="Expert in breaking down problems and generating detailed solution steps",
tools=[cot_save],
llm="gpt-4o-mini", # Using recommended model
)
save_to_huggingface_agent = Agent(
name="SaveToHuggingface",
role="Save to Huggingface",
goal="Save the generated chain of thought solutions to a Huggingface dataset",
backstory="Expert in saving data to Huggingface",
tools=[cot_upload_to_huggingface],
llm="gpt-4o-mini", # Using recommended model
)
# Define question-answer pairs to process in batches
qa_pairs = [
{
"question": "What is the sum of numbers from 1 to 10?",
"answer": "55"
},
{
"question": "Calculate the area of a circle with radius 5",
"answer": "78.54"
}
]
# Process qa_pairs in batches
batch_size = 2
for i in range(0, len(qa_pairs), batch_size):
batch_qa = qa_pairs[i:i + batch_size]
# Create task for current batch
qa_text = "\n".join(
f"- Q: {qa['question']}\n A: {qa['answer']}"
for qa in batch_qa
)
cot_task = Task(
description=(
"For each Q&A pair:\n"
"1. Use the tool cot_save to generate a chain of thought solution\n"
"2. Save to CSV file with filename 'cot_dataset.csv'\n\n"
f"Q&A Pairs:\n{qa_text}"
),
expected_output="Chain of thought solutions saved to CSV",
agent=cot_agent,
name=f"generate_cot_solutions_batch_{i//batch_size + 1}"
)
save_to_huggingface_task = Task(
description="Save to Huggingface dataset at mervinpraison/cot-dataset , cot_dataset.csv",
expected_output="Chain of thought solutions saved to Huggingface",
agent=save_to_huggingface_agent,
name=f"save_to_huggingface_batch_{i//batch_size + 1}"
)
# Initialize and run the agent
agents = PraisonAIAgents(
agents=[cot_agent, save_to_huggingface_agent],
tasks=[cot_task, save_to_huggingface_task]
)
# Execute current batch
result = agents.start()
print(f"\nBatch {i//batch_size + 1} completed. Solutions appended to CSV file.")
+142
View File
@@ -0,0 +1,142 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import cot_save, cot_upload_to_huggingface
from pydantic import BaseModel
import os
# Define Pydantic model for structured output
class DecisionModel(BaseModel):
response: str
decision: str
def write_csv(file_path, data):
"""Write data to CSV file."""
if not os.path.exists(file_path):
with open(file_path, 'w') as file:
file.write(data + '\n')
else:
with open(file_path, 'a') as file:
file.write(data + '\n')
return f"Data appended to {file_path}"
def count_questions(file_path):
"""Count lines in file."""
with open(file_path, 'r') as file:
return sum(1 for _ in file)
# Keep existing agents with minimal changes
qa_generator = Agent(
name="Generator",
role="Question Creator",
goal="Create challenging math and logic questions",
backstory="Expert in educational content creation",
llm="gpt-4o-mini",
tools=[write_csv, count_questions]
)
total_questions_evaluator = Agent(
name="TotalQuestionsEvaluator",
role="Total Questions Evaluator",
goal="Evaluate the total number of questions in qa_pairs.csv file",
backstory="Expert in evaluating the total number of questions in a file",
llm="gpt-4o-mini",
tools=[count_questions],
verbose=False
)
cot_generator = Agent(
name="COTGenerator",
role="Chain of Thought Specialist",
goal="Generate and manage chain of thought solutions for Q&A pairs",
backstory="Expert in breaking down problems and generating detailed solution steps",
tools=[cot_save],
llm="gpt-4o-mini",
verbose=False
)
upload_to_huggingface = Agent(
name="UploadToHuggingface",
role="Upload to Huggingface",
goal="Upload the generated chain of thought solutions to a Huggingface dataset",
backstory="Expert in saving data to Huggingface",
tools=[cot_upload_to_huggingface],
llm="gpt-4o-mini",
verbose=False
)
# Define tasks with workflow improvements
generate_task = Task(
description="""Generate question and answer in csv format without headers: question, answer and append to qa_pairs.csv file
generate 10 unique questions and answers and don't repeat on the same question and answer. Reponse with 'done' when done
with append mode as 'a'
Example question and answer:
question, answer
What is the sum of numbers from 1 to 10?, 55
Number of r's in the word strawberry, 3
""",
expected_output="append to qa_pairs.csv file with questions and answers and move to next task",
agent=qa_generator,
name="generate_task",
is_start=True,
next_tasks=["evaluate_total_questions"],
task_type="decision",
condition={
"more": "generate_task",
"done": "evaluate_total_questions"
}
)
evaluate_total_questions_task = Task(
description="Evaluate the total number of questions in qa_pairs.csv file is 1",
expected_output="Total number of questions in qa_pairs.csv file",
agent=total_questions_evaluator,
task_type="decision",
name="evaluate_total_questions",
condition={
"more": "generate_task",
"done": "generate_cot"
}
)
generate_cot_task = Task(
name="generate_cot",
description="""Generate chain of thought solutions for each question in the input file.
Save to cot_solutions.csv file
Don't generate chain of thought solutions again after receiving the response from Tool Call
After calling the tool, respond with a JSON object:
{
"response": "done",
"decision": "done"
}
""",
expected_output="done",
agent=cot_generator,
input_file="qa_pairs.csv",
task_type="loop",
next_tasks=["upload_to_huggingface"],
condition={
"done": ["upload_to_huggingface"],
"exit": [],
},
output_pydantic=DecisionModel # Use Pydantic model for output validation
)
upload_to_huggingface_task = Task(
name="upload_to_huggingface",
description="""Upload to Huggingface:
1. Save to cot_solutions.csv
2. Upload to mervinpraison/cot-dataset""",
expected_output="Dataset published successfully",
agent=upload_to_huggingface,
tools=[cot_upload_to_huggingface]
)
# Initialize workflow
agents = PraisonAIAgents(
agents=[qa_generator, total_questions_evaluator, cot_generator, upload_to_huggingface],
tasks=[generate_task, evaluate_total_questions_task, generate_cot_task, upload_to_huggingface_task],
process="workflow",
max_iter=30,
verbose=False
)
agents.start()
+6
View File
@@ -0,0 +1,6 @@
from praisonaiagents import Agent
agent = Agent(instructions="You are helpful Assisant", llm="deepseek-reasoner", reasoning_steps=True)
result = agent.start("Why sky is Blue?")
print(result)
+14
View File
@@ -0,0 +1,14 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
reasoning_agent = Agent(role="Helpful Assistant", reasoning_steps=True, llm="deepseek/deepseek-reasoner")
small_agent = Agent(role="Helpful Assistant", llm="gpt-3.5-turbo")
reasoning_task = Task(description="How many r's in the word 'Strawberry'?", agent=reasoning_agent)
small_task = Task(description="With the provided reasoning tell me how many r's in the word 'Strawberry'?", agent=small_agent)
agents = PraisonAIAgents(
agents=[reasoning_agent, small_agent],
tasks=[reasoning_task, small_task]
)
agents.start()
@@ -0,0 +1,19 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
llm_config = {
"model": "deepseek/deepseek-reasoner",
"max_tokens": 1
}
reasoning_agent = Agent(role="Helpful Assistant", llm=llm_config, reasoning_steps=True)
small_agent = Agent(role="Helpful Assistant", llm="openai/gpt-3.5-turbo")
reasoning_task = Task(description="How many r's in the word 'Strawberry'?", agent=reasoning_agent)
small_task = Task(description="With the provided reasoning tell me how many r's in the word 'Strawberry'?", agent=small_agent)
agents = PraisonAIAgents(
agents=[reasoning_agent, small_agent],
tasks=[reasoning_task, small_task]
)
agents.start()
+18
View File
@@ -0,0 +1,18 @@
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="deepseek-reasoner",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Why sky is Blue?"},
],
stream=True
)
for chunk in response:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.choices[0].delta.reasoning_content is not None:
print(chunk.choices[0].delta.reasoning_content, end="", flush=True)
+5 -6
View File
@@ -7,8 +7,8 @@ generator = Agent(
goal="Generate initial solutions and incorporate feedback",
instructions=(
"1. Look at the context from previous tasks.\n"
"2. If you see that you have already produced 2 points, then add another 2 new points "
" so that the total becomes 10.\n"
"2. If you see that you have already produced 2 points, then add another 2 new points starting with 3. "
" so that the total becomes 4.\n"
"3. Otherwise, just produce the first 2 points.\n"
"4. Return only the final list of points, with no extra explanation."
)
@@ -17,10 +17,10 @@ generator = Agent(
evaluator = Agent(
name="Evaluator",
role="Solution evaluator",
goal="Evaluate solutions and provide improvement feedback",
goal="Evaluate solutions and respond with more or done",
instructions=(
"1. Count how many lines in the response start with a number and a period (like '1. ' or '2. ').\n"
"2. If there are 10 or more, respond with 'done'.\n"
"2. If there are 4 or more, respond with 'done'.\n"
"3. Otherwise, respond with 'more'.\n"
"4. Return only the single word: 'done' or 'more'."
)
@@ -40,10 +40,9 @@ generate_task = Task(
evaluate_task = Task(
name="evaluate",
description="Check if there are 10 points about AI",
description="Check if there are 4 points about AI. ONLY RESPOND WITH 'more' or 'done' in your response field",
expected_output="more or done",
agent=evaluator,
next_tasks=["generate"],
context=[generate_task],
task_type="decision",
condition={
+14
View File
@@ -0,0 +1,14 @@
from litellm import completion
messages = [{"role": "user", "content": "What is 1+1?"}]
resp = completion(
model="deepseek/deepseek-reasoner",
messages=messages,
stream=False
)
reasoning_content = resp.choices[0].message.provider_specific_fields["reasoning_content"]
content = resp.choices[0].message.content
print(reasoning_content)
print(content)
+9
View File
@@ -0,0 +1,9 @@
from praisonaiagents import Agent
agent = Agent(
instructions="You are helpful Assisant",
llm="deepseek/deepseek-reasoner"
)
result = agent.start("Why sky is Blue?")
print(result)
+10
View File
@@ -0,0 +1,10 @@
from praisonaiagents import Agent
agent = Agent(
instructions="You are helpful Assisant",
llm="deepseek/deepseek-reasoner",
reasoning_steps=True
)
result = agent.start("Why sky is Blue?")
print(result)
+14
View File
@@ -0,0 +1,14 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
reasoning_agent = Agent(role="Helpful Assistant", reasoning_steps=True, llm="deepseek/deepseek-reasoner")
small_agent = Agent(role="Helpful Assistant", llm="openai/gpt-3.5-turbo")
reasoning_task = Task(description="How many r's in the word 'Strawberry'?", agent=reasoning_agent)
small_task = Task(description="With the provided reasoning tell me how many r's in the word 'Strawberry'?", agent=small_agent)
agents = PraisonAIAgents(
agents=[reasoning_agent, small_agent],
tasks=[reasoning_task, small_task]
)
agents.start()
File renamed without changes.
File renamed without changes.
+30
View File
@@ -0,0 +1,30 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from langchain_community.tools import YouTubeSearchTool
from langchain_community.utilities import WikipediaAPIWrapper
# Create an agent with both tools
agent = Agent(
name="SearchAgent",
role="Research Assistant",
goal="Search for information from multiple sources",
backstory="I am an AI assistant that can search YouTube and Wikipedia.",
tools=[YouTubeSearchTool, WikipediaAPIWrapper],
llm="openai/gpt-4o-mini"
)
# Create tasks to demonstrate both tools
task = Task(
name="search_task",
description="Search for information about 'AI advancements' on both YouTube and Wikipedia",
expected_output="Combined information from YouTube videos and Wikipedia articles",
agent=agent
)
# Create and start the workflow
agents = PraisonAIAgents(
agents=[agent],
tasks=[task],
verbose=True
)
agents.start()
+2 -1
View File
@@ -97,7 +97,8 @@ workflow = PraisonAIAgents(
agents=[router, worker1, worker2, worker3, synthesizer],
tasks=[router_task, worker1_task, worker2_task, worker3_task, synthesize_task],
process="workflow",
verbose=True
verbose=True,
max_iter=10
)
# Run orchestrated workflow
+344 -101
View File
@@ -21,10 +21,180 @@ from ..main import (
)
import inspect
import uuid
from dataclasses import dataclass
if TYPE_CHECKING:
from ..task.task import Task
@dataclass
class ChatCompletionMessage:
content: str
role: str = "assistant"
refusal: Optional[str] = None
audio: Optional[str] = None
function_call: Optional[dict] = None
tool_calls: Optional[List] = None
reasoning_content: Optional[str] = None
@dataclass
class Choice:
finish_reason: Optional[str]
index: int
message: ChatCompletionMessage
logprobs: Optional[dict] = None
@dataclass
class CompletionTokensDetails:
accepted_prediction_tokens: Optional[int] = None
audio_tokens: Optional[int] = None
reasoning_tokens: Optional[int] = None
rejected_prediction_tokens: Optional[int] = None
@dataclass
class PromptTokensDetails:
audio_tokens: Optional[int] = None
cached_tokens: int = 0
@dataclass
class CompletionUsage:
completion_tokens: int = 0
prompt_tokens: int = 0
total_tokens: int = 0
completion_tokens_details: Optional[CompletionTokensDetails] = None
prompt_tokens_details: Optional[PromptTokensDetails] = None
prompt_cache_hit_tokens: int = 0
prompt_cache_miss_tokens: int = 0
@dataclass
class ChatCompletion:
id: str
choices: List[Choice]
created: int
model: str
object: str = "chat.completion"
system_fingerprint: Optional[str] = None
service_tier: Optional[str] = None
usage: Optional[CompletionUsage] = None
def process_stream_chunks(chunks):
"""Process streaming chunks into combined response"""
if not chunks:
return None
try:
first_chunk = chunks[0]
last_chunk = chunks[-1]
# Basic metadata
id = getattr(first_chunk, "id", None)
created = getattr(first_chunk, "created", None)
model = getattr(first_chunk, "model", None)
system_fingerprint = getattr(first_chunk, "system_fingerprint", None)
# Track usage
completion_tokens = 0
prompt_tokens = 0
content_list = []
reasoning_list = []
tool_calls = []
current_tool_call = None
# First pass: Get initial tool call data
for chunk in chunks:
if not hasattr(chunk, "choices") or not chunk.choices:
continue
delta = getattr(chunk.choices[0], "delta", None)
if not delta:
continue
# Handle content and reasoning
if hasattr(delta, "content") and delta.content:
content_list.append(delta.content)
if hasattr(delta, "reasoning_content") and delta.reasoning_content:
reasoning_list.append(delta.reasoning_content)
# Handle tool calls
if hasattr(delta, "tool_calls") and delta.tool_calls:
for tool_call_delta in delta.tool_calls:
if tool_call_delta.index is not None and tool_call_delta.id:
# Found the initial tool call
current_tool_call = {
"id": tool_call_delta.id,
"type": "function",
"function": {
"name": tool_call_delta.function.name,
"arguments": ""
}
}
while len(tool_calls) <= tool_call_delta.index:
tool_calls.append(None)
tool_calls[tool_call_delta.index] = current_tool_call
current_tool_call = tool_calls[tool_call_delta.index]
elif current_tool_call is not None and hasattr(tool_call_delta.function, "arguments"):
if tool_call_delta.function.arguments:
current_tool_call["function"]["arguments"] += tool_call_delta.function.arguments
# Remove any None values and empty tool calls
tool_calls = [tc for tc in tool_calls if tc and tc["id"] and tc["function"]["name"]]
combined_content = "".join(content_list) if content_list else ""
combined_reasoning = "".join(reasoning_list) if reasoning_list else None
finish_reason = getattr(last_chunk.choices[0], "finish_reason", None) if hasattr(last_chunk, "choices") and last_chunk.choices else None
# Create ToolCall objects
processed_tool_calls = []
if tool_calls:
try:
from openai.types.chat import ChatCompletionMessageToolCall
for tc in tool_calls:
tool_call = ChatCompletionMessageToolCall(
id=tc["id"],
type=tc["type"],
function={
"name": tc["function"]["name"],
"arguments": tc["function"]["arguments"]
}
)
processed_tool_calls.append(tool_call)
except Exception as e:
print(f"Error processing tool call: {e}")
message = ChatCompletionMessage(
content=combined_content,
role="assistant",
reasoning_content=combined_reasoning,
tool_calls=processed_tool_calls if processed_tool_calls else None
)
choice = Choice(
finish_reason=finish_reason or "tool_calls" if processed_tool_calls else None,
index=0,
message=message
)
usage = CompletionUsage(
completion_tokens=completion_tokens,
prompt_tokens=prompt_tokens,
total_tokens=completion_tokens + prompt_tokens,
completion_tokens_details=CompletionTokensDetails(),
prompt_tokens_details=PromptTokensDetails()
)
return ChatCompletion(
id=id,
choices=[choice],
created=created,
model=model,
system_fingerprint=system_fingerprint,
usage=usage
)
except Exception as e:
print(f"Error processing chunks: {e}")
return None
class Agent:
def _generate_tool_definition(self, function_name):
"""
@@ -190,7 +360,8 @@ class Agent:
max_reflect: int = 3,
min_reflect: int = 1,
reflect_llm: Optional[str] = None,
user_id: Optional[str] = None
user_id: Optional[str] = None,
reasoning_steps: bool = False
):
# Add check at start if memory is requested
if memory is not None:
@@ -298,6 +469,7 @@ Your Goal: {self.goal}
# Store user_id
self.user_id = user_id or "praison"
self.reasoning_steps = reasoning_steps
# Check if knowledge parameter has any values
if not knowledge:
@@ -399,7 +571,54 @@ Your Goal: {self.goal}
def __str__(self):
return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True):
def _process_stream_response(self, messages, temperature, start_time, formatted_tools=None, reasoning_steps=False):
"""Process streaming response and return final response"""
try:
# Create the response stream
response_stream = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
tools=formatted_tools if formatted_tools else None,
stream=True
)
full_response_text = ""
reasoning_content = ""
chunks = []
# Create Live display with proper configuration
with Live(
display_generating("", start_time),
console=self.console,
refresh_per_second=4,
transient=True,
vertical_overflow="ellipsis",
auto_refresh=True
) as live:
for chunk in response_stream:
chunks.append(chunk)
if chunk.choices[0].delta.content:
full_response_text += chunk.choices[0].delta.content
live.update(display_generating(full_response_text, start_time))
# Update live display with reasoning content if enabled
if reasoning_steps and hasattr(chunk.choices[0].delta, "reasoning_content"):
rc = chunk.choices[0].delta.reasoning_content
if rc:
reasoning_content += rc
live.update(display_generating(f"{full_response_text}\n[Reasoning: {reasoning_content}]", start_time))
# Clear the last generating display with a blank line
self.console.print()
final_response = process_stream_chunks(chunks)
return final_response
except Exception as e:
display_error(f"Error in stream processing: {e}")
return None
def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True, reasoning_steps=False):
start_time = time.time()
logging.debug(f"{self.name} sending messages to LLM: {messages}")
@@ -425,20 +644,31 @@ Your Goal: {self.goal}
logging.warning(f"Tool {tool} not recognized")
try:
initial_response = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
tools=formatted_tools if formatted_tools else None,
stream=False
)
if stream:
# Process as streaming response with formatted tools
final_response = self._process_stream_response(
messages,
temperature,
start_time,
formatted_tools=formatted_tools if formatted_tools else None,
reasoning_steps=reasoning_steps
)
else:
# Process as regular non-streaming response
final_response = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
tools=formatted_tools if formatted_tools else None,
stream=False
)
tool_calls = getattr(initial_response.choices[0].message, 'tool_calls', None)
tool_calls = getattr(final_response.choices[0].message, 'tool_calls', None)
if tool_calls:
messages.append({
"role": "assistant",
"content": initial_response.choices[0].message.content,
"role": "assistant",
"content": final_response.choices[0].message.content,
"tool_calls": tool_calls
})
@@ -461,56 +691,31 @@ Your Goal: {self.goal}
"content": results_str
})
if stream:
response_stream = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
stream=True
)
full_response_text = ""
# Create Live display with proper configuration
with Live(
display_generating("", start_time),
console=self.console,
refresh_per_second=4,
transient=True, # Changed to False to preserve output
vertical_overflow="ellipsis",
auto_refresh=True
) as live:
for chunk in response_stream:
if chunk.choices[0].delta.content:
full_response_text += chunk.choices[0].delta.content
live.update(display_generating(full_response_text, start_time))
# Clear the last generating display with a blank line
self.console.print()
final_response = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
stream=False
)
return final_response
else:
if tool_calls:
# Get final response after tool calls
if stream:
final_response = self._process_stream_response(
messages,
temperature,
start_time,
formatted_tools=formatted_tools if formatted_tools else None,
reasoning_steps=reasoning_steps
)
else:
final_response = client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
stream=False
)
return final_response
else:
return initial_response
return final_response
except Exception as e:
display_error(f"Error in chat completion: {e}")
return None
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
reasoning_steps = reasoning_steps or self.reasoning_steps
# Search for existing knowledge if any knowledge is provided
if self.knowledge:
search_results = self.knowledge.search(prompt, agent_id=self.agent_id)
@@ -546,7 +751,8 @@ Your Goal: {self.goal}
agent_name=self.name,
agent_role=self.role,
agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools],
execute_tool_fn=self.execute_tool # Pass tool execution function
execute_tool_fn=self.execute_tool, # Pass tool execution function
reasoning_steps=reasoning_steps
)
self.chat_history.append({"role": "user", "content": prompt})
@@ -616,14 +822,13 @@ Your Goal: {self.goal}
agent_tools=agent_tools
)
response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None, reasoning_steps=reasoning_steps)
if not response:
return None
tool_calls = getattr(response.choices[0].message, 'tool_calls', None)
response_text = response.choices[0].message.content.strip()
if tool_calls:
if tool_calls: ## TODO: Most likely this tool call is already called in _chat_completion, so maybe we can remove this.
messages.append({
"role": "assistant",
"content": response_text,
@@ -675,6 +880,9 @@ Your Goal: {self.goal}
if self.verbose:
logging.debug(f"Agent {self.name} final response: {response_text}")
display_interaction(original_prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
# Return only reasoning content if reasoning_steps is True
if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content'):
return response.choices[0].message.reasoning_content
return response_text
reflection_prompt = f"""
@@ -749,8 +957,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
cleaned = cleaned[:-3].strip()
return cleaned
async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False):
"""Async version of chat method. TODO: Requires Syncing with chat method."""
reasoning_steps = reasoning_steps or self.reasoning_steps
try:
# Search for existing knowledge if any knowledge is provided
if self.knowledge:
@@ -781,7 +990,8 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
agent_name=self.name,
agent_role=self.role,
agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools],
execute_tool_fn=self.execute_tool_async
execute_tool_fn=self.execute_tool_async,
reasoning_steps=reasoning_steps
)
self.chat_history.append({"role": "user", "content": prompt})
@@ -847,48 +1057,48 @@ Your Goal: {self.goal}
)
# Format tools if provided
formatted_tools = []
if tools:
for tool in tools:
if isinstance(tool, str):
tool_def = self._generate_tool_definition(tool)
if tool_def:
formatted_tools.append(tool_def)
elif isinstance(tool, dict):
formatted_tools.append(tool)
elif hasattr(tool, "to_openai_tool"):
formatted_tools.append(tool.to_openai_tool())
elif callable(tool):
formatted_tools.append(self._generate_tool_definition(tool.__name__))
formatted_tools = []
if tools:
for tool in tools:
if isinstance(tool, str):
tool_def = self._generate_tool_definition(tool)
if tool_def:
formatted_tools.append(tool_def)
elif isinstance(tool, dict):
formatted_tools.append(tool)
elif hasattr(tool, "to_openai_tool"):
formatted_tools.append(tool.to_openai_tool())
elif callable(tool):
formatted_tools.append(self._generate_tool_definition(tool.__name__))
# Create async OpenAI client
async_client = AsyncOpenAI()
# Create async OpenAI client
async_client = AsyncOpenAI()
# Make the API call based on the type of request
if tools:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
tools=formatted_tools
)
return await self._achat_completion(response, tools)
elif output_json or output_pydantic:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
response_format={"type": "json_object"}
)
# Return the raw response
return response.choices[0].message.content
else:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature
)
return response.choices[0].message.content
# Make the API call based on the type of request
if tools:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
tools=formatted_tools
)
return await self._achat_completion(response, tools)
elif output_json or output_pydantic:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature,
response_format={"type": "json_object"}
)
# Return the raw response
return response.choices[0].message.content
else:
response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=temperature
)
return response.choices[0].message.content
except Exception as e:
display_error(f"Error in chat completion: {e}")
return None
@@ -896,7 +1106,7 @@ Your Goal: {self.goal}
display_error(f"Error in achat: {e}")
return None
async def _achat_completion(self, response, tools):
async def _achat_completion(self, response, tools, reasoning_steps=False):
"""Async version of _chat_completion method"""
try:
message = response.choices[0].message
@@ -942,9 +1152,42 @@ Your Goal: {self.goal}
final_response = await async_client.chat.completions.create(
model=self.llm,
messages=messages,
temperature=0.2
temperature=0.2,
stream=True
)
return final_response.choices[0].message.content
full_response_text = ""
reasoning_content = ""
chunks = []
start_time = time.time()
with Live(
display_generating("", start_time),
console=self.console,
refresh_per_second=4,
transient=True,
vertical_overflow="ellipsis",
auto_refresh=True
) as live:
async for chunk in final_response:
chunks.append(chunk)
if chunk.choices[0].delta.content:
full_response_text += chunk.choices[0].delta.content
live.update(display_generating(full_response_text, start_time))
if reasoning_steps and hasattr(chunk.choices[0].delta, "reasoning_content"):
rc = chunk.choices[0].delta.reasoning_content
if rc:
reasoning_content += rc
live.update(display_generating(f"{full_response_text}\n[Reasoning: {reasoning_content}]", start_time))
self.console.print()
final_response = process_stream_chunks(chunks)
# Return only reasoning content if reasoning_steps is True
if reasoning_steps and hasattr(final_response.choices[0].message, 'reasoning_content'):
return final_response.choices[0].message.reasoning_content
return final_response.choices[0].message.content if final_response else full_response_text
except Exception as e:
display_error(f"Error in final chat completion: {e}")
return formatted_results
@@ -952,7 +1195,7 @@ Your Goal: {self.goal}
return None
except Exception as e:
display_error(f"Error in _achat_completion: {e}")
return None
return None
async def astart(self, prompt: str, **kwargs):
"""Async version of start method"""
+4 -18
View File
@@ -390,15 +390,8 @@ Context:
task.status = "completed"
# Run execute_callback for memory operations
try:
try:
# If a loop is already running, just create the task
loop = asyncio.get_running_loop()
loop.create_task(task.execute_callback(task_output))
except RuntimeError:
# Otherwise, create and set a new loop, and run the callback
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(task.execute_callback(task_output))
# Use the new sync wrapper to avoid pending coroutine issues
task.execute_callback_sync(task_output)
except Exception as e:
logger.error(f"Error executing memory callback for task {task_id}: {e}")
logger.exception(e)
@@ -720,15 +713,8 @@ Context:
task.status = "completed"
# Run execute_callback for memory operations
try:
try:
# If a loop is already running, just create the task
loop = asyncio.get_running_loop()
loop.create_task(task.execute_callback(task_output))
except RuntimeError:
# Otherwise, create and set a new loop, and run the callback
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(task.execute_callback(task_output))
# Use the new sync wrapper to avoid pending coroutine issues
task.execute_callback_sync(task_output)
except Exception as e:
logger.error(f"Error executing memory callback for task {task_id}: {e}")
logger.exception(e)
+524 -134
View File
@@ -17,6 +17,8 @@ from ..main import (
from rich.console import Console
from rich.live import Live
# TODO: Include in-build tool calling in LLM class
# TODO: Restructure so that duplicate calls are not made (Sync with agent.py)
class LLMContextLengthExceededException(Exception):
"""Raised when LLM context length is exceeded"""
def __init__(self, message: str):
@@ -111,10 +113,28 @@ class LLM:
litellm.success_callback = []
litellm._async_success_callback = []
litellm.callbacks = []
# Additional logging suppression
litellm.suppress_debug_messages = True
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
logging.getLogger("litellm.main").setLevel(logging.WARNING)
verbose = extra_settings.get('verbose', True)
# Only suppress logs if not in debug mode
if not isinstance(verbose, bool) and verbose >= 10:
# Enable detailed debug logging
logging.getLogger("asyncio").setLevel(logging.DEBUG)
logging.getLogger("selector_events").setLevel(logging.DEBUG)
logging.getLogger("litellm.utils").setLevel(logging.DEBUG)
logging.getLogger("litellm.main").setLevel(logging.DEBUG)
litellm.suppress_debug_messages = False
litellm.set_verbose = True
else:
# Suppress debug logging for normal operation
logging.getLogger("asyncio").setLevel(logging.WARNING)
logging.getLogger("selector_events").setLevel(logging.WARNING)
logging.getLogger("litellm.utils").setLevel(logging.WARNING)
logging.getLogger("litellm.main").setLevel(logging.WARNING)
litellm.suppress_debug_messages = True
litellm._logging._disable_debugging()
warnings.filterwarnings("ignore", category=RuntimeWarning)
except ImportError:
raise ImportError(
"LiteLLM is required but not installed. "
@@ -142,11 +162,12 @@ class LLM:
self.extra_settings = extra_settings
self.console = Console()
self.chat_history = []
self.verbose = extra_settings.get('verbose', True)
self.verbose = verbose
self.markdown = extra_settings.get('markdown', True)
self.self_reflect = extra_settings.get('self_reflect', False)
self.max_reflect = extra_settings.get('max_reflect', 3)
self.min_reflect = extra_settings.get('min_reflect', 1)
self.reasoning_steps = extra_settings.get('reasoning_steps', False)
# Enable error dropping for cleaner output
litellm.drop_params = True
@@ -176,7 +197,8 @@ class LLM:
"""Enhanced get_response with all OpenAI-like features"""
try:
import litellm
# This below **kwargs** is passed to .completion() directly. so reasoning_steps has to be popped. OR find alternate best way of handling this.
reasoning_steps = kwargs.pop('reasoning_steps', self.reasoning_steps)
# Disable litellm debug messages
litellm.set_verbose = False
@@ -230,8 +252,55 @@ class LLM:
# Get response from LiteLLM
start_time = time.time()
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
# If reasoning_steps is True, do a single non-streaming call
if reasoning_steps:
resp = litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=False, # force non-streaming
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
response_text = resp["choices"][0]["message"]["content"]
# Optionally display reasoning if present
if verbose and reasoning_content:
display_interaction(
original_prompt,
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
display_interaction(
original_prompt,
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
# Otherwise do the existing streaming approach
else:
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
# Non-verbose mode, just collect the response
response_text = ""
for chunk in litellm.completion(
model=self.model,
@@ -241,23 +310,9 @@ class LLM:
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
# Non-verbose mode, just collect the response
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
response_text += chunk.choices[0].delta.content
response_text = response_text.strip()
response_text = response_text.strip()
# Get final completion to check for tool calls
final_response = litellm.completion(
@@ -302,9 +357,53 @@ class LLM:
"content": "Function returned an empty output"
})
# Get response after tool calls with streaming
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
# If reasoning_steps is True, do a single non-streaming call
if reasoning_steps:
resp = litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=False, # force non-streaming
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
response_text = resp["choices"][0]["message"]["content"]
# Optionally display reasoning if present
if verbose and reasoning_content:
display_interaction(
original_prompt,
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
display_interaction(
original_prompt,
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
# Otherwise do the existing streaming approach
else:
# Get response after tool calls with streaming
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
response_text = ""
for chunk in litellm.completion(
model=self.model,
@@ -313,21 +412,9 @@ class LLM:
stream=True
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
response_text += chunk.choices[0].delta.content
response_text = response_text.strip()
response_text = response_text.strip()
# Handle output formatting
if output_json or output_pydantic:
@@ -342,6 +429,9 @@ class LLM:
if verbose:
display_interaction(original_prompt, response_text, markdown=markdown,
generation_time=time.time() - start_time, console=console)
# Return reasoning content if reasoning_steps is True
if reasoning_steps and reasoning_content:
return reasoning_content
return response_text
# Handle self-reflection
@@ -357,32 +447,66 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
{"role": "user", "content": reflection_prompt}
]
# Get reflection response with streaming
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
# If reasoning_steps is True, do a single non-streaming call to capture reasoning
if reasoning_steps:
reflection_resp = litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=False, # Force non-streaming
response_format={"type": "json_object"},
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
# Grab reflection text and optional reasoning
reasoning_content = reflection_resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
reflection_text = reflection_resp["choices"][0]["message"]["content"]
# Optionally display reasoning if present
if verbose and reasoning_content:
display_interaction(
"Reflection reasoning:",
f"{reasoning_content}\n\nReflection result:\n{reflection_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
elif verbose:
display_interaction(
"Self-reflection (non-streaming):",
reflection_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
# Existing streaming approach
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
reflection_text = ""
for chunk in litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"},
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
reflection_text += content
live.update(display_generating(reflection_text, start_time))
else:
reflection_text = ""
for chunk in litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"}
response_format={"type": "json_object"},
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
reflection_text += content
live.update(display_generating(reflection_text, start_time))
else:
reflection_text = ""
for chunk in litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
reflection_text += chunk.choices[0].delta.content
reflection_text += chunk.choices[0].delta.content
try:
reflection_data = json.loads(reflection_text)
@@ -453,6 +577,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
"""Async version of get_response with identical functionality."""
try:
import litellm
reasoning_steps = kwargs.pop('reasoning_steps', self.reasoning_steps)
litellm.set_verbose = False
# Build messages list
@@ -490,10 +615,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
# Format tools for LiteLLM
formatted_tools = None
if tools:
logging.info(f"Starting tool formatting for {len(tools)} tools")
logging.debug(f"Starting tool formatting for {len(tools)} tools")
formatted_tools = []
for tool in tools:
logging.info(f"Processing tool: {tool.__name__ if hasattr(tool, '__name__') else str(tool)}")
logging.debug(f"Processing tool: {tool.__name__ if hasattr(tool, '__name__') else str(tool)}")
if hasattr(tool, '__name__'):
tool_name = tool.__name__
tool_doc = tool.__doc__ or "No description available"
@@ -539,10 +664,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
}
}
# Ensure tool definition is JSON serializable
print(f"Generated tool definition: {tool_def}")
try:
json.dumps(tool_def) # Test serialization
logging.info(f"Generated tool definition: {tool_def}")
logging.debug(f"Generated tool definition: {tool_def}")
formatted_tools.append(tool_def)
except TypeError as e:
logging.error(f"Tool definition not JSON serializable: {e}")
@@ -552,38 +676,67 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
if formatted_tools:
try:
json.dumps(formatted_tools) # Final serialization check
logging.info(f"Final formatted tools: {json.dumps(formatted_tools, indent=2)}")
logging.debug(f"Final formatted tools: {json.dumps(formatted_tools, indent=2)}")
except TypeError as e:
logging.error(f"Final tools list not JSON serializable: {e}")
formatted_tools = None
response_text = ""
if verbose:
# ----------------------------------------------------
# 1) Make the streaming call WITHOUT tools
# ----------------------------------------------------
async for chunk in await litellm.acompletion(
if reasoning_steps:
# Non-streaming call to capture reasoning
resp = await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
print("\033[K", end="\r")
print(f"Generating... {time.time() - start_time:.1f}s", end="\r")
stream=False, # force non-streaming
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
response_text = resp["choices"][0]["message"]["content"]
if verbose and reasoning_content:
display_interaction(
"Initial reasoning:",
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
elif verbose:
display_interaction(
"Initial response:",
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
# Non-verbose streaming call, still no tools
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
if verbose:
# ----------------------------------------------------
# 1) Make the streaming call WITHOUT tools
# ----------------------------------------------------
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
print("\033[K", end="\r")
print(f"Generating... {time.time() - start_time:.1f}s", end="\r")
else:
# Non-verbose streaming call, still no tools
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
response_text = response_text.strip()
@@ -598,7 +751,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
temperature=temperature,
stream=False,
tools=formatted_tools, # We safely pass tools here
**kwargs
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
# handle tool_calls from tool_response as usual...
tool_calls = tool_response.choices[0].message.get("tool_calls")
@@ -634,35 +787,66 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
"content": "Function returned an empty output"
})
# Get response after tool calls with streaming
# Get response after tool calls
response_text = ""
if verbose:
async for chunk in await litellm.acompletion(
if reasoning_steps:
# Non-streaming call to capture reasoning
resp = await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
tools=formatted_tools,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
print("\033[K", end="\r")
print(f"Reflecting... {time.time() - start_time:.1f}s", end="\r")
else:
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
stream=False, # force non-streaming
tools=formatted_tools, # Include tools
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
response_text = resp["choices"][0]["message"]["content"]
if verbose and reasoning_content:
display_interaction(
"Tool response reasoning:",
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
elif verbose:
display_interaction(
"Tool response:",
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
# Get response after tool calls with streaming
if verbose:
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
tools=formatted_tools,
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
print("\033[K", end="\r")
print(f"Reflecting... {time.time() - start_time:.1f}s", end="\r")
else:
response_text = ""
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
response_text = response_text.strip()
response_text = response_text.strip()
# Handle output formatting
if output_json or output_pydantic:
@@ -677,6 +861,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
if verbose:
display_interaction(original_prompt, response_text, markdown=markdown,
generation_time=time.time() - start_time, console=console)
# Return reasoning content if reasoning_steps is True
if reasoning_steps and reasoning_content:
return reasoning_content
return response_text
# Handle self-reflection
@@ -692,33 +879,66 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
{"role": "user", "content": reflection_prompt}
]
# Get reflection response
reflection_text = ""
if verbose:
async for chunk in await litellm.acompletion(
# If reasoning_steps is True, do a single non-streaming call to capture reasoning
if reasoning_steps:
reflection_resp = litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
stream=False, # Force non-streaming
response_format={"type": "json_object"},
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
reflection_text += content
print("\033[K", end="\r")
print(f"Reflecting... {time.time() - start_time:.1f}s", end="\r")
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
)
# Grab reflection text and optional reasoning
reasoning_content = reflection_resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
reflection_text = reflection_resp["choices"][0]["message"]["content"]
# Optionally display reasoning if present
if verbose and reasoning_content:
display_interaction(
"Reflection reasoning:",
f"{reasoning_content}\n\nReflection result:\n{reflection_text}",
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
elif verbose:
display_interaction(
"Self-reflection (non-streaming):",
reflection_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console
)
else:
async for chunk in await litellm.acompletion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"},
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
reflection_text += chunk.choices[0].delta.content
# Existing streaming approach
if verbose:
with Live(display_generating("", start_time), console=console, refresh_per_second=4) as live:
reflection_text = ""
for chunk in litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"},
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
reflection_text += content
live.update(display_generating(reflection_text, start_time))
else:
reflection_text = ""
for chunk in litellm.completion(
model=self.model,
messages=reflection_messages,
temperature=temperature,
stream=True,
response_format={"type": "json_object"},
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
reflection_text += chunk.choices[0].delta.content
while True: # Add loop for reflection handling
try:
@@ -820,4 +1040,174 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
if type(event) in event_types:
litellm._async_success_callback.remove(event)
litellm.callbacks = events
litellm.callbacks = events
# Response without tool calls
def response(
self,
prompt: Union[str, List[Dict]],
system_prompt: Optional[str] = None,
temperature: float = 0.2,
stream: bool = True,
verbose: bool = True,
markdown: bool = True,
console: Optional[Console] = None,
**kwargs
) -> str:
"""Simple function to get model response without tool calls or complex features"""
try:
import litellm
import logging
logger = logging.getLogger(__name__)
litellm.set_verbose = False
start_time = time.time()
logger.debug("Using synchronous response function")
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
messages.append({"role": "user", "content": prompt})
else:
messages.append({"role": "user", "content": prompt})
# Get response from LiteLLM
if stream:
response_text = ""
if verbose:
with Live(display_generating("", start_time), console=console or self.console, refresh_per_second=4) as live:
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
for chunk in litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
else:
response = litellm.completion(
model=self.model,
messages=messages,
temperature=temperature,
stream=False,
**kwargs
)
response_text = response.choices[0].message.content.strip()
if verbose:
display_interaction(
prompt if isinstance(prompt, str) else prompt[0].get("text", ""),
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console or self.console
)
return response_text.strip()
except Exception as error:
display_error(f"Error in response: {str(error)}")
raise
# Async version of response function. Response without tool calls
async def aresponse(
self,
prompt: Union[str, List[Dict]],
system_prompt: Optional[str] = None,
temperature: float = 0.2,
stream: bool = True,
verbose: bool = True,
markdown: bool = True,
console: Optional[Console] = None,
**kwargs
) -> str:
"""Async version of response function"""
try:
import litellm
import logging
logger = logging.getLogger(__name__)
litellm.set_verbose = False
start_time = time.time()
logger.debug("Using asynchronous response function")
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
messages.append({"role": "user", "content": prompt})
else:
messages.append({"role": "user", "content": prompt})
# Get response from LiteLLM
if stream:
response_text = ""
if verbose:
with Live(display_generating("", start_time), console=console or self.console, refresh_per_second=4) as live:
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
response_text += content
live.update(display_generating(response_text, start_time))
else:
async for chunk in await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
**kwargs
):
if chunk and chunk.choices and chunk.choices[0].delta.content:
response_text += chunk.choices[0].delta.content
else:
response = await litellm.acompletion(
model=self.model,
messages=messages,
temperature=temperature,
stream=False,
**kwargs
)
response_text = response.choices[0].message.content.strip()
if verbose:
display_interaction(
prompt if isinstance(prompt, str) else prompt[0].get("text", ""),
response_text,
markdown=markdown,
generation_time=time.time() - start_time,
console=console or self.console
)
return response_text.strip()
except Exception as error:
display_error(f"Error in response_async: {str(error)}")
raise
File diff suppressed because it is too large. Load diff
+35 -16
View File
@@ -38,7 +38,8 @@ class Task:
loop_state: Optional[Dict[str, Union[str, int]]] = None,
memory=None,
quality_check=True,
input_file: Optional[str] = None
input_file: Optional[str] = None,
rerun: bool = False # Renamed from can_rerun and logic inverted, default True for backward compatibility
):
# Add check if memory config is provided
if memory is not None or (config and config.get('memory_config')):
@@ -76,6 +77,7 @@ class Task:
self.loop_state = loop_state if loop_state else {}
self.memory = memory
self.quality_check = quality_check
self.rerun = rerun # Assigning the rerun parameter
# Set logger level based on config verbose level
verbose = self.config.get("verbose", 0)
@@ -83,7 +85,7 @@ class Task:
logger.setLevel(logging.INFO)
else:
logger.setLevel(logging.WARNING)
# Also set third-party loggers to WARNING
logging.getLogger('chromadb').setLevel(logging.WARNING)
logging.getLogger('openai').setLevel(logging.WARNING)
@@ -148,7 +150,7 @@ class Task:
logger.info(f"Task {self.id}: Initializing memory from config: {self.config['memory_config']}")
self.memory = Memory(config=self.config['memory_config'])
logger.info(f"Task {self.id}: Memory initialized successfully")
# Verify database was created
if os.path.exists(self.config['memory_config']['storage']['path']):
logger.info(f"Task {self.id}: Memory database exists after initialization")
@@ -182,11 +184,11 @@ class Task:
"""Execute callback and store quality metrics if enabled"""
logger.info(f"Task {self.id}: execute_callback called")
logger.info(f"Quality check enabled: {self.quality_check}")
# Initialize memory if not already initialized
if not self.memory:
self.memory = self.initialize_memory()
logger.info(f"Memory object exists: {self.memory is not None}")
if self.memory:
logger.info(f"Memory config: {self.memory.cfg}")
@@ -202,24 +204,24 @@ class Task:
except Exception as e:
logger.error(f"Task {self.id}: Failed to store task output in memory: {e}")
logger.exception(e)
logger.info(f"Task output: {task_output.raw[:100]}...")
if self.quality_check and self.memory:
try:
logger.info(f"Task {self.id}: Starting memory operations")
logger.info(f"Task {self.id}: Calculating quality metrics for output: {task_output.raw[:100]}...")
# Get quality metrics from LLM
metrics = self.memory.calculate_quality_metrics(
task_output.raw,
self.expected_output
)
logger.info(f"Task {self.id}: Quality metrics calculated: {metrics}")
quality_score = metrics.get("accuracy", 0.0)
logger.info(f"Task {self.id}: Quality score: {quality_score}")
# Store in both short and long-term memory with higher threshold
logger.info(f"Task {self.id}: Finalizing task output in memory...")
self.memory.finalize_task_output(
@@ -231,7 +233,7 @@ class Task:
task_id=self.id
)
logger.info(f"Task {self.id}: Finalized task output in memory")
# Store quality metrics separately
logger.info(f"Task {self.id}: Storing quality metrics...")
self.memory.store_quality(
@@ -240,7 +242,7 @@ class Task:
task_id=self.id,
metrics=metrics
)
# Store in both short and long-term memory with higher threshold
self.memory.finalize_task_output(
content=task_output.raw,
@@ -248,7 +250,7 @@ class Task:
quality_score=quality_score,
threshold=0.7 # Only high quality outputs in long-term memory
)
# Build context for next tasks
if self.next_tasks:
logger.info(f"Task {self.id}: Building context for next tasks...")
@@ -257,7 +259,7 @@ class Task:
max_items=5
)
logger.info(f"Task {self.id}: Built context for next tasks: {len(context)} items")
logger.info(f"Task {self.id}: Memory operations complete")
except Exception as e:
logger.error(f"Task {self.id}: Failed to process memory operations: {e}")
@@ -295,11 +297,28 @@ Expected Output: {self.expected_output}.
context_results.append(
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
)
# Join unique context results
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
task_prompt += f"""
Context:
{' '.join(unique_contexts)}
"""
"""
def execute_callback_sync(self, task_output: TaskOutput) -> None:
"""
Synchronous wrapper to ensure that execute_callback is awaited,
preventing 'Task was destroyed but pending!' warnings if called
from non-async code.
"""
import asyncio
try:
loop = asyncio.get_running_loop()
if loop.is_running():
loop.create_task(self.execute_callback(task_output))
else:
loop.run_until_complete(self.execute_callback(task_output))
except RuntimeError:
# If no loop is running in this context
asyncio.run(self.execute_callback(task_output))
+22 -1
View File
@@ -135,6 +135,27 @@ TOOL_MAPPINGS = {
'group_by': ('.pandas_tools', 'PandasTools'),
'pivot_table': ('.pandas_tools', 'PandasTools'),
'pandas_tools': ('.pandas_tools', 'PandasTools'),
# Chain of Thought Training Tools
'cot_run': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates text solution
'cot_run_dict': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates dict-based solution
'cot_generate': ('.train.data.generatecot', 'GenerateCOT'), # Generate text solution
'cot_generate_dict': ('.train.data.generatecot', 'GenerateCOT'), # Generate structured solution
'cot_improve': ('.train.data.generatecot', 'GenerateCOT'), # Improve text solution
'cot_improve_dict': ('.train.data.generatecot', 'GenerateCOT'), # Improve dict-based solution
'cot_check': ('.train.data.generatecot', 'GenerateCOT'), # Check correctness
'cot_find_error': ('.train.data.generatecot', 'GenerateCOT'), # Locate error in solution
'cot_load_answers': ('.train.data.generatecot', 'GenerateCOT'), # Load QA pairs
# COT Save/Export with QA Pairs
'cot_save_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Save with QA pairs
'cot_append_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append with QA pairs
'cot_export_json_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export JSON with QA pairs
'cot_export_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export CSV with QA pairs
'cot_append_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append CSV with QA pairs
'cot_save': ('.train.data.generatecot', 'GenerateCOT'), # Save single QA to file
'cot_upload_to_huggingface': ('.train.data.generatecot', 'GenerateCOT'), # Upload dataset to HuggingFace
'cot_tools': ('.train.data.generatecot', 'GenerateCOT'), # Full toolkit access
}
_instances = {} # Cache for class instances
@@ -161,7 +182,7 @@ def __getattr__(name: str) -> Any:
]:
return getattr(module, name)
if name in ['file_tools', 'pandas_tools', 'wikipedia_tools',
'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools']:
'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools', 'cot_tools']:
return module # Returns the callable module
return getattr(module, name)
else:
+54 -23
View File
@@ -89,7 +89,7 @@ class CSVTools:
def write_csv(
self,
filepath: str,
data: List[Dict[str, Any]],
data: Union[List[Dict[str, Any]], str],
encoding: str = 'utf-8',
delimiter: str = ',',
index: bool = False,
@@ -102,35 +102,66 @@ class CSVTools:
Args:
filepath: Path to CSV file
data: List of row dicts to write
encoding: File encoding
delimiter: Column delimiter
index: Whether to write row indices
header: Whether to write column headers
float_format: Format string for float values
date_format: Format string for date values
mode: Write mode ('w' for write, 'a' for append)
data: Either a list of dictionaries or a string containing CSV data
If string, each line should be comma-separated values
encoding: File encoding (default: 'utf-8')
delimiter: Column delimiter (default: ',')
index: Whether to write row indices (default: False)
header: Whether to write column headers (default: True)
float_format: Format string for float values (default: None)
date_format: Format string for date values (default: None)
mode: Write mode - 'w' for write, 'a' for append (default: 'w')
Returns:
bool: Success status
bool: True if successful, False otherwise
"""
try:
pd = self._get_pandas()
if pd is None:
return False
df = pd.DataFrame(data)
df.to_csv(
filepath,
encoding=encoding,
sep=delimiter,
index=index,
header=header,
float_format=float_format,
date_format=date_format,
mode=mode
)
return True
# Handle string input
if isinstance(data, str):
# Convert string to list of dicts
rows = []
if delimiter in data:
# Get existing columns if file exists and in append mode
existing_cols = []
if mode == 'a' and Path(filepath).exists():
try:
existing_df = pd.read_csv(filepath, nrows=1)
existing_cols = existing_df.columns.tolist()
except:
pass
values = [v.strip() for v in data.split(delimiter)]
if existing_cols:
# Use existing column names
row_dict = dict(zip(existing_cols, values))
else:
# Create generic column names
row_dict = {f'col{i}': val for i, val in enumerate(values)}
rows.append(row_dict)
data = rows
df = pd.DataFrame(data)
# Handle append mode properly
write_header = header if mode == 'w' else (header and not Path(filepath).exists())
df.to_csv(
filepath,
encoding=encoding,
sep=delimiter,
index=index,
header=write_header,
float_format=float_format,
date_format=date_format,
mode=mode
)
return True
except Exception as e:
error_msg = f"Error writing CSV file {filepath}: {str(e)}"
@@ -0,0 +1,531 @@
from typing import Dict, Optional, Union, Any
import json
from datetime import datetime
import os
import logging
# Setup logging based on environment variable
log_level = os.getenv('LOGLEVEL', 'INFO').upper()
logging.basicConfig(
level=log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Lazy loader for LLM
def get_llm():
try:
from praisonaiagents.llm.llm import LLM
return LLM
except ImportError:
raise ImportError(
"LLM is required for this toolbut not installed. "
"Please install with: pip install 'praisonaiagents[llm]' datasets huggingface-hub pandas"
)
class GenerateCOT:
def __init__(
self,
qa_pairs: Optional[Dict[str, str]] = None,
model: str = "gpt-4o-mini",
api_key: Optional[str] = None,
max_attempts: int = 3,
verbose: bool = True,
temperature: float = 0.5
):
self.qa_pairs = qa_pairs or {}
self.max_attempts = max_attempts
self.solutions = {}
self.llm = get_llm()(model=model) # Get LLM class and instantiate
self.model = model
self.temperature = temperature
self.verbose = verbose
logger.debug(f"Initialized GenerateCOT with model={model}, max_attempts={max_attempts}")
def _ask_ai(self, prompt: str) -> str:
logger.debug(f"Sending prompt to LLM: {prompt[:100]}...")
response = self.llm.response(prompt, temperature=self.temperature)
logger.debug(f"Received response: {response[:100]}...")
return response
def _build_solution_prompt(self, question: str, context: str) -> str:
return f"""
Solve this problem step by step: {question}
Context: {context}
Steps needed:
1. Break down the problem
2. Show your work
3. Explain each step
4. Give final answer
"""
def cot_generate(self, question: str, context: str = "") -> str:
prompt = self._build_solution_prompt(question, context)
return self._ask_ai(prompt)
def cot_check(self, question: str, answer: str) -> bool:
if question not in self.qa_pairs:
raise ValueError(f"No correct answer found for: {question}")
prompt = f"""
Question: {question}
Given Answer: {answer}
Correct Answer: {self.qa_pairs[question]}
Is the given answer correct? Reply only with 'true' or 'false'.
"""
return self._ask_ai(prompt).lower().strip() == "true"
def cot_find_error(self, question: str, solution: str) -> int:
if self.cot_check(question, solution):
return -1
sentences = [s.strip() for s in solution.replace('。', '.').split('.') if s.strip()]
left, right = 0, len(sentences)
while left < right:
mid = (left + right) // 2
partial = '. '.join(sentences[:mid]) + '.'
if self.cot_check(question, partial):
left = mid + 1
else:
right = mid
return left
def cot_improve(self, question: str, current: str) -> str:
best_solution = current
best_score = self._rate_solution(question, current)
attempts = 0
while attempts < self.max_attempts:
attempts += 1
new_solution = self.cot_generate(question, current)
new_score = self._rate_solution(question, new_solution)
if new_score > best_score:
best_solution = new_solution
best_score = new_score
if best_score > 0.8:
break
return best_solution
def _rate_solution(self, question: str, solution: str) -> float:
prompt = f"""
Rate this solution from 0 to 1:
Question: {question}
Solution: {solution}
Correct Answer: {self.qa_pairs.get(question, '')}
Return only a number between 0 and 1.
"""
try:
score = float(self._ask_ai(prompt))
return min(max(score, 0), 1)
except ValueError:
return 0.0
def cot_run(self, question: str) -> str:
"""Run COT generation for a single question."""
solution = self.cot_generate(question)
if self.cot_check(question, solution):
return solution
solution = self.cot_improve(question, solution)
error_pos = self.cot_find_error(question, solution)
if error_pos != -1:
correct_part = '. '.join(solution.split('. ')[:error_pos]) + '.'
solution = self.cot_generate(question, correct_part)
self.solutions[question] = {
"solution": solution,
"error_position": error_pos,
}
return solution
def cot_load_answers(self, filepath: str) -> bool:
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
self.qa_pairs.update(data)
return True
except Exception as e:
print(f"Error loading answers: {e}")
return False
def _is_qa_pairs(self, qa_pairs: Any) -> bool:
"""Validate if input is a proper QA pairs dictionary."""
if not qa_pairs:
return True # None or empty is valid
if not isinstance(qa_pairs, dict):
raise ValueError("qa_pairs must be a dictionary with questions as keys and answers as values")
return True
def cot_append_solutions_with_qa_pairs(
self,
filepath: str = 'solutions.json',
qa_pairs: Optional[Dict[str, str]] = None
) -> None:
"""Appends current solutions to existing file or creates a new one."""
try:
self._is_qa_pairs(qa_pairs) # Validate format
if qa_pairs:
self.qa_pairs.update(qa_pairs)
data = {
"solutions": self.solutions,
"qa_pairs": self.qa_pairs,
"saved_at": datetime.now().isoformat()
}
existing_data = {}
try:
with open(filepath, 'r', encoding='utf-8') as f:
existing_data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
pass
if existing_data:
existing_data["solutions"].update(data["solutions"])
existing_data["qa_pairs"].update(data["qa_pairs"])
existing_data["saved_at"] = data["saved_at"]
data = existing_data
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"Error appending solutions: {e}")
def cot_save_solutions_with_qa_pairs(
self,
filepath: str = 'solutions.json',
append: bool = False,
qa_pairs: Optional[Dict[str, str]] = None
) -> None:
try:
self._is_qa_pairs(qa_pairs) # Validate format
if qa_pairs:
self.qa_pairs.update(qa_pairs)
if append:
self.cot_append_solutions_with_qa_pairs(filepath)
return
data = {
"solutions": self.solutions,
"qa_pairs": self.qa_pairs,
"saved_at": datetime.now().isoformat()
}
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"Error saving solutions: {e}")
def cot_generate_dict(self, question: str, context: str = "") -> dict:
prompt = self._build_solution_prompt(question, context)
thought_process = self._ask_ai(prompt)
final_answer_prompt = f"""
Based on this solution, what is the final answer only:
{thought_process}
Give only the final answer, no explanation.
"""
final_answer = self._ask_ai(final_answer_prompt)
return {
"thought_process": thought_process,
"final_answer": final_answer
}
def cot_improve_dict(self, question: str, current_solution: str) -> dict:
"""
Improves the existing solution (text form), returning the best dictionary-based version.
"""
best_solution = {
"thought_process": current_solution,
"final_answer": current_solution
}
best_score = self._rate_solution(question, current_solution)
attempts = 0
while attempts < self.max_attempts:
attempts += 1
new_solution = self.cot_generate_dict(question, current_solution)
new_score = self._rate_solution(question, new_solution["thought_process"])
if new_score > best_score:
best_solution = new_solution
best_score = new_score
if best_score > 0.8:
break
return best_solution
def cot_run_dict(self, question: str) -> dict:
"""Uses the dictionary-based solution approach, storing the final solution in self.solutions."""
logger.debug(f"Starting cot_run_dict for question: {question}")
solution = self.cot_generate_dict(question)
logger.debug(f"Initial solution generated: {str(solution)[:100]}...")
if self.cot_check(question, solution["final_answer"]):
logger.debug("Initial solution passed verification")
self.solutions[question] = solution
return solution
logger.debug("Initial solution failed verification, attempting improvement")
improved = self.cot_improve_dict(question, solution["thought_process"])
if self.cot_check(question, improved["final_answer"]):
logger.debug("Improved solution passed verification")
self.solutions[question] = improved
return improved
logger.debug("Checking for errors in improved solution")
error_pos = self.cot_find_error(question, improved["thought_process"])
if error_pos != -1:
logger.debug(f"Found error at position {error_pos}, generating final solution")
partial_solution = '. '.join(improved["thought_process"].split('. ')[:error_pos]) + '.'
final = self.cot_generate_dict(question, partial_solution)
self.solutions[question] = final
return final
logger.debug("Using improved solution as final result")
self.solutions[question] = improved
return improved
def cot_export_json_with_qa_pairs(
self,
filepath: str = 'dataset.json',
save_to_file: bool = True,
qa_pairs: Optional[Dict[str, str]] = None
) -> Union[str, list]:
"""
Export solutions in Alpaca training format with their full thought process.
"""
try:
self._is_qa_pairs(qa_pairs) # Validate format
if qa_pairs:
self.qa_pairs.update(qa_pairs)
# Generate solutions if empty
if not self.solutions:
for question in qa_pairs:
self.cot_run_dict(question)
alpaca_data = []
for question, sol in self.solutions.items():
alpaca_data.append({
"instruction": question,
"input": "",
"output": sol.get("thought_process", "")
})
if not save_to_file:
return alpaca_data
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(alpaca_data, f, ensure_ascii=False, indent=2)
return filepath
except Exception as e:
print(f"Error exporting to Alpaca format: {e}")
return None
def cot_export_csv_with_qa_pairs(
self,
filepath: str = 'dataset.csv',
qa_pairs: Optional[Dict[str, str]] = None
) -> Optional[str]:
"""Export solutions in CSV format."""
try:
import csv
self._is_qa_pairs(qa_pairs) # Validate format
if qa_pairs:
self.qa_pairs.update(qa_pairs)
# Generate solutions if empty
if not self.solutions:
for question in qa_pairs:
self.cot_run_dict(question)
with open(filepath, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['instruction', 'input', 'output'])
for question, sol in self.solutions.items():
writer.writerow([question, '', sol.get("thought_process", "")])
return filepath
except Exception as e:
print(f"Error exporting to CSV format: {e}")
return None
def cot_save(
self,
question: str,
answer: str,
filepath: str = 'dataset.csv'
) -> Optional[str]:
"""Save a single question-answer pair with chain of thought to CSV file."""
logger.debug(f"Saving QA pair to {filepath}")
try:
self.qa_pairs[question] = answer
logger.debug("Added QA pair to internal dictionary")
solution = self.cot_run_dict(question)
logger.debug("Generated solution for question")
import csv
import os
file_exists = os.path.exists(filepath)
logger.debug(f"File exists: {file_exists}")
with open(filepath, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
if not file_exists:
logger.debug("Creating new file with headers")
writer.writerow(['instruction', 'input', 'output'])
writer.writerow([question, '', solution.get("thought_process", "")])
logger.debug("Wrote solution to file")
return filepath
except Exception as e:
logger.error(f"Error saving to CSV: {str(e)}")
return None
# Rename existing function to indicate it handles qa_pairs dictionary
def cot_append_csv_with_qa_pairs(
self,
filepath: str = 'dataset.csv',
qa_pairs: Optional[Dict[str, str]] = None
) -> Optional[str]:
"""Append solutions to CSV file using qa_pairs dictionary."""
try:
self._is_qa_pairs(qa_pairs) # Validate format
if qa_pairs:
self.qa_pairs.update(qa_pairs)
import csv
import os
file_exists = os.path.exists(filepath)
with open(filepath, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(['instruction', 'input', 'output'])
for question, sol in self.solutions.items():
writer.writerow([question, '', sol.get("thought_process", "")])
return filepath
except Exception as e:
print(f"Error appending to CSV: {e}")
return None
def cot_upload_to_huggingface(
self,
huggingface_username: str,
dataset_name: str,
filepath: str,
private: bool = False
) -> str:
"""Upload generated solutions to HuggingFace datasets."""
logger.debug(f"Attempting to upload {filepath} to HuggingFace as {dataset_name}")
try:
from datasets import Dataset
import pandas as pd
logger.debug(f"Loading data from {filepath}")
if filepath.endswith('.csv'):
data = pd.read_csv(filepath)
logger.debug(f"Loaded CSV with {len(data)} rows")
elif filepath.endswith('.json'):
data = pd.read_json(filepath)
logger.debug(f"Loaded JSON with {len(data)} records")
else:
raise ValueError("Only CSV and JSON files are supported")
logger.debug("Converting to HuggingFace dataset")
dataset = Dataset.from_pandas(data)
repo_id = f"{huggingface_username}/{dataset_name}"
logger.debug(f"Pushing to hub: {repo_id}")
dataset.push_to_hub(
repo_id,
private=private
)
logger.debug("Upload completed successfully")
return f"Dataset uploaded successfully to {repo_id}"
except Exception as e:
logger.error(f"Error uploading to HuggingFace: {str(e)}")
return None
# Usage example:
if __name__ == "__main__":
# Direct QA Pairs Export Example
print("\n=== Direct QA Pairs Export Example ===")
direct_qa_data = {
"Number of r's in the word strawberry": "3"
}
direct_generator = GenerateCOT()
# Export with qa_pairs passed directly to functions
direct_generator.cot_export_csv_with_qa_pairs(
filepath='direct_solutions.csv',
qa_pairs=direct_qa_data
)
# Example of using cot_save for a single QA pair
direct_generator.cot_save(
question="What is the capital of France?",
answer="Paris",
filepath="single_qa.csv"
)
# Upload to HuggingFace
direct_generator.cot_upload_to_huggingface(
huggingface_username="mervinpraison",
dataset_name="cot-test",
filepath="single_qa.csv"
)
# direct_generator.cot_export_json_with_qa_pairs(
# filepath='direct_solutions.json',
# qa_pairs=direct_qa_data
# )
# # Rest of the original examples...
# qa_data = {
# "What is 2+2?": "4",
# "How many letters in 'hello'?": "5"
# }
# generator = GenerateCOT(qa_pairs=qa_data)
# for question in qa_data:
# solution = generator.cot_run(question)
# print(f"Question: {question}")
# print(f"Solution: {solution}\n")
# answer = generator.cot_run("What is 2+2?")
# print(answer)
# # Additional QA data processing example
# print("\n=== Processing Additional QA Data ===")
# extra_qa_data = {
# "What is 5 * 3?": "15"
# }
# # Create separate generator for additional data
# extra_generator = GenerateCOT(qa_pairs=extra_qa_data)
# # Process and save solutions
# for question in extra_qa_data:
# solution = extra_generator.cot_run_dict(question)
# print(f"Processing extra question: {question}")
# # Save solutions separately
# extra_generator.cot_save_solutions_with_qa_pairs('extra_qa_solutions.json')
# # Export in Alpaca format
# extra_generator.cot_export_json_with_qa_pairs(filepath='extra_qa_alpaca.json', save_to_file=True)
# # Demonstrate loading saved data
# loaded_generator = GenerateCOT(qa_pairs={})
# loaded_generator.cot_load_answers('extra_qa_solutions.json')
# print("\nLoaded extra QA pairs:", loaded_generator.qa_pairs)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.50"
version = "0.0.60"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
authors = [
{ name="Mervin Praison" }
+2 -1
View File
@@ -6,7 +6,8 @@ agent = Agent(
)
task = Task(
description="Create the list of tasks to be looped through.",
description="complete the task",
expected_output="task completed",
agent=agent,
task_type="loop",
input_file="tasks.csv"
+1 -1
View File
@@ -1868,7 +1868,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.50"
version = "0.0.60"
source = { editable = "." }
dependencies = [
{ name = "openai" },
+1
View File
@@ -7,6 +7,7 @@ agent = Agent(
self_reflect=True,
min_reflect=3,
max_reflect=5,
llm="gpt-4o-mini"
)
agent.start(
"What is the history of AI?"
+124
View File
@@ -0,0 +1,124 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import cot_save, cot_upload_to_huggingface
from pydantic import BaseModel
import os
def print_data(data):
print(data)
return "done"
def upload_to_huggingface(data):
print(data)
return "done"
# Define Pydantic model for structured output
class DecisionModel(BaseModel):
response: str
decision: str
def write_csv(file_path, data):
"""Write data to CSV file."""
# delete file if exists
if os.path.exists(file_path):
os.remove(file_path)
with open(file_path, 'w') as file:
file.write(data + '\n')
return f"Data appended to {file_path}"
def count_questions(file_path):
"""Count lines in file."""
with open(file_path, 'r') as file:
return sum(1 for _ in file)
# Keep existing agents with minimal changes
qa_generator = Agent(
name="Generator",
role="Question Creator",
goal="Create challenging math and logic questions",
backstory="Expert in educational content creation",
llm="gpt-4o-mini",
tools=[write_csv, count_questions]
)
cot_generator = Agent(
name="COTGenerator",
role="Chain of Thought Specialist",
goal="Generate and manage chain of thought solutions for Q&A pairs",
backstory="Expert in breaking down problems and generating detailed solution steps",
tools=[print_data],
llm="gpt-4o-mini",
verbose=False
)
upload_to_huggingface = Agent(
name="UploadToHuggingface",
role="Upload to Huggingface",
goal="Upload the generated chain of thought solutions to a Huggingface dataset",
backstory="Expert in saving data to Huggingface",
tools=[upload_to_huggingface],
llm="gpt-4o-mini",
verbose=False
)
# Define tasks with workflow improvements
generate_task = Task(
description="""Generate question and answer in csv format without headers: question, answer and append to qa_pairs.csv file
generate 1 unique questions and answers and don't repeat on the same question and answer. Reponse with 'done' when done
with append mode as 'a'
Example question and answer:
question, answer
What is the sum of numbers from 1 to 10?, 55
Number of r's in the word strawberry, 3
""",
expected_output="append to qa_pairs.csv file with questions and answers and move to next task",
agent=qa_generator,
name="generate_task",
is_start=True,
next_tasks=["evaluate_total_questions"],
task_type="decision",
condition={
"more": "generate_task",
"done": "generate_cot"
}
)
generate_cot_task = Task(
name="generate_cot",
description="""Generate chain of thought solutions for each question in the input file.
After processing all questions, respond with a JSON object:
{
"response": "done",
"decision": "done"
}
""",
expected_output="done",
agent=cot_generator,
input_file="qa_pairs.csv",
task_type="loop",
next_tasks=["upload_to_huggingface"],
condition={
"done": ["upload_to_huggingface"],
"exit": [],
},
output_pydantic=DecisionModel # Use Pydantic model for output validation
)
upload_to_huggingface_task = Task(
name="upload_to_huggingface",
description="""Upload to Huggingface:
1. Save to cot_solutions.csv
2. Upload to mervinpraison/cot-dataset""",
expected_output="Dataset published successfully",
agent=upload_to_huggingface
)
# Initialize workflow
agents = PraisonAIAgents(
agents=[qa_generator, cot_generator, upload_to_huggingface],
tasks=[generate_task, generate_cot_task, upload_to_huggingface_task],
process="workflow",
max_iter=10,
verbose=False
)
agents.start()
+1 -1
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.0.57 gunicorn markdown
RUN pip install flask praisonai==2.0.60 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.0.57 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.0.60 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+12 -76
View File
@@ -112,86 +112,22 @@ graph LR
<Step title="Create Agent">
Create `app.py`:
<CodeGroup>
```python Single Agent
from praisonaiagents import Agent, Task, PraisonAIAgents
```python Single Agent
from praisonaiagents import Agent
# Create an agent
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o"
)
agent = Agent(instructions="Your are a helpful AI assistant")
agent.start("Write a movie script about a robot in Mars")
```
# Define a task
task = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
```python Multi Agents
from praisonaiagents import Agent, PraisonAIAgents
# Run the agents
agents = PraisonAIAgents(
agents=[researcher],
tasks=[task],
verbose=False
)
research_agent = Agent(instructions="Research about AI")
summarise_agent = Agent(instructions="Summarise research agent's findings")
result = agents.start()
```
```python Multiple Agents
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create multiple agents
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o",
markdown=True
)
writer = Agent(
name="Writer",
role="Tech Content Strategist",
goal="Craft compelling content on tech advancements",
backstory="You are a content strategist",
llm="gpt-4o",
markdown=True
)
# Define multiple tasks
task1 = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
task2 = Task(
name="writing_task",
description="Create a blog post about AI advancements",
expected_output="A blog post",
agent=writer
)
# Run with hierarchical process
agents = PraisonAIAgents(
agents=[researcher, writer],
tasks=[task1, task2],
verbose=False,
process="hierarchical",
manager_llm="gpt-4o"
)
result = agents.start()
```
agents = PraisonAIAgents(agents=[research_agent, summarise_agent])
agents.start()
```
</CodeGroup>
</Step>
+289
View File
@@ -0,0 +1,289 @@
---
title: "Generate Synthetic Reasoning Data Agents"
sidebarTitle: "Generate Reasoning Data"
description: "Learn how to generate chain-of-thought reasoning data using PraisonAI Agents."
icon: "brain"
---
```mermaid
flowchart TD
Start[Start] --> Generator[Question Answer Generator Agent]
Generator --> Evaluator[Evaluator Agent]
Evaluator --> COT[Reasoning Steps / COT Generator Agent]
COT --> COT
COT --> Upload[HuggingFace Uploader Agent]
Upload --> End[End]
style Start fill:#8B0000,color:#fff
style Generator fill:#2E8B57,color:#fff
style Evaluator fill:#2E8B57,color:#fff
style COT fill:#2E8B57,color:#fff
style Upload fill:#2E8B57,color:#fff
style End fill:#8B0000,color:#fff
```
## What is Chain-of-Thought Generation?
Chain-of-Thought (CoT) Generation is a process where AI agents create detailed, step-by-step reasoning paths for solving problems. This involves generating questions, evaluating them, producing detailed solution steps, and making the data available for training and analysis.
## Quick Start
<Steps>
<Step title="Install Package">
First, install the PraisonAI Agents package:
```bash
pip install "praisonaiagents[llm]" datasets huggingface-hub pandas
```
</Step>
<Step title="Set API Key">
Set your OpenAI API key as an environment variable in your terminal:
```bash
export OPENAI_API_KEY=your_api_key_here
```
</Step>
<Step title="Create a file">
Create a new file `app.py` with the basic setup:
```python
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import cot_save, cot_upload_to_huggingface
from pydantic import BaseModel
import os
# Define Pydantic model for structured output
class DecisionModel(BaseModel):
response: str
decision: str
def write_csv(file_path, data):
"""Write data to CSV file."""
if not os.path.exists(file_path):
with open(file_path, 'w') as file:
file.write(data + '\n')
else:
with open(file_path, 'a') as file:
file.write(data + '\n')
return f"Data appended to {file_path}"
def count_questions(file_path):
"""Count lines in file."""
with open(file_path, 'r') as file:
return sum(1 for _ in file)
# Create specialized agents
qa_generator = Agent(
name="Generator",
role="Question Creator",
goal="Create challenging math and logic questions",
backstory="Expert in educational content creation",
llm="gpt-4o-mini",
tools=[write_csv, count_questions]
)
total_questions_evaluator = Agent(
name="TotalQuestionsEvaluator",
role="Total Questions Evaluator",
goal="Evaluate the total number of questions in qa_pairs.csv file",
backstory="Expert in evaluating the total number of questions in a file",
llm="gpt-4o-mini",
tools=[count_questions],
verbose=False
)
cot_generator = Agent(
name="COTGenerator",
role="Chain of Thought Specialist",
goal="Generate and manage chain of thought solutions for Q&A pairs",
backstory="Expert in breaking down problems and generating detailed solution steps",
tools=[cot_save],
llm="gpt-4o-mini",
verbose=False
)
upload_to_huggingface = Agent(
name="UploadToHuggingface",
role="Upload to Huggingface",
goal="Upload the generated chain of thought solutions to a Huggingface dataset",
backstory="Expert in saving data to Huggingface",
tools=[cot_upload_to_huggingface],
llm="gpt-4o-mini",
verbose=False
)
# Define tasks with workflow
generate_task = Task(
description="""Generate question and answer in csv format without headers: question, answer and append to qa_pairs.csv file
generate 10 unique questions and answers and don't repeat on the same question and answer. Reponse with 'done' when done
with append mode as 'a'
Example question and answer:
question, answer
What is the sum of numbers from 1 to 10?, 55
Number of r's in the word strawberry, 3
""",
expected_output="append to qa_pairs.csv file with questions and answers and move to next task",
agent=qa_generator,
name="generate_task",
is_start=True,
next_tasks=["evaluate_total_questions"],
task_type="decision",
condition={
"more": "generate_task",
"done": "evaluate_total_questions"
}
)
evaluate_total_questions_task = Task(
description="Evaluate the total number of questions in qa_pairs.csv file is 1",
expected_output="Total number of questions in qa_pairs.csv file",
agent=total_questions_evaluator,
task_type="decision",
name="evaluate_total_questions",
condition={
"more": "generate_task",
"done": "generate_cot"
}
)
generate_cot_task = Task(
name="generate_cot",
description="""Generate chain of thought solutions for each question in the input file.
Save to cot_solutions.csv file
Don't generate chain of thought solutions again after receiving the response from Tool Call
After calling the tool, respond with a JSON object:
{
"response": "done",
"decision": "done"
}
""",
expected_output="done",
agent=cot_generator,
input_file="qa_pairs.csv",
task_type="loop",
next_tasks=["upload_to_huggingface"],
condition={
"done": ["upload_to_huggingface"],
"exit": [],
},
output_pydantic=DecisionModel # Use Pydantic model for output validation
)
upload_to_huggingface_task = Task(
name="upload_to_huggingface",
description="""Upload to Huggingface:
1. Save to cot_solutions.csv
2. Upload to mervinpraison/cot-dataset""",
expected_output="Dataset published successfully",
agent=upload_to_huggingface,
tools=[cot_upload_to_huggingface]
)
# Initialize workflow
agents = PraisonAIAgents(
agents=[qa_generator, total_questions_evaluator, cot_generator, upload_to_huggingface],
tasks=[generate_task, evaluate_total_questions_task, generate_cot_task, upload_to_huggingface_task],
process="workflow",
max_iter=30,
verbose=False
)
agents.start()
```
</Step>
</Steps>
## Features
<CardGroup cols={2}>
<Card title="Question Generation" icon="question">
Create challenging math and logic questions with answers.
</Card>
<Card title="Question Evaluation" icon="check-double">
Evaluate and validate generated questions for quality.
</Card>
<Card title="CoT Solutions" icon="diagram-project">
Generate detailed chain-of-thought solutions for each question.
</Card>
<Card title="Data Management" icon="database">
Save and manage generated data in structured formats.
</Card>
<Card title="HuggingFace Integration" icon="cloud-arrow-up">
Upload datasets directly to HuggingFace for sharing.
</Card>
</CardGroup>
## Understanding the Workflow
<AccordionGroup>
<Accordion title="Key Components">
<CardGroup cols={2}>
<Card title="Question Generator" icon="robot">
Creates unique math and logic questions with answers. Uses `write_csv` and `count_questions` tools.
</Card>
<Card title="Questions Evaluator" icon="magnifying-glass-chart">
Validates the total number of generated questions. Uses `count_questions` tool.
</Card>
<Card title="CoT Generator" icon="diagram-project">
Produces detailed step-by-step solutions. Uses `cot_save` tool for solution management.
</Card>
<Card title="HuggingFace Uploader" icon="cloud-arrow-up">
Publishes datasets to HuggingFace. Uses `cot_upload_to_huggingface` tool.
</Card>
</CardGroup>
</Accordion>
<Accordion title="Task Types and Flow Control">
<Tabs>
<Tab title="Decision Tasks">
Used in question generation and evaluation phases.
```python Decision Task Example
generate_task = Task(
task_type="decision",
condition={
"more": "generate_task",
"done": "evaluate_total_questions"
}
)
```
<Note>
Conditions determine whether to continue generating or move forward. The task can loop back to itself or proceed to the next task.
</Note>
</Tab>
<Tab title="Loop Tasks">
Used in Chain-of-Thought generation phase.
```python Loop Task Example
generate_cot_task = Task(
task_type="loop",
input_file="qa_pairs.csv",
output_pydantic=DecisionModel
)
```
<Warning>
Always use Pydantic models for output validation in loop tasks to ensure data consistency.
</Warning>
</Tab>
</Tabs>
<Info>
Each task type serves a specific purpose in the workflow:
- **Decision Tasks**: Control flow and branching logic
- **Loop Tasks**: Process data iteratively with validation
</Info>
</Accordion>
</AccordionGroup>
## Next Steps
<CardGroup>
<Card title="Introduction" icon="book" href="/introduction">
Learn more about PraisonAI and its core concepts
</Card>
<Card title="Quick Start" icon="bolt" href="/quickstart">
Get started with the basics of PraisonAI
</Card>
</CardGroup>
+137
View File
@@ -0,0 +1,137 @@
---
title: "Reasoning Extract Agents"
description: "Learn how to create AI agents that can perform step-by-step reasoning and extract information"
icon: "brain-circuit"
---
```mermaid
flowchart LR
In[Input] --> RA[("Reasoning Agent")]
RA --> Steps[Reasoning Steps]
Steps --> SA[("Small Agent")]
SA --> Out[Output]
style In fill:#8B0000,color:#fff
style RA fill:#2E8B57,color:#fff,shape:circle
style Steps fill:#4169E1,color:#fff
style SA fill:#2E8B57,color:#fff,shape:circle
style Out fill:#8B0000,color:#fff
```
A workflow where a reasoning agent breaks down complex problems into steps, followed by a smaller agent that processes these steps to provide concise answers.
## Prerequisites
<Steps>
<Step title="Install Package">
Install required packages:
```bash
pip install "praisonaiagents[llm]"
```
<Note>
praisonaiagents[llm] includes all necessary dependencies for reasoning agents
</Note>
</Step>
<Step title="Set API Key">
Configure your API key:
```bash
export OPENAI_API_KEY=your_api_key_here
```
</Step>
<Step title="Create File">
Create a new file called `app.py` and add the following code:
</Step>
<Step title="Run Application">
Execute the script:
```bash
python app.py
```
</Step>
</Steps>
## Code
```python
from praisonaiagents import Agent, Task, PraisonAIAgents
reasoning_agent = Agent(
role="Helpful Assistant",
reasoning_steps=True,
llm="deepseek/deepseek-reasoner"
)
small_agent = Agent(
role="Helpful Assistant",
llm="gpt-3.5-turbo"
)
reasoning_task = Task(
description="How many r's in the word 'Strawberry'?",
agent=reasoning_agent
)
small_task = Task(
description="With the provided reasoning tell me how many r's in the word 'Strawberry'?",
agent=small_agent
)
agents = PraisonAIAgents(
agents=[reasoning_agent, small_agent],
tasks=[reasoning_task, small_task]
)
agents.start()
```
## Features
<CardGroup cols={2}>
<Card title="Step-by-Step Reasoning" icon="list-check">
Break down complex problems into logical steps.
</Card>
<Card title="Multi-Agent Collaboration" icon="users">
Combine reasoning and processing agents.
</Card>
<Card title="Flexible Models" icon="arrows-split-up-and-left">
Use different models for reasoning and processing.
</Card>
<Card title="Task Chaining" icon="link">
Connect reasoning output to processing tasks.
</Card>
</CardGroup>
## Understanding Reasoning Agents
<Card title="What are Reasoning Agents?" icon="question">
Reasoning agents enable:
- Detailed step-by-step analysis
- Logical problem decomposition
- Clear reasoning paths
- Enhanced decision making
</Card>
## Troubleshooting
<CardGroup cols={2}>
<Card title="Reasoning Issues" icon="triangle-exclamation">
If reasoning steps aren't clear:
- Check reasoning_steps parameter
- Verify model compatibility
- Review task descriptions
</Card>
<Card title="Agent Communication" icon="messages">
If agents aren't coordinating:
- Ensure task order is correct
- Check task dependencies
- Verify agent configurations
</Card>
</CardGroup>
<Note>
The reasoning agent uses a specialized model (deepseek-reasoner) optimized for step-by-step analysis, while the small agent can use a more general-purpose model for processing the reasoning output.
</Note>
+424 -124
View File
@@ -1,7 +1,7 @@
---
title: "Praison AI"
sidebarTitle: "Home"
description: "Comprehensive guide to PraisonAI - an AI Agents Framework with Self Reflection, combining PraisonAI Agents, AutoGen, and CrewAI for building multi-agent LLM systems"
description: "PraisonAI is a production-ready Multi-AI Agents framework with self-reflection, designed to create AI Agents to automate and solve problems ranging from simple tasks to complex challenges. By integrating PraisonAI Agents, AutoGen, and CrewAI into a low-code solution, it streamlines the building and management of multi-agent LLM systems, emphasising simplicity, customisation, and effective human-agent collaboration."
icon: "house"
openGraph:
url: "/"
@@ -66,24 +66,53 @@ seo:
Internet search using Crawl4AI and Tavily
</Card>
</CardGroup>
<br />
<div className="relative w-full aspect-video overflow-hidden rounded-lg shadow-lg mb-8">
<iframe
className="absolute top-0 left-0 w-full h-full border-0"
src="https://www.youtube.com/embed/Fn1lQjC0GO0"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{
maxWidth: '100vw',
margin: '0 auto'
}}
></iframe>
</div>
## Installation Options
## Install
<Tabs>
<Tab title="Code">
<Steps>
<Step title="Install Package">
```bash
pip install praisonaiagents
```
</Step>
<Step title="Set API Key">
```bash
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
```
</Step>
<Step title="Create File">
Create `app.py` file
## Code Example
<CodeGroup>
```python Single Agent
from praisonaiagents import Agent
agent = Agent(instructions="Your are a helpful AI assistant")
agent.start("Write a movie script about a robot in Mars")
```
```python Multi Agents
from praisonaiagents import Agent, PraisonAIAgents
research_agent = Agent(instructions="Research about AI")
summarise_agent = Agent(instructions="Summarise research agent's findings")
agents = PraisonAIAgents(agents=[research_agent, summarise_agent])
agents.start()
```
</CodeGroup>
</Step>
<Step title="Run Script">
```bash
python app.py
```
</Step>
</Steps>
</Tab>
<Tab title="No Code">
<Tip>
PraisonAI combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration.
@@ -109,115 +138,361 @@ seo:
</Step>
</Steps>
</Tab>
<Tab title="Code">
<Steps>
<Step title="Install Package">
```bash
pip install praisonaiagents
```
</Step>
<Step title="Set API Key">
```bash
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
```
</Step>
<Step title="Create File">
Create `app.py` file
## Code Example
<CodeGroup>
```python Basic
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create an agent
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o"
)
# Define a task
task = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
# Run the agents
agents = PraisonAIAgents(
agents=[researcher],
tasks=[task],
verbose=False
)
result = agents.start()
```
```python Advanced
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create multiple agents
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o",
markdown=True
)
writer = Agent(
name="Writer",
role="Tech Content Strategist",
goal="Craft compelling content on tech advancements",
backstory="You are a content strategist",
llm="gpt-4o",
markdown=True
)
# Define multiple tasks
task1 = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
task2 = Task(
name="writing_task",
description="Create a blog post about AI advancements",
expected_output="A blog post",
agent=writer
)
# Run with hierarchical process
agents = PraisonAIAgents(
agents=[researcher, writer],
tasks=[task1, task2],
verbose=False,
process="hierarchical",
manager_llm="gpt-4o"
)
result = agents.start()
```
</CodeGroup>
</Step>
<Step title="Run Script">
```bash
python app.py
```
</Step>
</Steps>
</Tab>
</Tabs>
## AI Agents Flow
```mermaid
graph LR
%% Define the main flow
Start([▶ Start]) --> Agent1
Agent1 --> Process[⚙ Process]
Process --> Agent2
Agent2 --> Output([✓ Output])
Process -.-> Agent1
%% Define subgraphs for agents and their tasks
subgraph Agent1[ ]
Task1[📋 Task]
AgentIcon1[🤖 AI Agent]
Tools1[🔧 Tools]
Task1 --- AgentIcon1
AgentIcon1 --- Tools1
end
subgraph Agent2[ ]
Task2[📋 Task]
AgentIcon2[🤖 AI Agent]
Tools2[🔧 Tools]
Task2 --- AgentIcon2
AgentIcon2 --- Tools2
end
classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
classDef tools fill:#2E8B57,stroke:#7C90A0,color:#fff
classDef transparent fill:none,stroke:none
class Start,Output,Task1,Task2 input
class Process,AgentIcon1,AgentIcon2 process
class Tools1,Tools2 tools
class Agent1,Agent2 transparent
```
## AI Agents with Tools
Create AI agents that can use tools to interact with external systems and perform actions.
```mermaid
flowchart TB
subgraph Tools
direction TB
T3[Internet Search]
T1[Code Execution]
T2[Formatting]
end
Input[Input] ---> Agents
subgraph Agents
direction LR
A1[Agent 1]
A2[Agent 2]
A3[Agent 3]
end
Agents ---> Output[Output]
T3 --> A1
T1 --> A2
T2 --> A3
style Tools fill:#189AB4,color:#fff
style Agents fill:#8B0000,color:#fff
style Input fill:#8B0000,color:#fff
style Output fill:#8B0000,color:#fff
```
## AI Agents with Memory
Create AI agents with memory capabilities for maintaining context and information across tasks.
```mermaid
flowchart TB
subgraph Memory
direction TB
STM[Short Term]
LTM[Long Term]
end
subgraph Store
direction TB
DB[(Vector DB)]
end
Input[Input] ---> Agents
subgraph Agents
direction LR
A1[Agent 1]
A2[Agent 2]
A3[Agent 3]
end
Agents ---> Output[Output]
Memory <--> Store
Store <--> A1
Store <--> A2
Store <--> A3
style Memory fill:#189AB4,color:#fff
style Store fill:#2E8B57,color:#fff
style Agents fill:#8B0000,color:#fff
style Input fill:#8B0000,color:#fff
style Output fill:#8B0000,color:#fff
```
## AI Agents with Different Processes
### Sequential Process
The simplest form of task execution where tasks are performed one after another.
```mermaid
graph LR
Input[Input] --> A1
subgraph Agents
direction LR
A1[Agent 1] --> A2[Agent 2] --> A3[Agent 3]
end
A3 --> Output[Output]
classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
classDef transparent fill:none,stroke:none
class Input,Output input
class A1,A2,A3 process
class Agents transparent
```
### Hierarchical Process
Uses a manager agent to coordinate task execution and agent assignments.
```mermaid
graph TB
Input[Input] --> Manager
subgraph Agents
Manager[Manager Agent]
subgraph Workers
direction LR
W1[Worker 1]
W2[Worker 2]
W3[Worker 3]
end
Manager --> W1
Manager --> W2
Manager --> W3
end
W1 --> Manager
W2 --> Manager
W3 --> Manager
Manager --> Output[Output]
classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
classDef transparent fill:none,stroke:none
class Input,Output input
class Manager,W1,W2,W3 process
class Agents,Workers transparent
```
### Workflow Process
Advanced process type supporting complex task relationships and conditional execution.
```mermaid
graph LR
Input[Input] --> Start
subgraph Workflow
direction LR
Start[Start] --> C1{Condition}
C1 --> |Yes| A1[Agent 1]
C1 --> |No| A2[Agent 2]
A1 --> Join
A2 --> Join
Join --> A3[Agent 3]
end
A3 --> Output[Output]
classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
classDef decision fill:#2E8B57,stroke:#7C90A0,color:#fff
classDef transparent fill:none,stroke:none
class Input,Output input
class Start,A1,A2,A3,Join process
class C1 decision
class Workflow transparent
```
#### Agentic Routing Workflow
Create AI agents that can dynamically route tasks to specialized LLM instances.
```mermaid
flowchart LR
In[In] --> Router[LLM Call Router]
Router --> LLM1[LLM Call 1]
Router --> LLM2[LLM Call 2]
Router --> LLM3[LLM Call 3]
LLM1 --> Out[Out]
LLM2 --> Out
LLM3 --> Out
style In fill:#8B0000,color:#fff
style Router fill:#2E8B57,color:#fff
style LLM1 fill:#2E8B57,color:#fff
style LLM2 fill:#2E8B57,color:#fff
style LLM3 fill:#2E8B57,color:#fff
style Out fill:#8B0000,color:#fff
```
#### Agentic Orchestrator Worker
Create AI agents that orchestrate and distribute tasks among specialized workers.
```mermaid
flowchart LR
In[In] --> Router[LLM Call Router]
Router --> LLM1[LLM Call 1]
Router --> LLM2[LLM Call 2]
Router --> LLM3[LLM Call 3]
LLM1 --> Synthesizer[Synthesizer]
LLM2 --> Synthesizer
LLM3 --> Synthesizer
Synthesizer --> Out[Out]
style In fill:#8B0000,color:#fff
style Router fill:#2E8B57,color:#fff
style LLM1 fill:#2E8B57,color:#fff
style LLM2 fill:#2E8B57,color:#fff
style LLM3 fill:#2E8B57,color:#fff
style Synthesizer fill:#2E8B57,color:#fff
style Out fill:#8B0000,color:#fff
```
#### Agentic Autonomous Workflow
Create AI agents that can autonomously monitor, act, and adapt based on environment feedback.
```mermaid
flowchart LR
Human[Human] <--> LLM[LLM Call]
LLM -->|ACTION| Environment[Environment]
Environment -->|FEEDBACK| LLM
LLM --> Stop[Stop]
style Human fill:#8B0000,color:#fff
style LLM fill:#2E8B57,color:#fff
style Environment fill:#8B0000,color:#fff
style Stop fill:#333,color:#fff
```
#### Agentic Parallelization
Create AI agents that can execute tasks in parallel for improved performance.
```mermaid
flowchart LR
In[In] --> LLM2[LLM Call 2]
In --> LLM1[LLM Call 1]
In --> LLM3[LLM Call 3]
LLM1 --> Aggregator[Aggregator]
LLM2 --> Aggregator
LLM3 --> Aggregator
Aggregator --> Out[Out]
style In fill:#8B0000,color:#fff
style LLM1 fill:#2E8B57,color:#fff
style LLM2 fill:#2E8B57,color:#fff
style LLM3 fill:#2E8B57,color:#fff
style Aggregator fill:#fff,color:#000
style Out fill:#8B0000,color:#fff
```
#### Agentic Prompt Chaining
Create AI agents with sequential prompt chaining for complex workflows.
```mermaid
flowchart LR
In[In] --> LLM1[LLM Call 1] --> Gate{Gate}
Gate -->|Pass| LLM2[LLM Call 2] -->|Output 2| LLM3[LLM Call 3] --> Out[Out]
Gate -->|Fail| Exit[Exit]
style In fill:#8B0000,color:#fff
style LLM1 fill:#2E8B57,color:#fff
style LLM2 fill:#2E8B57,color:#fff
style LLM3 fill:#2E8B57,color:#fff
style Out fill:#8B0000,color:#fff
style Exit fill:#8B0000,color:#fff
```
#### Agentic Evaluator Optimizer
Create AI agents that can generate and optimize solutions through iterative feedback.
```mermaid
flowchart LR
In[In] --> Generator[LLM Call Generator]
Generator -->|SOLUTION| Evaluator[LLM Call Evaluator] -->|ACCEPTED| Out[Out]
Evaluator -->|REJECTED + FEEDBACK| Generator
style In fill:#8B0000,color:#fff
style Generator fill:#2E8B57,color:#fff
style Evaluator fill:#2E8B57,color:#fff
style Out fill:#8B0000,color:#fff
```
#### Repetitive Agents
Create AI agents that can efficiently handle repetitive tasks through automated loops.
```mermaid
flowchart LR
In[Input] --> LoopAgent[("Looping Agent")]
LoopAgent --> Task[Task]
Task --> |Next iteration| LoopAgent
Task --> |Done| Out[Output]
style In fill:#8B0000,color:#fff
style LoopAgent fill:#2E8B57,color:#fff,shape:circle
style Task fill:#2E8B57,color:#fff
style Out fill:#8B0000,color:#fff
```
<br />
<div className="relative w-full aspect-video overflow-hidden rounded-lg shadow-lg mb-8">
<iframe
className="absolute top-0 left-0 w-full h-full border-0"
src="https://www.youtube.com/embed/Fn1lQjC0GO0"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{
maxWidth: '100vw',
margin: '0 auto'
}}
></iframe>
</div>
## Integration Options
<AccordionGroup>
@@ -338,4 +613,29 @@ seo:
<div className="hover:opacity-80 transition-opacity">
<img src="https://img.shields.io/github/forks/MervinPraison/PraisonAI?style=social" alt="GitHub Forks" />
</div>
</div>
</div>
## Video Tutorials
| Topic | Video |
|-------|--------|
| AI Agents with Self Reflection | [![Self Reflection](https://img.youtube.com/vi/vLXobEN2Vc8/0.jpg)](https://www.youtube.com/watch?v=vLXobEN2Vc8) |
| AI Agents with Reasoning | [![Reasoning](https://img.youtube.com/vi/KNDVWGN3TpM/0.jpg)](https://www.youtube.com/watch?v=KNDVWGN3TpM) |
| Multimodal AI Agents | [![Multimodal](https://img.youtube.com/vi/hjAWmUT1qqY/0.jpg)](https://www.youtube.com/watch?v=hjAWmUT1qqY) |
| AI Agents Workflow | [![Workflow](https://img.youtube.com/vi/yWTH44QPl2A/0.jpg)](https://www.youtube.com/watch?v=yWTH44QPl2A) |
| Async AI Agents | [![Async](https://img.youtube.com/vi/VhVQfgo00LE/0.jpg)](https://www.youtube.com/watch?v=VhVQfgo00LE) |
| Mini AI Agents | [![Mini](https://img.youtube.com/vi/OkvYp5aAGSg/0.jpg)](https://www.youtube.com/watch?v=OkvYp5aAGSg) |
| AI Agents with Memory | [![Memory](https://img.youtube.com/vi/1hVfVxvPnnQ/0.jpg)](https://www.youtube.com/watch?v=1hVfVxvPnnQ) |
| Repetitive Agents | [![Repetitive](https://img.youtube.com/vi/dAYGxsjDOPg/0.jpg)](https://www.youtube.com/watch?v=dAYGxsjDOPg) |
| Introduction | [![Introduction](https://img.youtube.com/vi/Fn1lQjC0GO0/0.jpg)](https://www.youtube.com/watch?v=Fn1lQjC0GO0) |
| Tools Overview | [![Tools Overview](https://img.youtube.com/vi/XaQRgRpV7jo/0.jpg)](https://www.youtube.com/watch?v=XaQRgRpV7jo) |
| Custom Tools | [![Custom Tools](https://img.youtube.com/vi/JSU2Rndh06c/0.jpg)](https://www.youtube.com/watch?v=JSU2Rndh06c) |
| Firecrawl Integration | [![Firecrawl](https://img.youtube.com/vi/UoqUDcLcOYo/0.jpg)](https://www.youtube.com/watch?v=UoqUDcLcOYo) |
| User Interface | [![UI](https://img.youtube.com/vi/tg-ZjNl3OCg/0.jpg)](https://www.youtube.com/watch?v=tg-ZjNl3OCg) |
| Crawl4AI Integration | [![Crawl4AI](https://img.youtube.com/vi/KAvuVUh0XU8/0.jpg)](https://www.youtube.com/watch?v=KAvuVUh0XU8) |
| Chat Interface | [![Chat](https://img.youtube.com/vi/sw3uDqn2h1Y/0.jpg)](https://www.youtube.com/watch?v=sw3uDqn2h1Y) |
| Code Interface | [![Code](https://img.youtube.com/vi/_5jQayO-MQY/0.jpg)](https://www.youtube.com/watch?v=_5jQayO-MQY) |
| Mem0 Integration | [![Mem0](https://img.youtube.com/vi/KIGSgRxf1cY/0.jpg)](https://www.youtube.com/watch?v=KIGSgRxf1cY) |
| Training | [![Training](https://img.youtube.com/vi/aLawE8kwCrI/0.jpg)](https://www.youtube.com/watch?v=aLawE8kwCrI) |
| Realtime Voice Interface | [![Realtime](https://img.youtube.com/vi/frRHfevTCSw/0.jpg)](https://www.youtube.com/watch?v=frRHfevTCSw) |
| Call Interface | [![Call](https://img.youtube.com/vi/m1cwrUG2iAk/0.jpg)](https://www.youtube.com/watch?v=m1cwrUG2iAk) |
+99 -121
View File
@@ -89,143 +89,121 @@ graph LR
## Getting Started is Easy
<Tabs>
<Tab title="Code">
<Steps>
<Steps>
<Step title="Install Package">
Install the PraisonAI Agents package:
```bash
pip install praisonaiagents
```
</Step>
<Step title="Install PraisonAI">
Install the core package:
```bash Terminal
pip install praisonaiagents
```
</Step>
<Step title="Set API Key">
```bash
export OPENAI_API_KEY=your_openai_key
```
Generate your OpenAI API key from [OpenAI](https://platform.openai.com/api-keys).
Use other LLM providers like Ollama, Anthropic, Groq, Google, etc. Please refer to the [Models](/models) for more information.
</Step>
<Step title="Configure Environment">
```bash Terminal
export OPENAI_API_KEY=your_openai_key
```
Generate your OpenAI API key from [OpenAI](https://platform.openai.com/api-keys)
Use other LLM providers like Ollama, Anthropic, Groq, Google, etc. Please refer to the [Models](/models) for more information.
</Step>
<Step title="Create Agents">
Create `app.py`:
<CodeGroup>
```python Single Agent
from praisonaiagents import Agent, PraisonAIAgents
<Step title="Create Agent">
Create `app.py`:
<CodeGroup>
```python Single Agent
from praisonaiagents import Agent, Task, PraisonAIAgents
# Create a simple agent
summarise_agent = Agent(instructions="Summarise Photosynthesis")
# Create an agent
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o"
)
# Run the agent
agents = PraisonAIAgents(agents=[summarise_agent])
agents.start()
```
# Define a task
task = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
```python Multiple Agents
from praisonaiagents import Agent, PraisonAIAgents
# Run the agents
agents = PraisonAIAgents(
agents=[researcher],
tasks=[task],
verbose=False
)
# Create agents with specific roles
diet_agent = Agent(
instructions="Give me 5 healthy food recipes",
)
result = agents.start()
```
blog_agent = Agent(
instructions="Write a blog post about the food recipes",
)
```python Multiple Agents
from praisonaiagents import Agent, Task, PraisonAIAgents
# Run multiple agents
agents = PraisonAIAgents(agents=[diet_agent, blog_agent])
agents.start()
```
</CodeGroup>
</Step>
# Create multiple agents
researcher = Agent(
name="Researcher",
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI",
backstory="You are an expert at a technology research group",
verbose=True,
llm="gpt-4o",
markdown=True
)
<Step title="Run Agents">
Execute your script:
```bash
python app.py
```
writer = Agent(
name="Writer",
role="Tech Content Strategist",
goal="Craft compelling content on tech advancements",
backstory="You are a content strategist",
llm="gpt-4o",
markdown=True
)
# Define multiple tasks
task1 = Task(
name="research_task",
description="Analyze 2024's AI advancements",
expected_output="A detailed report",
agent=researcher
)
task2 = Task(
name="writing_task",
description="Create a blog post about AI advancements",
expected_output="A blog post",
agent=writer
)
# Run with hierarchical process
agents = PraisonAIAgents(
agents=[researcher, writer],
tasks=[task1, task2],
verbose=False,
process="hierarchical",
manager_llm="gpt-4o"
)
result = agents.start()
```
</CodeGroup>
</Step>
<Step title="Start Agents">
Execute your script:
```bash Terminal
python app.py
```
</Step>
</Steps>
</Tab>
You'll see:
- Agent initialization
- Task execution progress
- Final results
<Tip>
You have successfully CreatedAI Agents and made them work for you!
</Tip>
</Step>
</Steps>
</Tab>
<Tab title="No Code">
<Steps>
<Step title="Install Package">
Install the No Code PraisonAI Package:
```bash
pip install praisonaiagents
```
</Step>
<Step title="Install PraisonAI">
Install the PraisonAI package:
```bash
pip install praisonai
```
</Step>
<Step title="Set API Key">
```bash
export OPENAI_API_KEY=your_openai_key
```
</Step>
<Step title="Set API Key">
Set your OpenAI API key as an environment variable in your terminal:
```bash Terminal
export OPENAI_API_KEY=your_openai_key
```
Generate your OpenAI API key from [OpenAI](https://platform.openai.com/api-keys)
Use other LLM providers like Ollama, Anthropic, Groq, Google, etc. Please refer to the [Models](/models) for more information.
</Step>
<Step title="Create Config">
Create `agents.yaml`:
<Step title="Start AutoAgents">
Run your AutoAgents:
```bash
praisonai --auto "Create a movie script about a robot in Mars"
```
</Step>
<CodeGroup>
```yaml Single Agent
roles:
summarise_agent:
instructions: Summarise Photosynthesis
```
```yaml Multiple Agents
roles:
diet_agent:
instructions: Give me 5 healthy food recipes
blog_agent:
instructions: Write a blog post about the food recipes
```
</CodeGroup>
<Note>
You can automatically create `agents.yaml` using:
```bash
praisonai --init "your task description"
```
</Note>
</Step>
<Step title="Run Agents">
Execute your config:
```bash
praisonai agents.yaml
```
</Step>
</Steps>
</Tab>
</Tabs>
+2
View File
@@ -118,11 +118,13 @@
"features/autoagents",
"features/selfreflection",
"features/rag",
"features/reasoning-extract",
"features/reasoning",
"features/multimodal",
"features/langchain",
"features/async",
"features/mini",
"features/generate-reasoning",
"features/codeagent",
"features/mathagent",
"features/structured",
+142
View File
@@ -0,0 +1,142 @@
from praisonaiagents import Agent, Task, PraisonAIAgents
from praisonaiagents.tools import cot_save, cot_upload_to_huggingface
from pydantic import BaseModel
import os
# Define Pydantic model for structured output
class DecisionModel(BaseModel):
response: str
decision: str
def write_csv(file_path, data):
"""Write data to CSV file."""
if not os.path.exists(file_path):
with open(file_path, 'w') as file:
file.write(data + '\n')
else:
with open(file_path, 'a') as file:
file.write(data + '\n')
return f"Data appended to {file_path}"
def count_questions(file_path):
"""Count lines in file."""
with open(file_path, 'r') as file:
return sum(1 for _ in file)
# Keep existing agents with minimal changes
qa_generator = Agent(
name="Generator",
role="Question Creator",
goal="Create challenging math and logic questions",
backstory="Expert in educational content creation",
llm="gpt-4o-mini",
tools=[write_csv, count_questions]
)
total_questions_evaluator = Agent(
name="TotalQuestionsEvaluator",
role="Total Questions Evaluator",
goal="Evaluate the total number of questions in qa_pairs.csv file",
backstory="Expert in evaluating the total number of questions in a file",
llm="gpt-4o-mini",
tools=[count_questions],
verbose=False
)
cot_generator = Agent(
name="COTGenerator",
role="Chain of Thought Specialist",
goal="Generate and manage chain of thought solutions for Q&A pairs",
backstory="Expert in breaking down problems and generating detailed solution steps",
tools=[cot_save],
llm="gpt-4o-mini",
verbose=False
)
upload_to_huggingface = Agent(
name="UploadToHuggingface",
role="Upload to Huggingface",
goal="Upload the generated chain of thought solutions to a Huggingface dataset",
backstory="Expert in saving data to Huggingface",
tools=[cot_upload_to_huggingface],
llm="gpt-4o-mini",
verbose=False
)
# Define tasks with workflow improvements
generate_task = Task(
description="""Generate question and answer in csv format without headers: question, answer and append to qa_pairs.csv file
generate 10 unique questions and answers and don't repeat on the same question and answer. Reponse with 'done' when done
with append mode as 'a'
Example question and answer:
question, answer
What is the sum of numbers from 1 to 10?, 55
Number of r's in the word strawberry, 3
""",
expected_output="append to qa_pairs.csv file with questions and answers and move to next task",
agent=qa_generator,
name="generate_task",
is_start=True,
next_tasks=["evaluate_total_questions"],
task_type="decision",
condition={
"more": "generate_task",
"done": "evaluate_total_questions"
}
)
evaluate_total_questions_task = Task(
description="Evaluate the total number of questions in qa_pairs.csv file is 1",
expected_output="Total number of questions in qa_pairs.csv file",
agent=total_questions_evaluator,
task_type="decision",
name="evaluate_total_questions",
condition={
"more": "generate_task",
"done": "generate_cot"
}
)
generate_cot_task = Task(
name="generate_cot",
description="""Generate chain of thought solutions for each question in the input file.
Save to cot_solutions.csv file
Don't generate chain of thought solutions again after receiving the response from Tool Call
After calling the tool, respond with a JSON object:
{
"response": "done",
"decision": "done"
}
""",
expected_output="done",
agent=cot_generator,
input_file="qa_pairs.csv",
task_type="loop",
next_tasks=["upload_to_huggingface"],
condition={
"done": ["upload_to_huggingface"],
"exit": [],
},
output_pydantic=DecisionModel # Use Pydantic model for output validation
)
upload_to_huggingface_task = Task(
name="upload_to_huggingface",
description="""Upload to Huggingface:
1. Save to cot_solutions.csv
2. Upload to mervinpraison/cot-dataset""",
expected_output="Dataset published successfully",
agent=upload_to_huggingface,
tools=[cot_upload_to_huggingface]
)
# Initialize workflow
agents = PraisonAIAgents(
agents=[qa_generator, total_questions_evaluator, cot_generator, upload_to_huggingface],
tasks=[generate_task, evaluate_total_questions_task, generate_cot_task, upload_to_huggingface_task],
process="workflow",
max_iter=30,
verbose=False
)
agents.start()
+1 -1
View File
@@ -3,7 +3,7 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.57.tar.gz"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/2.0.60.tar.gz"
sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum
license "MIT"
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.0.57 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.0.60 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+4 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "PraisonAI"
version = "2.0.57"
version = "2.0.60"
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."
readme = "README.md"
license = ""
@@ -12,7 +12,7 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.50",
"praisonaiagents>=0.0.60",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
@@ -84,7 +84,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.7", "crewai"]
[tool.poetry]
name = "PraisonAI"
version = "2.0.57"
version = "2.0.60"
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."
authors = ["Mervin Praison"]
license = ""
@@ -102,7 +102,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.50"
praisonaiagents = ">=0.0.60"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
Generated
+5 -5
View File
@@ -3060,7 +3060,7 @@ wheels = [
[[package]]
name = "praisonai"
version = "2.0.57"
version = "2.0.60"
source = { editable = "." }
dependencies = [
{ name = "instructor" },
@@ -3197,7 +3197,7 @@ requires-dist = [
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.7" },
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.7" },
{ name = "praisonaiagents", specifier = ">=0.0.50" },
{ name = "praisonaiagents", specifier = ">=0.0.60" },
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
@@ -3250,16 +3250,16 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.50"
version = "0.0.60"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "openai" },
{ name = "pydantic" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/44/52c6e7a0c8bef2cc861e35206c0a5fee350b07134d0c4745dfb62ddfc09d/praisonaiagents-0.0.50.tar.gz", hash = "sha256:3cb6447422a76aff6846df6047e885c7552516ec6a36321bb0c0f663dfcffa24", size = 85120 }
sdist = { url = "https://files.pythonhosted.org/packages/ba/31/19a0226dda6c6a367294b613296852e89294f5b839482157e8e7b8b6985a/praisonaiagents-0.0.60.tar.gz", hash = "sha256:38404fc976c086bde71ad51fff7b417a105574ba7877f71403625cef90494fc0", size = 100110 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/b5/5433a5611a0798063fd1e3fdbd74f90171f2a9b593adfe448865b30b8c8b/praisonaiagents-0.0.50-py3-none-any.whl", hash = "sha256:3a0657d4bfd1ad70d319ab741b87e9b81b0ffd7aa57499afdc9bd914bdf7f4cd", size = 103242 },
{ url = "https://files.pythonhosted.org/packages/13/b0/ae8e2b0b3d0dc1e259e670506530db46cc18ab8300e98323788299e62a95/praisonaiagents-0.0.60-py3-none-any.whl", hash = "sha256:a3a73b60bb8fd760bdeeeed3a7dded49223dc3a29044e73d2af174a0179f710d", size = 118558 },
]
[[package]]