mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 08:11:07 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f62e9201a | ||
|
|
1b69360d9f | ||
|
|
9bdc6545ad | ||
|
|
06cd909367 | ||
|
|
67aeece7fc | ||
|
|
ef67d0753c | ||
|
|
f374f9f791 | ||
|
|
492fb29af9 | ||
|
|
cd56ebd5cf | ||
|
|
08e99f6ef7 | ||
|
|
670d653ae9 | ||
|
|
faa014b8b0 | ||
|
|
6d454d0c6e | ||
|
|
0ec3fe3510 | ||
|
|
707888cdf9 | ||
|
|
42051630bc | ||
|
|
bfcccb6355 | ||
|
|
e329afc394 | ||
|
|
9417cc6457 | ||
|
|
10a3d279ae | ||
|
|
2a916208e1 | ||
|
|
bcd0bbbd24 | ||
|
|
5701a40f11 | ||
|
|
3ffac395bb | ||
|
|
37119aa3ac | ||
|
|
8b2d324d3b | ||
|
|
6d0b29e60d | ||
|
|
583cbc5efa | ||
|
|
90dd84a152 |
No files matched your search
@@ -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"
|
||||
|
||||
[](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 | [](https://www.youtube.com/watch?v=vLXobEN2Vc8) |
|
||||
| AI Agents with Reasoning | [](https://www.youtube.com/watch?v=KNDVWGN3TpM) |
|
||||
| Multimodal AI Agents | [](https://www.youtube.com/watch?v=hjAWmUT1qqY) |
|
||||
| AI Agents Workflow | [](https://www.youtube.com/watch?v=yWTH44QPl2A) |
|
||||
| Async AI Agents | [](https://www.youtube.com/watch?v=VhVQfgo00LE) |
|
||||
| Mini AI Agents | [](https://www.youtube.com/watch?v=OkvYp5aAGSg) |
|
||||
| AI Agents with Memory | [](https://www.youtube.com/watch?v=1hVfVxvPnnQ) |
|
||||
| Repetitive Agents | [](https://www.youtube.com/watch?v=dAYGxsjDOPg) |
|
||||
| Introduction | [](https://www.youtube.com/watch?v=Fn1lQjC0GO0) |
|
||||
| Tools Overview | [](https://www.youtube.com/watch?v=XaQRgRpV7jo) |
|
||||
| Custom Tools | [](https://www.youtube.com/watch?v=JSU2Rndh06c) |
|
||||
|
||||
+143
-1
@@ -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.
|
||||
@@ -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.")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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={
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"
|
||||
|
||||
Generated
+1
-1
@@ -1868,7 +1868,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.50"
|
||||
version = "0.0.60"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "openai" },
|
||||
|
||||
@@ -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?"
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
|
||||
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')
|
||||
|
||||
|
||||
+12
-76
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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 | [](https://www.youtube.com/watch?v=vLXobEN2Vc8) |
|
||||
| AI Agents with Reasoning | [](https://www.youtube.com/watch?v=KNDVWGN3TpM) |
|
||||
| Multimodal AI Agents | [](https://www.youtube.com/watch?v=hjAWmUT1qqY) |
|
||||
| AI Agents Workflow | [](https://www.youtube.com/watch?v=yWTH44QPl2A) |
|
||||
| Async AI Agents | [](https://www.youtube.com/watch?v=VhVQfgo00LE) |
|
||||
| Mini AI Agents | [](https://www.youtube.com/watch?v=OkvYp5aAGSg) |
|
||||
| AI Agents with Memory | [](https://www.youtube.com/watch?v=1hVfVxvPnnQ) |
|
||||
| Repetitive Agents | [](https://www.youtube.com/watch?v=dAYGxsjDOPg) |
|
||||
| Introduction | [](https://www.youtube.com/watch?v=Fn1lQjC0GO0) |
|
||||
| Tools Overview | [](https://www.youtube.com/watch?v=XaQRgRpV7jo) |
|
||||
| Custom Tools | [](https://www.youtube.com/watch?v=JSU2Rndh06c) |
|
||||
| Firecrawl Integration | [](https://www.youtube.com/watch?v=UoqUDcLcOYo) |
|
||||
| User Interface | [](https://www.youtube.com/watch?v=tg-ZjNl3OCg) |
|
||||
| Crawl4AI Integration | [](https://www.youtube.com/watch?v=KAvuVUh0XU8) |
|
||||
| Chat Interface | [](https://www.youtube.com/watch?v=sw3uDqn2h1Y) |
|
||||
| Code Interface | [](https://www.youtube.com/watch?v=_5jQayO-MQY) |
|
||||
| Mem0 Integration | [](https://www.youtube.com/watch?v=KIGSgRxf1cY) |
|
||||
| Training | [](https://www.youtube.com/watch?v=aLawE8kwCrI) |
|
||||
| Realtime Voice Interface | [](https://www.youtube.com/watch?v=frRHfevTCSw) |
|
||||
| Call Interface | [](https://www.youtube.com/watch?v=m1cwrUG2iAk) |
|
||||
+99
-121
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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]]
|
||||
|
||||
Reference in new issue
Block a user