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 | ||
|
|
ef7fa66e33 | ||
|
|
1659ed61bc | ||
|
|
b8939ef439 | ||
|
|
4bada9e5fc | ||
|
|
51c4cb00b0 | ||
|
|
ad96549ea3 | ||
|
|
183d10d7cc | ||
|
|
bce1559ab5 | ||
|
|
6f3fb87ba3 | ||
|
|
e7f6b2df67 | ||
|
|
08d72eeb80 | ||
|
|
11aefae90f | ||
|
|
10d5158d27 | ||
|
|
1d8b814fcd | ||
|
|
6e8a5b98a8 | ||
|
|
59d0ff8c95 | ||
|
|
b2109faa06 | ||
|
|
024eaed456 | ||
|
|
4fd3a572cf | ||
|
|
baaf8b6256 | ||
|
|
fe4550d2bc | ||
|
|
db09a94425 | ||
|
|
2db36dc932 | ||
|
|
8c0e4277a4 | ||
|
|
41448617d5 | ||
|
|
49ea23c676 | ||
|
|
bb0dc63680 | ||
|
|
7774f2c565 | ||
|
|
5533fd3476 | ||
|
|
262a90a235 | ||
|
|
60928dcb90 | ||
|
|
c8552e1158 | ||
|
|
c57d7db9bb | ||
|
|
236abc2c83 | ||
|
|
76c5f8fe3f | ||
|
|
82750020d2 | ||
|
|
f49ad9fbc2 | ||
|
|
c086c60fd6 | ||
|
|
4f8f4f2036 | ||
|
|
8e91c20967 | ||
|
|
6f092c1507 | ||
|
|
8f553d3a54 | ||
|
|
2d647e25b5 | ||
|
|
039bb772f5 | ||
|
|
459c2f2b70 | ||
|
|
ee59706357 | ||
|
|
2a51644773 | ||
|
|
cce908fa22 | ||
|
|
acf476f715 | ||
|
|
e5d77de79c | ||
|
|
9ab444b54c | ||
|
|
e13f543c6d | ||
|
|
4f235d358c | ||
|
|
b7847feb1e | ||
|
|
7ac730462c | ||
|
|
9cbc8d082d | ||
|
|
7fb0aed7ac | ||
|
|
206f0d01e7 | ||
|
|
e19a310314 | ||
|
|
1ea572689e | ||
|
|
96df649b05 | ||
|
|
5aed75837a | ||
|
|
9c8f68a05f | ||
|
|
9778a69c4d | ||
|
|
73c7994380 | ||
|
|
82b83ded41 | ||
|
|
fc80db32c2 | ||
|
|
3f115ca7ba | ||
|
|
a32d45e6da | ||
|
|
8823df26ff | ||
|
|
9c2126fbd6 | ||
|
|
741b01bdf0 | ||
|
|
6ed9a69cd8 | ||
|
|
56a42e6d95 | ||
|
|
d2b4affdd1 | ||
|
|
0d5c8b478f | ||
|
|
fdfad70319 | ||
|
|
fba6b2e2a0 | ||
|
|
f9bfb85f44 | ||
|
|
aab4b5b466 | ||
|
|
7ccadde004 | ||
|
|
22a9d5ff23 | ||
|
|
98a11209ea | ||
|
|
df3c97e73a | ||
|
|
27808964be | ||
|
|
b5daf16e0d | ||
|
|
a2cac05d16 | ||
|
|
ad9828d3bb | ||
|
|
85ddf04b47 | ||
|
|
1c1a1b719b | ||
|
|
a8e0914e05 | ||
|
|
ec6d883f4f | ||
|
|
d094cbfac5 | ||
|
|
14e3724aed | ||
|
|
92436277d9 | ||
|
|
3e0fd298e2 | ||
|
|
fad0b41b52 | ||
|
|
6c3e5ee187 | ||
|
|
f842ee7048 | ||
|
|
b0ff6f3f60 | ||
|
|
798f6dad14 | ||
|
|
629bf0d669 | ||
|
|
eaca447b82 | ||
|
|
bef3693627 | ||
|
|
aa62babed6 | ||
|
|
b73f181200 | ||
|
|
66d2998047 | ||
|
|
c5b6f8b91a | ||
|
|
823e46ca84 | ||
|
|
57040333a7 | ||
|
|
8c6800f876 | ||
|
|
0d701ae43c | ||
|
|
7a8c25621c | ||
|
|
218c5443b1 | ||
|
|
f56810341c | ||
|
|
4f601a573f | ||
|
|
43c03f4813 | ||
|
|
03856b74bf | ||
|
|
eadfc0cff0 | ||
|
|
f33c44d4c5 | ||
|
|
3f7885289d | ||
|
|
833c795e9a | ||
|
|
c460c9da60 | ||
|
|
656887c700 | ||
|
|
e0089cac41 | ||
|
|
bf5e069c7b | ||
|
|
94cf264b4c | ||
|
|
ab44fd4b27 | ||
|
|
7bad6cf7e9 | ||
|
|
4ffbe48c58 | ||
|
|
476eeb1184 | ||
|
|
550769017a |
No files matched your search
+2
-1
@@ -65,4 +65,5 @@ outputs
|
||||
agents/praisonaiagents/build
|
||||
agents/praisonaiagents/dist
|
||||
agents/praisonaiagents/praisonaiagents.egg-info
|
||||
.codegpt
|
||||
.codegpt
|
||||
.praison
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.praison.ai">
|
||||
@@ -37,35 +37,24 @@ PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application
|
||||
- 🧠 Reasoning AI Agents
|
||||
- 👁️ Multi Modal AI Agents
|
||||
- 🤝 Multi Agent Collaboration
|
||||
- ⚡ AI Agent Workflow
|
||||
- 🔄 Use CrewAI or AutoGen Framework
|
||||
- 🎭 AI Agent Workflow
|
||||
- 📚 Add Custom Knowledge
|
||||
- 🧠 Agents with Short and Long Term Memory
|
||||
- 📄 Chat with PDF Agents
|
||||
- 💻 Code Interpreter Agents
|
||||
- 📚 RAG Agents
|
||||
- 🤔 Async & Parallel Processing
|
||||
- 🔄 Auto Agents
|
||||
- 🔢 Math Agents
|
||||
- 🎯 Structured Output Agents
|
||||
- 🔗 LangChain Integrated Agents
|
||||
- 📞 Callback Agents
|
||||
- 🤏 Mini AI Agents
|
||||
- 🛠️ 100+ Custom Tools
|
||||
- 📄 YAML Configuration
|
||||
- 💯 100+ LLM Support
|
||||
- 💻 Chat with ENTIRE Codebase
|
||||
- 🎨 Interactive UIs
|
||||
- 📄 YAML-based Configuration
|
||||
- 🛠️ Custom Tool Integration
|
||||
- 🔍 Internet Search Capability (using Crawl4AI and Tavily)
|
||||
- 🖼️ Vision Language Model (VLM) Support
|
||||
- 🎙️ Real-time Voice Interaction
|
||||
|
||||
## Using No Code
|
||||
|
||||
### Auto Mode:
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --auto create a movie script about Robots in Mars
|
||||
```
|
||||
|
||||
### Initialise Mode:
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --init create a movie script about Robots in Mars
|
||||
praisonai
|
||||
```
|
||||
|
||||
## Using Coding
|
||||
## Using Code
|
||||
|
||||
Light weight package dedicated for coding:
|
||||
```bash
|
||||
@@ -76,58 +65,13 @@ pip install praisonaiagents
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
### 1. Single Agent
|
||||
|
||||
Create app.py file and add the code below:
|
||||
```python
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# 1. Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You are an expert at a technology research group,
|
||||
skilled in identifying trends and analyzing complex data.""",
|
||||
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 known for
|
||||
making complex tech topics interesting and easy to understand.""",
|
||||
llm="gpt-4o",
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 2. Define Tasks
|
||||
task1 = Task(
|
||||
name="research_task",
|
||||
description="""Analyze 2024's AI advancements.
|
||||
Find major trends, new technologies, and their effects.""",
|
||||
expected_output="""A detailed report on 2024 AI advancements""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="writing_task",
|
||||
description="""Create a blog post about major AI advancements using the insights you have.
|
||||
Make it interesting, clear, and suited for tech enthusiasts.
|
||||
It should be at least 4 paragraphs long.""",
|
||||
expected_output="A blog post of at least 4 paragraphs",
|
||||
agent=writer,
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2],
|
||||
verbose=False,
|
||||
process="hierarchical",
|
||||
manager_llm="gpt-4o"
|
||||
)
|
||||
|
||||
result = agents.start()
|
||||
from praisonaiagents import Agent
|
||||
agent = Agent(instructions="Your are a helpful AI assistant")
|
||||
agent.start("Write a movie script about a robot in Mars")
|
||||
```
|
||||
|
||||
Run:
|
||||
@@ -135,6 +79,381 @@ Run:
|
||||
python app.py
|
||||
```
|
||||
|
||||
### 2. Multi Agents
|
||||
|
||||
Create app.py file and add the code below:
|
||||
```python
|
||||
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()
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
## Using No Code
|
||||
|
||||
### Auto Mode:
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --auto create a movie script about Robots in Mars
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## Adding Models
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.praison.ai/models">
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/%F0%9F%93%9A_Models-Visit_docs.praison.ai-blue?style=for-the-badge&logo=bookstack&logoColor=white" alt="Models" />
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Ollama Integration
|
||||
```bash
|
||||
export OPENAI_BASE_URL=http://localhost:11434/v1
|
||||
@@ -147,267 +466,16 @@ export OPENAI_API_KEY=xxxxxxxxxxx
|
||||
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
|
||||
```
|
||||
|
||||
## Logging
|
||||
```bash
|
||||
export LOGLEVEL=info
|
||||
```
|
||||
|
||||
Advanced logging:
|
||||
```bash
|
||||
export LOGLEVEL=debug
|
||||
```
|
||||
|
||||
<div align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/images/architecture-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/images/architecture-light.png" />
|
||||
<img alt="PraisonAI Architecture" src="docs/images/architecture-light.png" />
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
## Different User Interfaces:
|
||||
|
||||
| Interface | Description | URL |
|
||||
|---|---|---|
|
||||
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praison.ai/ui/ui](https://docs.praison.ai/ui/ui) |
|
||||
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
|
||||
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
|
||||
| **Realtime** | Real-time voice interaction with AI | [https://docs.praison.ai/ui/realtime](https://docs.praison.ai/ui/realtime) |
|
||||
|
||||
| Other Features | Description | Docs |
|
||||
|---|---|---|
|
||||
| **Train** | Fine-tune LLMs using your custom data | [https://docs.praison.ai/train](https://docs.praison.ai/train) |
|
||||
|
||||
|
||||
## Google Colab Multi Agents
|
||||
|
||||
| | Cookbook | Open in Colab |
|
||||
| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Basic | PraisonAI | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a> |
|
||||
| Include Tools | PraisonAI Tools | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-tools-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a> |
|
||||
|
||||
## Installation Options
|
||||
|
||||
### Using pip
|
||||
```bash
|
||||
pip install praisonai
|
||||
```
|
||||
|
||||
### Framework-specific Installation
|
||||
```bash
|
||||
# Install with CrewAI support
|
||||
pip install "praisonai[crewai]"
|
||||
|
||||
# Install with AutoGen support
|
||||
pip install "praisonai[autogen]"
|
||||
|
||||
# Install with both frameworks
|
||||
pip install "praisonai[crewai,autogen]"
|
||||
```
|
||||
|
||||
### UI and Additional Features
|
||||
```bash
|
||||
# Install UI support
|
||||
pip install "praisonai[ui]"
|
||||
|
||||
# Install Chat interface
|
||||
pip install "praisonai[chat]"
|
||||
|
||||
# Install Code interface
|
||||
pip install "praisonai[code]"
|
||||
|
||||
# Install Realtime voice interaction
|
||||
pip install "praisonai[realtime]"
|
||||
|
||||
# Install Call feature
|
||||
pip install "praisonai[call]"
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Set your OpenAI API key
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
|
||||
# Initialize with CrewAI (default)
|
||||
praisonai --init "create a movie script about dog in moon"
|
||||
|
||||
# Or initialize with AutoGen
|
||||
praisonai --framework autogen --init "create a movie script about dog in moon"
|
||||
|
||||
# Run the agents
|
||||
praisonai
|
||||
```
|
||||
|
||||
## Full Automatic Mode
|
||||
|
||||
```bash
|
||||
# With CrewAI (default)
|
||||
praisonai --auto "create a movie script about Dog in Moon"
|
||||
|
||||
# With AutoGen
|
||||
praisonai --framework autogen --auto "create a movie script about Dog in Moon"
|
||||
```
|
||||
|
||||
## Framework-specific Features
|
||||
|
||||
### CrewAI
|
||||
When installing with `pip install "praisonai[crewai]"`, you get:
|
||||
- CrewAI framework support
|
||||
- PraisonAI tools integration
|
||||
- Task delegation capabilities
|
||||
- Sequential and parallel task execution
|
||||
|
||||
### AutoGen
|
||||
When installing with `pip install "praisonai[autogen]"`, you get:
|
||||
- AutoGen framework support
|
||||
- PraisonAI tools integration
|
||||
- Multi-agent conversation capabilities
|
||||
- Code execution environment
|
||||
|
||||
|
||||
|
||||
## TL;DR Multi Agents
|
||||
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
praisonai --init create a movie script about dog in moon
|
||||
praisonai
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Initialise](#initialise)
|
||||
- [Run](#run)
|
||||
- [Full Automatic Mode](#full-automatic-mode)
|
||||
- [User Interface](#user-interface)
|
||||
- [Praison AI Chat](#praison-ai-chat)
|
||||
- [Create Custom Tools](#create-custom-tools)
|
||||
- [Agents Playbook](#agents-playbook)
|
||||
- [Include praisonai package in your project](#include-praisonai-package-in-your-project)
|
||||
- [Commands to Install Dev Dependencies](#commands-to-install-dependencies)
|
||||
- [Other Models](#other-models)
|
||||
- [Contributing](#contributing)
|
||||
- [Star History](#star-history)
|
||||
|
||||
## Installation Multi Agents
|
||||
|
||||
```bash
|
||||
pip install praisonai
|
||||
```
|
||||
|
||||
## Initialise
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
```
|
||||
|
||||
Generate your OPENAI API KEY from here: https://platform.openai.com/api-keys
|
||||
|
||||
Note: You can use other providers such as Ollama, Mistral ... etc. Details are provided at the bottom.
|
||||
|
||||
```bash
|
||||
praisonai --init create a movie script about dog in moon
|
||||
```
|
||||
|
||||
This will automatically create agents.yaml file in the current directory.
|
||||
|
||||
### To initialise with a specific agent framework (Optional):
|
||||
|
||||
```bash
|
||||
praisonai --framework autogen --init create movie script about cat in mars
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
praisonai
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
python -m praisonai
|
||||
```
|
||||
|
||||
### Specify the agent framework (Optional):
|
||||
|
||||
```bash
|
||||
praisonai --framework autogen
|
||||
```
|
||||
|
||||
### Full Automatic Mode
|
||||
|
||||
```bash
|
||||
praisonai --auto create a movie script about Dog in Moon
|
||||
```
|
||||
|
||||
## User Interface
|
||||
|
||||
## PraisonAI User Interfaces:
|
||||
|
||||
| Interface | Description | URL |
|
||||
| --------- | ------------------------------------------ | --------------------------------------------------------------------- |
|
||||
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praisonai.com/ui/ui](https://docs.praison.ai/ui/ui) |
|
||||
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praisonai.com/ui/chat](https://docs.praison.ai/ui/chat) |
|
||||
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praisonai.com/ui/code](https://docs.praison.ai/ui/code) |
|
||||
|
||||
```bash
|
||||
pip install -U "praisonai[ui]"
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
chainlit create-secret
|
||||
export CHAINLIT_AUTH_SECRET=xxxxxxxx
|
||||
praisonai ui
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
python -m praisonai ui
|
||||
```
|
||||
|
||||
## Praison AI Chat
|
||||
|
||||
- https://docs.praison.ai/chat/
|
||||
|
||||
```bash
|
||||
pip install "praisonai[chat]"
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
praisonai chat
|
||||
```
|
||||
|
||||
### Internet Search
|
||||
|
||||
Praison AI Chat and Praison AI Code now includes internet search capabilities using Crawl4AI and Tavily, allowing you to retrieve up-to-date information during your conversations.
|
||||
|
||||
### Vision Language Model Support
|
||||
|
||||
You can now upload images and ask questions based on them using Vision Language Models. This feature enables visual understanding and analysis within your chat sessions.
|
||||
|
||||
## Praison AI Code
|
||||
|
||||
```bash
|
||||
pip install "praisonai[code]"
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
praisonai code
|
||||
```
|
||||
|
||||
### Internet Search
|
||||
|
||||
Praison AI Code also includes internet search functionality, enabling you to find relevant code snippets and programming information online.
|
||||
|
||||
## Create Custom Tools
|
||||
|
||||
- https://docs.praison.ai/tools/custom/
|
||||
## No Code Options
|
||||
|
||||
## Agents Playbook
|
||||
|
||||
### Simple Playbook Example
|
||||
|
||||
Create `agents.yaml` file and add the code below:
|
||||
|
||||
```yaml
|
||||
framework: crewai
|
||||
framework: praisonai
|
||||
topic: Artificial Intelligence
|
||||
roles:
|
||||
screenwriter:
|
||||
@@ -420,62 +488,25 @@ roles:
|
||||
expected_output: "Complete script ready for production."
|
||||
```
|
||||
|
||||
*To run the playbook:*
|
||||
```bash
|
||||
praisonai agents.yaml
|
||||
```
|
||||
|
||||
## Use 100+ Models
|
||||
|
||||
- https://docs.praison.ai/models/
|
||||
<div align="center">
|
||||
<a href="https://docs.praison.ai">
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/📚_Documentation-Visit_docs.praison.ai-blue?style=for-the-badge&logo=bookstack&logoColor=white" alt="Documentation" />
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Include praisonai package in your project
|
||||
## Development:
|
||||
|
||||
- https://docs.praison.ai/developers/wrapper
|
||||
- https://docs.praison.ai/developers/wrapper-tools/
|
||||
|
||||
## Option 1: Using RAW YAML
|
||||
|
||||
```python
|
||||
from praisonai import PraisonAI
|
||||
|
||||
# Example agent_yaml content
|
||||
agent_yaml = """
|
||||
framework: "crewai"
|
||||
topic: "Space Exploration"
|
||||
|
||||
roles:
|
||||
astronomer:
|
||||
role: "Space Researcher"
|
||||
goal: "Discover new insights about {topic}"
|
||||
backstory: "You are a curious and dedicated astronomer with a passion for unraveling the mysteries of the cosmos."
|
||||
tasks:
|
||||
investigate_exoplanets:
|
||||
description: "Research and compile information about exoplanets discovered in the last decade."
|
||||
expected_output: "A summarized report on exoplanet discoveries, including their size, potential habitability, and distance from Earth."
|
||||
"""
|
||||
|
||||
# Create a PraisonAI instance with the agent_yaml content
|
||||
praisonai = PraisonAI(agent_yaml=agent_yaml)
|
||||
|
||||
# Run PraisonAI
|
||||
result = praisonai.run()
|
||||
|
||||
# Print the result
|
||||
print(result)
|
||||
```
|
||||
|
||||
## Option 2: Using separate agents.yaml file
|
||||
|
||||
Note: Please create agents.yaml file before hand.
|
||||
|
||||
```python
|
||||
from praisonai import PraisonAI
|
||||
|
||||
def basic(): # Basic Mode
|
||||
praisonai = PraisonAI(agent_file="agents.yaml")
|
||||
praisonai.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
basic()
|
||||
```
|
||||
|
||||
## Commands to Install Dependencies:
|
||||
Below is used for development only.
|
||||
|
||||
### Using uv
|
||||
```bash
|
||||
@@ -500,18 +531,33 @@ uv pip install -r pyproject.toml --extra "crewai,autogen"
|
||||
- Submit a pull request via GitHub's web interface.
|
||||
- Await feedback from project maintainers.
|
||||
|
||||
## Other Features
|
||||
|
||||
- 🔄 Use CrewAI or AutoGen Framework
|
||||
- 💻 Chat with ENTIRE Codebase
|
||||
- 🎨 Interactive UIs
|
||||
- 📄 YAML-based Configuration
|
||||
- 🛠️ Custom Tool Integration
|
||||
- 🔍 Internet Search Capability (using Crawl4AI and Tavily)
|
||||
- 🖼️ Vision Language Model (VLM) Support
|
||||
- 🎙️ Real-time Voice Interaction
|
||||
|
||||
## Star History
|
||||
|
||||
[](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) |
|
||||
@@ -525,58 +571,3 @@ Praison AI is an open-sourced software licensed under the **[MIT license](https:
|
||||
| Realtime Voice Interface | [](https://www.youtube.com/watch?v=frRHfevTCSw) |
|
||||
| Call Interface | [](https://www.youtube.com/watch?v=m1cwrUG2iAk) |
|
||||
|
||||
## License
|
||||
|
||||
Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
## Local Docker Development with Live Reload
|
||||
|
||||
To facilitate local development with live reload, you can use Docker. Follow the steps below:
|
||||
|
||||
1. **Create a `Dockerfile.dev`**:
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install flask praisonai==2.0.18 watchdog
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
ENV FLASK_ENV=development
|
||||
|
||||
CMD ["flask", "run", "--host=0.0.0.0"]
|
||||
```
|
||||
|
||||
2. **Create a `docker-compose.yml`**:
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "5555:5555"
|
||||
environment:
|
||||
FLASK_ENV: development
|
||||
command: flask run --host=0.0.0.0
|
||||
|
||||
watch:
|
||||
image: alpine:latest
|
||||
volumes:
|
||||
- .:/app
|
||||
command: sh -c "apk add --no-cache inotify-tools && while inotifywait -r -e modify,create,delete /app; do kill -HUP 1; done"
|
||||
```
|
||||
|
||||
3. **Run Docker Compose**:
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This setup will allow you to develop locally with live reload, making it easier to test and iterate on your code.
|
||||
@@ -1,6 +1,7 @@
|
||||
framework: praisonai
|
||||
process: sequential
|
||||
topic: create movie script about cat in mars
|
||||
memory: false
|
||||
roles:
|
||||
researcher:
|
||||
backstory: Skilled in research, with a focus on gathering accurate and relevant
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
1. Make minimal code changes as possible
|
||||
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
|
||||
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
|
||||
4. Always Follow the principle LESS IS MORE
|
||||
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.
|
||||
|
||||
|
||||
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,43 @@
|
||||
FROM mcr.microsoft.com/devcontainers/python:3.11
|
||||
|
||||
# Prevent apt from showing prompts
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
curl \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user
|
||||
ARG USERNAME=vscode
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
# Create the user and group only if they don't exist
|
||||
RUN if ! getent group $USER_GID > /dev/null 2>&1; then \
|
||||
groupadd --gid $USER_GID $USERNAME; \
|
||||
fi && \
|
||||
if ! getent passwd $USER_UID > /dev/null 2>&1; then \
|
||||
useradd --uid $USER_UID --gid $USER_GID -m $USERNAME; \
|
||||
fi && \
|
||||
apt-get update && \
|
||||
apt-get install -y sudo && \
|
||||
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME && \
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt /tmp/pip-tmp/
|
||||
RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
|
||||
&& rm -rf /tmp/pip-tmp
|
||||
|
||||
# Switch to non-root user
|
||||
USER $USERNAME
|
||||
|
||||
# Set the default shell to bash
|
||||
ENV SHELL=/bin/bash
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "PraisonAI Development",
|
||||
"dockerFile": "Dockerfile",
|
||||
"context": "..",
|
||||
"runArgs": [
|
||||
"--env-file",
|
||||
".env",
|
||||
"--network=host"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.black-formatter",
|
||||
"ms-toolsai.jupyter",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
],
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "/usr/local/bin/python",
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [88],
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteUser": "vscode",
|
||||
"updateRemoteUserUID": false,
|
||||
"forwardPorts": [],
|
||||
"appPort": ["56040:56040"],
|
||||
"overrideCommand": false,
|
||||
"init": true
|
||||
}
|
||||
+2
-1
@@ -83,4 +83,5 @@ dist
|
||||
build
|
||||
*.mp4
|
||||
*.png
|
||||
graph.py
|
||||
graph.py
|
||||
chroma_db/
|
||||
Whitespace-only changes.
@@ -0,0 +1,32 @@
|
||||
.cursorrules
|
||||
.gitignore
|
||||
.praisoncontext
|
||||
.praisonignore
|
||||
.windsurfrules
|
||||
async_example.py
|
||||
async_example_full.py
|
||||
async_example_full_multigroups.py
|
||||
auto_agents_example.py
|
||||
code_agents_example.py
|
||||
example_callback.py
|
||||
example_custom_tools.py
|
||||
example_sequential.py
|
||||
image.jpg
|
||||
langchain_example.py
|
||||
langchain_logs.py
|
||||
long_term.db
|
||||
mini_agents_example.py
|
||||
multimodal.py
|
||||
praisonaiagents/tools/*
|
||||
praisonaiagents/process/*
|
||||
pyproject.toml
|
||||
short_term.db
|
||||
structured_agents_example.py
|
||||
structured_response_example.py
|
||||
test.py
|
||||
tools_example.py
|
||||
uv.lock
|
||||
workflow_example_basic.py
|
||||
workflow_example_detailed.py
|
||||
__pycache__
|
||||
__pycache__/*
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Make minimal code changes as possible
|
||||
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
|
||||
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
|
||||
4. Always Follow the principle LESS IS MORE
|
||||
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.
|
||||
@@ -0,0 +1,18 @@
|
||||
import asyncio
|
||||
from praisonaiagents import Agent
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gemini/gemini-1.5-flash-8b",
|
||||
self_reflect=True,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Use achat instead of start/chat for async operation
|
||||
response = await agent.achat("Why sky is Blue in 1000 words?")
|
||||
print(response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,127 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from pydantic import BaseModel
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from tavily import AsyncTavilyClient
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
llm_config = {
|
||||
"model": "gemini/gemini-1.5-flash-8b",
|
||||
"response_format": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
|
||||
# Define output model
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, Any]]
|
||||
total_results: int
|
||||
|
||||
# Define Tavily search tool
|
||||
async def tavily_search(query) -> Dict[str, Any]:
|
||||
"""Async Tavily search tool that returns structured results"""
|
||||
client = AsyncTavilyClient()
|
||||
try:
|
||||
results = await client.search(query)
|
||||
print(f"Tavily search results: {results}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": results.get("results", []),
|
||||
"total_results": len(results.get("results", []))
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"Tavily search error: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
if output.output_format == "JSON":
|
||||
logging.info(f"Search completed: {output.json_dict.get('query', '')}")
|
||||
elif output.output_format == "Pydantic":
|
||||
logging.info(f"Search completed: {output.pydantic}")
|
||||
|
||||
async def run_parallel_searches():
|
||||
# Create search agent with direct tool function
|
||||
search_agent = Agent(
|
||||
name="TavilySearchAgent",
|
||||
role="Search Specialist",
|
||||
goal="Perform parallel web searches with high accuracy",
|
||||
backstory="Expert in web search and information retrieval",
|
||||
tools=[tavily_search],
|
||||
llm=llm_config
|
||||
)
|
||||
|
||||
# Create summary agent
|
||||
summary_agent = Agent(
|
||||
name="ContentSynthesizer",
|
||||
role="Research Analyst",
|
||||
goal="Synthesize search results into coherent insights",
|
||||
backstory="Expert in analyzing and combining multiple sources of information",
|
||||
llm=llm_config
|
||||
)
|
||||
|
||||
# Define search topics
|
||||
search_topics = [
|
||||
"Latest AI breakthroughs 2024",
|
||||
"Future of quantum computing",
|
||||
"Advancements in robotics"
|
||||
]
|
||||
|
||||
# Create parallel search tasks
|
||||
search_tasks = [
|
||||
Task(
|
||||
name=f"search_{i}",
|
||||
description=f"Search and analyze: {topic}",
|
||||
expected_output="Comprehensive search results with analysis",
|
||||
agent=search_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summary task
|
||||
summary_task = Task(
|
||||
name="synthesize_results",
|
||||
description="Create a comprehensive summary of all search findings",
|
||||
expected_output="Synthesized insights from all searches",
|
||||
agent=summary_agent,
|
||||
async_execution=False,
|
||||
context=search_tasks
|
||||
)
|
||||
|
||||
# Initialize agents manager
|
||||
agents = PraisonAIAgents(
|
||||
agents=[search_agent, summary_agent],
|
||||
tasks=search_tasks + [summary_task]
|
||||
)
|
||||
|
||||
# Execute tasks
|
||||
results = await agents.astart()
|
||||
return {
|
||||
"search_results": [str(results["task_results"][i]) for i in range(len(search_tasks))],
|
||||
"summary": str(results["task_results"][summary_task.id]),
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
async def main():
|
||||
try:
|
||||
logging.info("Starting parallel Tavily searches...")
|
||||
results = await run_parallel_searches()
|
||||
logging.info("Search process completed successfully")
|
||||
print("\nResults Summary:")
|
||||
for topic, result in zip(results["topics"], results["search_results"]):
|
||||
print(f"\n{topic}:\n{result}")
|
||||
print(f"\nFinal Summary:\n{results['summary']}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,119 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from pydantic import BaseModel
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from tavily import AsyncTavilyClient
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Define output model
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, Any]]
|
||||
total_results: int
|
||||
|
||||
# Define Tavily search tool
|
||||
async def tavily_search(query: str) -> Dict[str, Any]:
|
||||
"""Async Tavily search tool that returns structured results"""
|
||||
client = AsyncTavilyClient()
|
||||
try:
|
||||
results = await client.search(query)
|
||||
print(f"Tavily search results: {results}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": results.get("results", []),
|
||||
"total_results": len(results.get("results", []))
|
||||
}
|
||||
except Exception as e:
|
||||
logging.error(f"Tavily search error: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
if output.output_format == "JSON":
|
||||
logging.info(f"Search completed: {output.json_dict.get('query', '')}")
|
||||
elif output.output_format == "Pydantic":
|
||||
logging.info(f"Search completed: {output.pydantic}")
|
||||
|
||||
async def run_parallel_searches():
|
||||
# Create search agent with direct tool function
|
||||
search_agent = Agent(
|
||||
name="TavilySearchAgent",
|
||||
role="Search Specialist",
|
||||
goal="Perform parallel web searches with high accuracy",
|
||||
backstory="Expert in web search and information retrieval",
|
||||
tools=[tavily_search]
|
||||
)
|
||||
|
||||
# Create summary agent
|
||||
summary_agent = Agent(
|
||||
name="ContentSynthesizer",
|
||||
role="Research Analyst",
|
||||
goal="Synthesize search results into coherent insights",
|
||||
backstory="Expert in analyzing and combining multiple sources of information"
|
||||
)
|
||||
|
||||
# Define search topics
|
||||
search_topics = [
|
||||
"Latest AI breakthroughs 2024",
|
||||
"Future of quantum computing",
|
||||
"Advancements in robotics"
|
||||
]
|
||||
|
||||
# Create parallel search tasks
|
||||
search_tasks = [
|
||||
Task(
|
||||
name=f"search_{i}",
|
||||
description=f"Search and analyze: {topic}",
|
||||
expected_output="Comprehensive search results with analysis",
|
||||
agent=search_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summary task
|
||||
summary_task = Task(
|
||||
name="synthesize_results",
|
||||
description="Create a comprehensive summary of all search findings",
|
||||
expected_output="Synthesized insights from all searches",
|
||||
agent=summary_agent,
|
||||
async_execution=False,
|
||||
context=search_tasks
|
||||
)
|
||||
|
||||
# Initialize agents manager
|
||||
agents = PraisonAIAgents(
|
||||
agents=[search_agent, summary_agent],
|
||||
tasks=search_tasks + [summary_task]
|
||||
)
|
||||
|
||||
# Execute tasks
|
||||
results = await agents.astart()
|
||||
return {
|
||||
"search_results": [str(results["task_results"][i]) for i in range(len(search_tasks))],
|
||||
"summary": str(results["task_results"][summary_task.id]),
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
async def main():
|
||||
try:
|
||||
logging.info("Starting parallel Tavily searches...")
|
||||
results = await run_parallel_searches()
|
||||
logging.info("Search process completed successfully")
|
||||
print("\nResults Summary:")
|
||||
for topic, result in zip(results["topics"], results["search_results"]):
|
||||
print(f"\n{topic}:\n{result}")
|
||||
print(f"\nFinal Summary:\n{results['summary']}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+24
-3
@@ -12,6 +12,14 @@ from praisonaiagents.main import (
|
||||
)
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
import logging
|
||||
|
||||
# Configure logging with more detail
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
console = Console()
|
||||
|
||||
@@ -48,7 +56,7 @@ def sync_search_tool(query: str) -> List[Dict]:
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
|
||||
async def async_search_tool(query: str) -> List[Dict]:
|
||||
async def async_search_tool(query: str) -> Dict:
|
||||
"""
|
||||
Asynchronous search using DuckDuckGo.
|
||||
Args:
|
||||
@@ -56,23 +64,36 @@ async def async_search_tool(query: str) -> List[Dict]:
|
||||
Returns:
|
||||
list: Search results
|
||||
"""
|
||||
logger.debug(f"Starting async_search_tool with query: {query}")
|
||||
display_tool_call(f"Running async search for: {query}", console)
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
logger.debug("Performing DuckDuckGo search")
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
logger.debug(f"Search completed, found {len(results)} results")
|
||||
return {
|
||||
"query": query,
|
||||
"results": results,
|
||||
"total_results": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error during async search: {e}"
|
||||
logger.error(f"Error in async_search_tool: {e}", exc_info=True)
|
||||
display_error(error_msg, console)
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# 3. Define both sync and async callbacks
|
||||
def sync_callback(output: TaskOutput):
|
||||
|
||||
@@ -4,6 +4,13 @@ from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
from praisonaiagents import AutoAgents
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agents = AutoAgents(
|
||||
instructions="Search for information about AI Agents",
|
||||
tools=[duckduckgo],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,114 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_environment_state():
|
||||
"""Simulates getting current environment state"""
|
||||
current_time = int(time.time())
|
||||
states = ["normal", "critical", "optimal"]
|
||||
state = states[current_time % 3]
|
||||
print(f"Environment state: {state}")
|
||||
return state
|
||||
|
||||
def perform_action(state: str):
|
||||
"""Simulates performing an action based on state"""
|
||||
actions = {
|
||||
"normal": "maintain",
|
||||
"critical": "fix",
|
||||
"optimal": "enhance"
|
||||
}
|
||||
action = actions.get(state, "observe")
|
||||
print(f"Performing action: {action} for state: {state}")
|
||||
return action
|
||||
|
||||
def get_feedback():
|
||||
"""Simulates environment feedback"""
|
||||
current_time = int(time.time())
|
||||
feedback = "positive" if current_time % 2 == 0 else "negative"
|
||||
print(f"Feedback received: {feedback}")
|
||||
return feedback
|
||||
|
||||
# Create specialized agents
|
||||
llm_caller = Agent(
|
||||
name="Environment Monitor",
|
||||
role="State analyzer",
|
||||
goal="Monitor environment and analyze state",
|
||||
instructions="Check environment state and provide analysis",
|
||||
tools=[get_environment_state]
|
||||
)
|
||||
|
||||
action_agent = Agent(
|
||||
name="Action Executor",
|
||||
role="Action performer",
|
||||
goal="Execute appropriate actions based on state",
|
||||
instructions="Determine and perform actions based on environment state",
|
||||
tools=[perform_action]
|
||||
)
|
||||
|
||||
feedback_agent = Agent(
|
||||
name="Feedback Processor",
|
||||
role="Feedback analyzer",
|
||||
goal="Process environment feedback and adapt strategy",
|
||||
instructions="Analyze feedback and provide adaptation recommendations",
|
||||
tools=[get_feedback]
|
||||
)
|
||||
|
||||
# Create tasks for autonomous workflow
|
||||
monitor_task = Task(
|
||||
name="monitor_environment",
|
||||
description="Monitor and analyze environment state",
|
||||
expected_output="Current environment state analysis",
|
||||
agent=llm_caller,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["execute_action"],
|
||||
condition={
|
||||
"normal": ["execute_action"],
|
||||
"critical": ["execute_action"],
|
||||
"optimal": "exit"
|
||||
}
|
||||
)
|
||||
|
||||
action_task = Task(
|
||||
name="execute_action",
|
||||
description="Execute appropriate action based on state",
|
||||
expected_output="Action execution result",
|
||||
agent=action_agent,
|
||||
next_tasks=["process_feedback"]
|
||||
)
|
||||
|
||||
feedback_task = Task(
|
||||
name="process_feedback",
|
||||
description="Process feedback and adapt strategy",
|
||||
expected_output="Strategy adaptation based on feedback",
|
||||
agent=feedback_agent,
|
||||
next_tasks=["monitor_environment"], # Create feedback loop
|
||||
context=[monitor_task, action_task] # Access to previous states and actions
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[llm_caller, action_agent, feedback_agent],
|
||||
tasks=[monitor_task, action_task, feedback_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
def main():
|
||||
print("\nStarting Autonomous Agent Workflow...")
|
||||
print("=" * 50)
|
||||
|
||||
# Run autonomous workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nAutonomous Agent Results:")
|
||||
print("=" * 50)
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
task_name = result.description
|
||||
print(f"\nTask: {task_name}")
|
||||
print(f"Result: {result.raw}")
|
||||
print("-" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,8 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
agent.start("Why sky is Blue?")
|
||||
@@ -0,0 +1,14 @@
|
||||
from praisonaiagents.knowledge import Chunking
|
||||
|
||||
# No heavy imports or initialization happens here
|
||||
chunking = Chunking(chunker_type='token')
|
||||
|
||||
# Chunker is only initialized when first used
|
||||
chunks = chunking.chunk("Your text here")
|
||||
|
||||
# Embedding model is only loaded if using semantic chunking
|
||||
semantic_chunking = Chunking(chunker_type='recursive')
|
||||
# Embedding model and chunker are loaded here on first use
|
||||
semantic_chunks = semantic_chunking.chunk("Your text here")
|
||||
|
||||
print(semantic_chunks)
|
||||
@@ -0,0 +1,97 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
from gitingest import ingest
|
||||
|
||||
class CodeMetrics(BaseModel):
|
||||
category: str
|
||||
score: int
|
||||
findings: List[str]
|
||||
|
||||
class CodeAnalysisReport(BaseModel):
|
||||
overall_quality: int
|
||||
code_metrics: List[CodeMetrics]
|
||||
architecture_score: int
|
||||
maintainability_score: int
|
||||
performance_score: int
|
||||
security_score: int
|
||||
test_coverage: int
|
||||
key_strengths: List[str]
|
||||
improvement_areas: List[str]
|
||||
tech_stack: List[str]
|
||||
recommendations: List[str]
|
||||
complexity_metrics: Dict[str, int]
|
||||
best_practices: List[Dict[str, str]]
|
||||
potential_risks: List[str]
|
||||
documentation_quality: int
|
||||
|
||||
code_analyzer = Agent(
|
||||
role="Code Analysis Expert",
|
||||
goal="Provide comprehensive code evaluation and recommendations",
|
||||
backstory="""Expert code analyst specializing in architecture review,
|
||||
best practices, and technical debt assessment.""",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
code_analysis_task = Task(
|
||||
description="""Analyze code repository and provide structured evaluation:
|
||||
|
||||
1. Overall Quality (0-100)
|
||||
2. Core Metrics Analysis:
|
||||
- Architecture and Design
|
||||
- Code Maintainability
|
||||
- Performance Optimization
|
||||
- Security Practices
|
||||
- Test Coverage
|
||||
3. Technical Assessment:
|
||||
- Technology Stack Review
|
||||
- Code Complexity Analysis
|
||||
- Best Practices Adherence
|
||||
- Risk Assessment
|
||||
4. Recommendations:
|
||||
- Key Improvements
|
||||
- Architecture Suggestions
|
||||
- Security Enhancements""",
|
||||
expected_output="Detailed code analysis report with metrics and recommendations",
|
||||
agent=code_analyzer,
|
||||
output_pydantic=CodeAnalysisReport
|
||||
)
|
||||
|
||||
def analyze_code(code_source: str) -> CodeAnalysisReport:
|
||||
"""
|
||||
Analyze code from directory path or GitHub URL
|
||||
"""
|
||||
# Ingest code content
|
||||
summary, tree, content = ingest(code_source)
|
||||
|
||||
# Concatenate context into structured format
|
||||
context_text = f"""
|
||||
CODE REPOSITORY ANALYSIS
|
||||
=======================
|
||||
|
||||
SUMMARY
|
||||
-------
|
||||
{summary}
|
||||
|
||||
REPOSITORY STRUCTURE
|
||||
-------------------
|
||||
{tree}
|
||||
|
||||
SOURCE CODE
|
||||
-----------
|
||||
{content}
|
||||
"""
|
||||
|
||||
# Initialize and run analysis
|
||||
agents = PraisonAIAgents(
|
||||
agents=[code_analyzer],
|
||||
tasks=[code_analysis_task]
|
||||
)
|
||||
|
||||
return agents.start(context_text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
code_source = "https://github.com/openai/openai-python/tree/main/src/openai/cli/_api/chat" # GitHub URL or local directory
|
||||
result = analyze_code(code_source)
|
||||
print(result)
|
||||
@@ -0,0 +1,217 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict, Any
|
||||
from gitingest import ingest
|
||||
|
||||
# Import the same classes and code from code-analysis-agents.py
|
||||
class CodeMetrics(BaseModel):
|
||||
category: str
|
||||
score: int
|
||||
findings: List[str]
|
||||
|
||||
class CodeAnalysisReport(BaseModel):
|
||||
overall_quality: int
|
||||
code_metrics: List[CodeMetrics]
|
||||
architecture_score: int
|
||||
maintainability_score: int
|
||||
performance_score: int
|
||||
security_score: int
|
||||
test_coverage: int
|
||||
key_strengths: List[str]
|
||||
improvement_areas: List[str]
|
||||
tech_stack: List[str]
|
||||
recommendations: List[str]
|
||||
complexity_metrics: Dict[str, int]
|
||||
best_practices: List[Dict[str, str]]
|
||||
potential_risks: List[str]
|
||||
documentation_quality: int
|
||||
|
||||
def analyze_code(code_source: str) -> CodeAnalysisReport:
|
||||
"""
|
||||
Analyze code from directory path or GitHub URL
|
||||
"""
|
||||
# Create code analyzer agent
|
||||
code_analyzer = Agent(
|
||||
role="Code Analysis Expert",
|
||||
goal="Provide comprehensive code evaluation and recommendations",
|
||||
backstory="""Expert code analyst specializing in architecture review,
|
||||
best practices, and technical debt assessment.""",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Create analysis task
|
||||
code_analysis_task = Task(
|
||||
description="""Analyze code repository and provide structured evaluation:
|
||||
|
||||
1. Overall Quality (0-100)
|
||||
2. Core Metrics Analysis:
|
||||
- Architecture and Design
|
||||
- Code Maintainability
|
||||
- Performance Optimization
|
||||
- Security Practices
|
||||
- Test Coverage
|
||||
3. Technical Assessment:
|
||||
- Technology Stack Review
|
||||
- Code Complexity Analysis
|
||||
- Best Practices Adherence
|
||||
- Risk Assessment
|
||||
4. Recommendations:
|
||||
- Key Improvements
|
||||
- Architecture Suggestions
|
||||
- Security Enhancements""",
|
||||
expected_output="Detailed code analysis report with metrics and recommendations",
|
||||
agent=code_analyzer,
|
||||
output_pydantic=CodeAnalysisReport
|
||||
)
|
||||
|
||||
# Ingest code content
|
||||
summary, tree, content = ingest(code_source)
|
||||
|
||||
# Concatenate context into structured format
|
||||
context_text = f"""
|
||||
CODE REPOSITORY ANALYSIS
|
||||
=======================
|
||||
|
||||
SUMMARY
|
||||
-------
|
||||
{summary}
|
||||
|
||||
REPOSITORY STRUCTURE
|
||||
-------------------
|
||||
{tree}
|
||||
|
||||
SOURCE CODE
|
||||
-----------
|
||||
{content}
|
||||
"""
|
||||
|
||||
# Initialize and run analysis
|
||||
agents = PraisonAIAgents(
|
||||
agents=[code_analyzer],
|
||||
tasks=[code_analysis_task]
|
||||
)
|
||||
|
||||
result = agents.start(context_text)
|
||||
|
||||
# Extract the Pydantic model from the result
|
||||
if isinstance(result, dict) and 'task_results' in result:
|
||||
# Get the first task result's pydantic output
|
||||
analysis_result = result['task_results'][0].pydantic
|
||||
if isinstance(analysis_result, CodeAnalysisReport):
|
||||
return analysis_result
|
||||
|
||||
# If we can't get the Pydantic model, create one from the raw data
|
||||
return CodeAnalysisReport(**result)
|
||||
|
||||
def display_code_metrics(metrics, cols):
|
||||
"""Display code metrics in columns with color-coded scores"""
|
||||
for i, metric in enumerate(metrics):
|
||||
score = metric.score
|
||||
color = "red" if score < 60 else "orange" if score < 75 else "green"
|
||||
|
||||
# Use modulo to alternate between columns
|
||||
with cols[i % len(cols)]:
|
||||
st.markdown(f"### {metric.category}")
|
||||
st.markdown(f"**Score:** :{color}[{score}%]")
|
||||
for finding in metric.findings:
|
||||
st.markdown(f"• {finding}")
|
||||
|
||||
def display_section(title: str, items: list, icon: str = "•"):
|
||||
"""Display a section with items in a consistent format"""
|
||||
st.markdown(f"### {title}")
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
for key, value in item.items():
|
||||
st.markdown(f"{icon} **{key}**: {value}")
|
||||
else:
|
||||
st.markdown(f"{icon} {item}")
|
||||
|
||||
def main():
|
||||
st.set_page_config(
|
||||
page_title="Code Analysis Agent",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
st.title("Code Analysis Agent")
|
||||
|
||||
with st.sidebar:
|
||||
st.header("Input")
|
||||
code_source = st.text_input(
|
||||
"GitHub URL or Local Path",
|
||||
placeholder="https://github.com/username/repo or /path/to/directory"
|
||||
)
|
||||
analyze_button = st.button("Analyze Code", type="primary")
|
||||
|
||||
if analyze_button and code_source:
|
||||
try:
|
||||
with st.spinner("Analyzing code..."):
|
||||
result = analyze_code(code_source)
|
||||
|
||||
# Overall Metrics
|
||||
st.header("Overall Metrics")
|
||||
cols = st.columns(6)
|
||||
metrics = {
|
||||
"Overall Quality": result.overall_quality,
|
||||
"Architecture": result.architecture_score,
|
||||
"Maintainability": result.maintainability_score,
|
||||
"Performance": result.performance_score,
|
||||
"Security": result.security_score,
|
||||
"Test Coverage": result.test_coverage
|
||||
}
|
||||
|
||||
for (metric, value), col in zip(metrics.items(), cols):
|
||||
color = "red" if value < 60 else "orange" if value < 75 else "green"
|
||||
col.metric(metric, f"{value}%")
|
||||
col.markdown(f":{color}[{'●' * (value // 20)}]")
|
||||
|
||||
# Detailed Analysis
|
||||
st.header("Detailed Analysis")
|
||||
metric_cols = st.columns(2)
|
||||
display_code_metrics(result.code_metrics, metric_cols)
|
||||
|
||||
# Technology Stack
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.header("Technology Stack")
|
||||
for tech in result.tech_stack:
|
||||
st.markdown(f"🔧 {tech}")
|
||||
|
||||
with col2:
|
||||
st.header("Complexity Metrics")
|
||||
for metric, value in result.complexity_metrics.items():
|
||||
st.metric(metric.replace('_', ' ').title(), value)
|
||||
|
||||
# Key Findings
|
||||
st.header("Key Findings")
|
||||
cols = st.columns(2)
|
||||
|
||||
with cols[0]:
|
||||
display_section("✅ Strengths", result.key_strengths)
|
||||
display_section("🔄 Best Practices", result.best_practices)
|
||||
|
||||
with cols[1]:
|
||||
display_section("⚠️ Areas for Improvement", result.improvement_areas)
|
||||
display_section("❗ Potential Risks", result.potential_risks)
|
||||
|
||||
# Recommendations
|
||||
st.header("Recommendations")
|
||||
for i, rec in enumerate(result.recommendations, 1):
|
||||
st.markdown(f"**{i}.** {rec}")
|
||||
|
||||
# Documentation Quality
|
||||
st.header("Documentation")
|
||||
doc_score = result.documentation_quality
|
||||
color = "red" if doc_score < 60 else "orange" if doc_score < 75 else "green"
|
||||
st.markdown(f"Documentation Quality: :{color}[{doc_score}%]")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error analyzing code: {str(e)}")
|
||||
st.exception(e)
|
||||
|
||||
elif analyze_button:
|
||||
st.warning("Please enter a GitHub URL or local path")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import json
|
||||
from e2b_code_interpreter import Sandbox
|
||||
|
||||
def code_interpreter(code: str):
|
||||
"""
|
||||
A function to demonstrate running Python code dynamically using e2b_code_interpreter.
|
||||
"""
|
||||
print(f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}")
|
||||
exec_result = Sandbox().run_code(code)
|
||||
if exec_result.error:
|
||||
print("[Code Interpreter error]", exec_result.error)
|
||||
return {"error": str(exec_result.error)}
|
||||
else:
|
||||
results = []
|
||||
for result in exec_result.results:
|
||||
if hasattr(result, '__iter__'):
|
||||
results.extend(list(result))
|
||||
else:
|
||||
results.append(str(result))
|
||||
logs = {"stdout": list(exec_result.logs.stdout), "stderr": list(exec_result.logs.stderr)}
|
||||
return json.dumps({"results": results, "logs": logs})
|
||||
|
||||
code_agent = Agent(
|
||||
name="code_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in writing Python scripts",
|
||||
self_reflect=False
|
||||
)
|
||||
execution_agent = Agent(
|
||||
name="execution_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in executing Python scripts",
|
||||
self_reflect=False,
|
||||
tools=[code_interpreter]
|
||||
)
|
||||
|
||||
code_agent_task = Task(
|
||||
description="Write a simple Python script to print 'Hello, World!'",
|
||||
expected_output="A Python script that prints 'Hello, World!'",
|
||||
agent=code_agent
|
||||
)
|
||||
execution_agent_task = Task(
|
||||
description="Execute the Python script",
|
||||
expected_output="The output of the Python script",
|
||||
agent=execution_agent
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(agents=[code_agent, execution_agent], tasks=[code_agent_task, execution_agent_task])
|
||||
agents.start()
|
||||
@@ -0,0 +1,108 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
from typing import List, Dict
|
||||
|
||||
def analyze_code_changes():
|
||||
"""Simulates code analysis"""
|
||||
issues = [
|
||||
{"type": "style", "severity": "low", "file": "main.py"},
|
||||
{"type": "security", "severity": "high", "file": "auth.py"},
|
||||
{"type": "performance", "severity": "medium", "file": "data.py"}
|
||||
]
|
||||
return issues[int(time.time()) % 3]
|
||||
|
||||
def suggest_fixes(issue: Dict):
|
||||
"""Simulates fix suggestions"""
|
||||
fixes = {
|
||||
"style": "Apply PEP 8 formatting",
|
||||
"security": "Implement input validation",
|
||||
"performance": "Use list comprehension"
|
||||
}
|
||||
return fixes.get(issue["type"], "Review manually")
|
||||
|
||||
def apply_automated_fix(fix: str):
|
||||
"""Simulates applying automated fixes"""
|
||||
success = int(time.time()) % 2 == 0
|
||||
return "fixed" if success else "manual_review"
|
||||
|
||||
# Create specialized agents
|
||||
analyzer = Agent(
|
||||
name="Code Analyzer",
|
||||
role="Code analysis",
|
||||
goal="Analyze code changes and identify issues",
|
||||
instructions="Review code changes and report issues",
|
||||
tools=[analyze_code_changes]
|
||||
)
|
||||
|
||||
fix_suggester = Agent(
|
||||
name="Fix Suggester",
|
||||
role="Solution provider",
|
||||
goal="Suggest fixes for identified issues",
|
||||
instructions="Provide appropriate fix suggestions",
|
||||
tools=[suggest_fixes]
|
||||
)
|
||||
|
||||
fix_applier = Agent(
|
||||
name="Fix Applier",
|
||||
role="Fix implementation",
|
||||
goal="Apply suggested fixes automatically when possible",
|
||||
instructions="Implement suggested fixes and report results",
|
||||
tools=[apply_automated_fix]
|
||||
)
|
||||
|
||||
# Create workflow tasks
|
||||
analysis_task = Task(
|
||||
name="analyze_code",
|
||||
description="Analyze code changes for issues",
|
||||
expected_output="Identified code issues",
|
||||
agent=analyzer,
|
||||
is_start=True,
|
||||
next_tasks=["suggest_fixes"]
|
||||
)
|
||||
|
||||
suggestion_task = Task(
|
||||
name="suggest_fixes",
|
||||
description="Suggest fixes for identified issues",
|
||||
expected_output="Fix suggestions",
|
||||
agent=fix_suggester,
|
||||
next_tasks=["apply_fixes"]
|
||||
)
|
||||
|
||||
fix_task = Task(
|
||||
name="apply_fixes",
|
||||
description="Apply suggested fixes",
|
||||
expected_output="Fix application status",
|
||||
agent=fix_applier,
|
||||
task_type="decision",
|
||||
condition={
|
||||
"fixed": "",
|
||||
"manual_review": ["suggest_fixes"] # Loop back for manual review
|
||||
}
|
||||
)
|
||||
|
||||
# Create workflow
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[analyzer, fix_suggester, fix_applier],
|
||||
tasks=[analysis_task, suggestion_task, fix_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
def main():
|
||||
print("\nStarting Code Review Workflow...")
|
||||
print("=" * 50)
|
||||
|
||||
# Run workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nCode Review Results:")
|
||||
print("=" * 50)
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"\nTask: {task_id}")
|
||||
print(f"Result: {result.raw}")
|
||||
print("-" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,98 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
|
||||
class SkillMetrics(BaseModel):
|
||||
name: str
|
||||
score: int
|
||||
comments: str
|
||||
|
||||
class CVAnalysisReport(BaseModel):
|
||||
overall_score: int
|
||||
skill_metrics: List[SkillMetrics]
|
||||
experience_score: int
|
||||
education_score: int
|
||||
presentation_score: int
|
||||
achievement_impact_score: int
|
||||
market_readiness: int
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
market_fit: str
|
||||
recommendations: List[str]
|
||||
technical_skills: List[Dict[str, int]]
|
||||
soft_skills: List[Dict[str, int]]
|
||||
skill_gaps: List[str]
|
||||
key_achievements: List[str]
|
||||
impact_metrics: List[Dict[str, str]]
|
||||
areas_of_expertise: List[str]
|
||||
|
||||
# Create single CV analysis agent
|
||||
cv_analyzer = Agent(
|
||||
role="CV Analysis Expert",
|
||||
goal="Provide comprehensive CV evaluation and recommendations",
|
||||
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
|
||||
market trends, and talent assessment. You excel at evaluating skills, experience,
|
||||
and providing actionable recommendations.""",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Single comprehensive analysis task
|
||||
cv_analysis_task = Task(
|
||||
description="""Perform a detailed CV analysis with scoring metrics and provide output in the following format:
|
||||
|
||||
1. Overall Score (0-100):
|
||||
- Calculate based on weighted average of all metrics
|
||||
|
||||
2. Individual Skill Metrics:
|
||||
- For each major skill found in CV:
|
||||
* Name of skill
|
||||
* Score (0-100)
|
||||
* Specific feedback/comments
|
||||
|
||||
3. Core Metrics (0-100 each):
|
||||
- Experience Score: Based on relevance and years
|
||||
- Education Score: Based on relevance and level
|
||||
- Presentation Score: CV format, clarity, structure
|
||||
- Achievement Impact Score: Measurable results
|
||||
- Market Readiness: Current market fit
|
||||
|
||||
4. Technical Skills:
|
||||
- List each skill with proficiency score (0-100)
|
||||
- Example: {"Python": 85, "AWS": 70}
|
||||
|
||||
5. Soft Skills:
|
||||
- List each skill with proficiency score (0-100)
|
||||
- Example: {"Communication": 90, "Leadership": 75}
|
||||
|
||||
6. Detailed Analysis:
|
||||
- Key Strengths: Top 3-5 standout points
|
||||
- Areas for Improvement: 3-5 specific points
|
||||
- Market Fit: Current industry alignment
|
||||
- Skill Gaps: Missing critical skills for target role
|
||||
- Key Achievements: Quantifiable accomplishments
|
||||
- Impact Metrics: {"metric": "value"}
|
||||
- Areas of Expertise: Primary domains of expertise
|
||||
|
||||
7. Recommendations:
|
||||
- 3-5 actionable steps for improvement
|
||||
|
||||
Provide clear justification for all scores and ensure feedback is specific and actionable.""",
|
||||
expected_output="Structured CV analysis report with detailed metrics and scoring justification",
|
||||
agent=cv_analyzer,
|
||||
output_pydantic=CVAnalysisReport
|
||||
)
|
||||
|
||||
# Create and run the agent
|
||||
agents = PraisonAIAgents(
|
||||
agents=[cv_analyzer],
|
||||
tasks=[cv_analysis_task]
|
||||
)
|
||||
|
||||
# Read and pass CV content through start()
|
||||
with open('cv.txt', 'r') as file:
|
||||
cv_content = file.read()
|
||||
|
||||
# Start the analysis by passing CV content
|
||||
result = agents.start(cv_content)
|
||||
|
||||
print(result)
|
||||
@@ -0,0 +1,202 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
import json
|
||||
import asyncio
|
||||
import nest_asyncio
|
||||
from functools import partial
|
||||
|
||||
# Enable nested event loops
|
||||
nest_asyncio.apply()
|
||||
|
||||
class SkillMetrics(BaseModel):
|
||||
name: str
|
||||
score: int
|
||||
comments: str
|
||||
|
||||
class CVAnalysisReport(BaseModel):
|
||||
overall_score: int
|
||||
skill_metrics: List[SkillMetrics]
|
||||
experience_score: int
|
||||
education_score: int
|
||||
presentation_score: int
|
||||
achievement_impact_score: int
|
||||
market_readiness: int
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
market_fit: str
|
||||
recommendations: List[str]
|
||||
technical_skills: List[Dict[str, int]]
|
||||
soft_skills: List[Dict[str, int]]
|
||||
skill_gaps: List[str]
|
||||
key_achievements: List[str]
|
||||
impact_metrics: List[Dict[str, str]]
|
||||
areas_of_expertise: List[str]
|
||||
|
||||
async def analyze_cv_async(cv_content):
|
||||
cv_analyzer = Agent(
|
||||
role="CV Analysis Expert",
|
||||
goal="Provide comprehensive CV evaluation and recommendations",
|
||||
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
|
||||
market trends, and talent assessment. You excel at evaluating skills, experience,
|
||||
and providing actionable recommendations.""",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
cv_analysis_task = Task(
|
||||
description="""Perform a detailed CV analysis with scoring metrics and provide output in the following format:
|
||||
1. Overall Score (0-100):
|
||||
- Calculate based on weighted average of all metrics
|
||||
|
||||
2. Individual Skill Metrics:
|
||||
- For each major skill found in CV:
|
||||
* Name of skill
|
||||
* Score (0-100)
|
||||
* Specific feedback/comments
|
||||
|
||||
3. Core Metrics (0-100 each):
|
||||
- Experience Score: Based on relevance and years
|
||||
- Education Score: Based on relevance and level
|
||||
- Presentation Score: CV format, clarity, structure
|
||||
- Achievement Impact Score: Measurable results
|
||||
- Market Readiness: Current market fit
|
||||
|
||||
4. Technical Skills:
|
||||
- List each skill with proficiency score (0-100)
|
||||
|
||||
5. Soft Skills:
|
||||
- List each skill with proficiency score (0-100)
|
||||
|
||||
6. Detailed Analysis sections
|
||||
|
||||
7. Recommendations:
|
||||
- 3-5 actionable steps for improvement""",
|
||||
expected_output="Structured CV analysis report with detailed metrics and scoring justification",
|
||||
agent=cv_analyzer,
|
||||
output_pydantic=CVAnalysisReport
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[cv_analyzer],
|
||||
tasks=[cv_analysis_task]
|
||||
)
|
||||
|
||||
try:
|
||||
result = await agents.astart(cv_content)
|
||||
return result['task_results'][0].pydantic
|
||||
except Exception as e:
|
||||
print(f"Error during analysis: {str(e)}")
|
||||
raise e
|
||||
|
||||
def analyze_cv(cv_content):
|
||||
async def run_analysis():
|
||||
return await analyze_cv_async(cv_content)
|
||||
|
||||
try:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
result = loop.run_until_complete(run_analysis())
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
def main():
|
||||
st.set_page_config(page_title="CV Analyzer", layout="wide")
|
||||
st.title("Professional CV Analyzer")
|
||||
|
||||
# File upload
|
||||
cv_file = st.file_uploader("Upload your CV (TXT format)", type=['txt'])
|
||||
|
||||
if cv_file:
|
||||
cv_content = cv_file.read().decode()
|
||||
|
||||
with st.spinner('Analyzing your CV...'):
|
||||
try:
|
||||
analysis = analyze_cv(cv_content)
|
||||
|
||||
# Display Overall Score
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("Overall Score", f"{analysis.overall_score}/100")
|
||||
with col2:
|
||||
st.metric("Market Readiness", f"{analysis.market_readiness}/100")
|
||||
with col3:
|
||||
st.metric("Experience Score", f"{analysis.experience_score}/100")
|
||||
|
||||
# Core Metrics
|
||||
st.subheader("Core Metrics")
|
||||
metrics_cols = st.columns(4)
|
||||
with metrics_cols[0]:
|
||||
st.metric("Education", f"{analysis.education_score}/100")
|
||||
with metrics_cols[1]:
|
||||
st.metric("Presentation", f"{analysis.presentation_score}/100")
|
||||
with metrics_cols[2]:
|
||||
st.metric("Achievement Impact", f"{analysis.achievement_impact_score}/100")
|
||||
with metrics_cols[3]:
|
||||
st.metric("Market Readiness", f"{analysis.market_readiness}/100")
|
||||
|
||||
# Skill Metrics
|
||||
st.subheader("Detailed Skill Analysis")
|
||||
for skill in analysis.skill_metrics:
|
||||
with st.expander(f"{skill.name} - Score: {skill.score}/100"):
|
||||
st.write(skill.comments)
|
||||
|
||||
# Technical and Soft Skills
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.subheader("Technical Skills")
|
||||
for skill in analysis.technical_skills:
|
||||
for skill_name, score in skill.items():
|
||||
st.progress(score/100)
|
||||
st.caption(f"{skill_name}: {score}/100")
|
||||
|
||||
with col2:
|
||||
st.subheader("Soft Skills")
|
||||
for skill in analysis.soft_skills:
|
||||
for skill_name, score in skill.items():
|
||||
st.progress(score/100)
|
||||
st.caption(f"{skill_name}: {score}/100")
|
||||
|
||||
# Key Information
|
||||
st.subheader("Key Analysis")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.write("🎯 Key Strengths")
|
||||
for strength in analysis.key_strengths:
|
||||
st.write(f"• {strength}")
|
||||
|
||||
st.write("🎮 Areas of Expertise")
|
||||
for expertise in analysis.areas_of_expertise:
|
||||
st.write(f"• {expertise}")
|
||||
|
||||
with col2:
|
||||
st.write("🔄 Areas for Improvement")
|
||||
for area in analysis.areas_for_improvement:
|
||||
st.write(f"• {area}")
|
||||
|
||||
st.write("⚠️ Skill Gaps")
|
||||
for gap in analysis.skill_gaps:
|
||||
st.write(f"• {gap}")
|
||||
|
||||
# Impact Metrics
|
||||
st.subheader("Impact Metrics")
|
||||
for metric in analysis.impact_metrics:
|
||||
for key, value in metric.items():
|
||||
st.metric(key, value)
|
||||
|
||||
# Recommendations
|
||||
st.subheader("📋 Recommendations")
|
||||
for rec in analysis.recommendations:
|
||||
st.write(f"• {rec}")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"An error occurred during analysis: {str(e)}")
|
||||
|
||||
else:
|
||||
st.info("Please upload your CV in TXT format to begin the analysis")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,61 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
|
||||
class CVAnalysisReport(BaseModel):
|
||||
overall_score: int
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
market_fit: str
|
||||
recommendations: List[str]
|
||||
technical_skills: List[str]
|
||||
soft_skills: List[str]
|
||||
skill_gaps: List[str]
|
||||
key_achievements: List[str]
|
||||
impact_metrics: List[Dict[str, str]]
|
||||
areas_of_expertise: List[str]
|
||||
|
||||
# Create single CV analysis agent
|
||||
cv_analyzer = Agent(
|
||||
role="CV Analysis Expert",
|
||||
goal="Provide comprehensive CV evaluation and recommendations",
|
||||
backstory="""You are an expert CV analyst with deep knowledge of industry requirements,
|
||||
market trends, and talent assessment. You excel at evaluating skills, experience,
|
||||
and providing actionable recommendations.""",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Read CV content first
|
||||
with open('cv.txt', 'r') as file:
|
||||
cv_content = file.read()
|
||||
|
||||
cv_key_strengths = Task(
|
||||
description="List the key strengths to look",
|
||||
expected_output="List of key strengths",
|
||||
agent=cv_analyzer,
|
||||
)
|
||||
|
||||
# Single comprehensive analysis task with CV content in context
|
||||
cv_analysis_task = Task(
|
||||
description="""Analyze the CV comprehensively including:
|
||||
1. Technical and soft skills assessment
|
||||
2. Experience and achievements evaluation
|
||||
3. Overall CV evaluation with recommendations
|
||||
Provide detailed insights and actionable recommendations.""",
|
||||
expected_output="Complete CV analysis report",
|
||||
agent=cv_analyzer,
|
||||
output_pydantic=CVAnalysisReport,
|
||||
context=[cv_content, cv_key_strengths] # Pass CV content directly in task context
|
||||
)
|
||||
|
||||
# Create and run the agent
|
||||
agents = PraisonAIAgents(
|
||||
agents=[cv_analyzer],
|
||||
tasks=[cv_key_strengths,cv_analysis_task],
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
# Start the analysis - no need to pass content again since it's in task context
|
||||
result = agents.start()
|
||||
|
||||
print(result)
|
||||
@@ -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,27 @@
|
||||
# example tools.py
|
||||
from duckduckgo_search import DDGS
|
||||
from praisonai_tools import BaseTool
|
||||
|
||||
class InternetSearchTool(BaseTool):
|
||||
name: str = "InternetSearchTool"
|
||||
description: str = "Search Internet for relevant information based on a query or latest news"
|
||||
|
||||
def _run(self, query: str):
|
||||
ddgs = DDGS()
|
||||
results = ddgs.text(keywords=query, region='wt-wt', safesearch='moderate', max_results=5)
|
||||
return results
|
||||
|
||||
# Example agent
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents import PraisonAIAgents
|
||||
agent = Agent(
|
||||
name="Internet Search Agent",
|
||||
tools=[InternetSearchTool],
|
||||
instructions="""
|
||||
You are an agent that can search the internet for relevant information based on a query or latest news.
|
||||
"""
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
result = PraisonAIAgents(agents=[agent], verbose=10).start()
|
||||
print(result)
|
||||
@@ -0,0 +1,57 @@
|
||||
Name: Alex Johnson
|
||||
Location: London, UK
|
||||
|
||||
Professional Summary
|
||||
Dynamic and results-oriented leader with a proven track record of driving innovation, scaling operations, and achieving transformational growth. Over 15 years of experience in engineering, strategic management, and executive leadership in the automotive and renewable energy sectors. Passionate about sustainable technologies and visionary leadership.
|
||||
|
||||
Professional Experience
|
||||
Chief Operating Officer (COO)
|
||||
NextGen Mobility Ltd. | Palo Alto, CA | 2020 – Present
|
||||
|
||||
Spearheaded global operations for an electric vehicle company, scaling production by 200% in three years.
|
||||
Introduced automation-driven supply chain strategies, reducing costs by 15%.
|
||||
Collaborated with R&D to launch three new EV models, driving 25% revenue growth annually.
|
||||
Vice President of Engineering
|
||||
ElectroDrive Systems | San Francisco, CA | 2015 – 2020
|
||||
|
||||
Led a team of 500+ engineers in developing next-generation EV batteries, achieving a 30% increase in energy efficiency.
|
||||
Secured $50M in government funding for sustainable energy innovation.
|
||||
Drove cross-functional collaboration, aligning product development with market needs.
|
||||
Director of Renewable Energy Initiatives
|
||||
GreenTech Innovations | London, UK | 2011 – 2015
|
||||
|
||||
Oversaw £100M projects in solar and wind energy deployment across Europe.
|
||||
Established partnerships with policymakers to influence renewable energy legislation.
|
||||
Pioneered a smart energy grid system, enhancing energy distribution efficiency by 40%.
|
||||
Senior Project Manager
|
||||
Innovate Auto Ltd. | Munich, Germany | 2008 – 2011
|
||||
|
||||
Managed R&D projects for autonomous driving technologies.
|
||||
Delivered projects 20% under budget through strategic resource allocation.
|
||||
Filed 10+ patents in automotive AI and renewable integration.
|
||||
Mechanical Engineer
|
||||
AutoFusion Technologies | Berlin, Germany | 2005 – 2008
|
||||
|
||||
Designed core systems for EV powertrains, optimising energy consumption by 15%.
|
||||
Collaborated with suppliers to improve material efficiency, cutting costs by £5M annually.
|
||||
Education
|
||||
MBA in Strategic Management
|
||||
Harvard Business School | Cambridge, MA | 2015
|
||||
|
||||
MSc in Renewable Energy Engineering
|
||||
Imperial College London | London, UK | 2007
|
||||
|
||||
BSc in Mechanical Engineering
|
||||
Technical University of Munich | Munich, Germany | 2005
|
||||
|
||||
Skills
|
||||
Strategic Leadership & Visionary Thinking
|
||||
Electric Vehicle Engineering
|
||||
Renewable Energy Systems
|
||||
Global Operations Management
|
||||
Policy Advocacy & Government Relations
|
||||
Supply Chain Optimisation
|
||||
Achievements
|
||||
Featured in Forbes 30 Under 40 for innovations in EV technology.
|
||||
Author of The Future of Sustainable Mobility.
|
||||
Keynote speaker at World Energy Summit, 2023.
|
||||
@@ -0,0 +1,10 @@
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import read_csv, read_excel, write_csv, write_excel, filter_data, get_summary, group_by, pivot_table
|
||||
import os
|
||||
|
||||
agent = Agent(instructions="You are a Data Analyst Agent", tools=[read_csv, read_excel, write_csv, write_excel, filter_data, get_summary, group_by, pivot_table])
|
||||
agent.start(f"""
|
||||
Read the data from the csv file {os.path.join(os.path.dirname(__file__), "tesla-stock-price.csv")}
|
||||
Analyse the data and give me the insights
|
||||
read_csv to read the file
|
||||
""")
|
||||
@@ -0,0 +1,9 @@
|
||||
# pip install praisonaiagents
|
||||
# export OPENAI_BASE_URL=http://localhost:11434/v1
|
||||
# ollama pull deepseek-r1
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(instructions="You are helpful Assisant", llm="deepseek-r1")
|
||||
|
||||
agent.start("Why sky is Blue?")
|
||||
@@ -0,0 +1,62 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def init_agent():
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "deepseek-r1:latest",
|
||||
"temperature": 0,
|
||||
"max_tokens": 8000,
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "nomic-embed-text:latest",
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
"embedding_dims": 1536
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return Agent(
|
||||
name="Knowledge Agent",
|
||||
instructions="You answer questions based on the provided knowledge.",
|
||||
knowledge=["kag-research-paper.pdf"],
|
||||
knowledge_config=config,
|
||||
user_id="user1",
|
||||
llm="deepseek-r1"
|
||||
)
|
||||
|
||||
st.title("Knowledge Agent Chat")
|
||||
|
||||
if "agent" not in st.session_state:
|
||||
st.session_state.agent = init_agent()
|
||||
st.session_state.messages = []
|
||||
|
||||
if "messages" in st.session_state:
|
||||
for message in st.session_state.messages:
|
||||
with st.chat_message(message["role"]):
|
||||
st.markdown(message["content"])
|
||||
|
||||
prompt = st.chat_input("Ask a question...")
|
||||
|
||||
if prompt:
|
||||
st.session_state.messages.append({"role": "user", "content": prompt})
|
||||
with st.chat_message("user"):
|
||||
st.markdown(prompt)
|
||||
|
||||
with st.chat_message("assistant"):
|
||||
response = st.session_state.agent.start(prompt)
|
||||
st.markdown(response)
|
||||
st.session_state.messages.append({"role": "assistant", "content": response})
|
||||
@@ -0,0 +1,54 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "deepseek-r1:latest",
|
||||
"temperature": 0,
|
||||
"max_tokens": 8000,
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "nomic-embed-text:latest",
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Create an agent
|
||||
rag_agent = Agent(
|
||||
name="RAG Agent",
|
||||
role="Information Specialist",
|
||||
goal="Retrieve knowledge efficiently",
|
||||
llm="deepseek-r1"
|
||||
)
|
||||
|
||||
# Define a task for the agent
|
||||
rag_task = Task(
|
||||
name="RAG Task",
|
||||
description="What is KAG?",
|
||||
expected_output="Answer to the question",
|
||||
agent=rag_agent,
|
||||
context=[config] # Vector Database provided as context
|
||||
)
|
||||
|
||||
# Build Agents
|
||||
agents = PraisonAIAgents(
|
||||
agents=[rag_agent],
|
||||
tasks=[rag_task],
|
||||
user_id="user1"
|
||||
)
|
||||
|
||||
# Start Agents
|
||||
agents.start()
|
||||
@@ -0,0 +1,62 @@
|
||||
import streamlit as st
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def init_agent():
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "deepseek-r1:latest",
|
||||
"temperature": 0,
|
||||
"max_tokens": 8000,
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "nomic-embed-text:latest",
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
"embedding_dims": 1536
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return Agent(
|
||||
name="Knowledge Agent",
|
||||
instructions="You answer questions based on the provided knowledge.",
|
||||
knowledge=["kag-research-paper.pdf"],
|
||||
knowledge_config=config,
|
||||
user_id="user1",
|
||||
llm="deepseek-r1"
|
||||
)
|
||||
|
||||
st.title("Knowledge Agent Chat")
|
||||
|
||||
if "agent" not in st.session_state:
|
||||
st.session_state.agent = init_agent()
|
||||
st.session_state.messages = []
|
||||
|
||||
if "messages" in st.session_state:
|
||||
for message in st.session_state.messages:
|
||||
with st.chat_message(message["role"]):
|
||||
st.markdown(message["content"])
|
||||
|
||||
prompt = st.chat_input("Ask a question...")
|
||||
|
||||
if prompt:
|
||||
st.session_state.messages.append({"role": "user", "content": prompt})
|
||||
with st.chat_message("user"):
|
||||
st.markdown(prompt)
|
||||
|
||||
with st.chat_message("assistant"):
|
||||
response = st.session_state.agent.start(prompt)
|
||||
st.markdown(response)
|
||||
st.session_state.messages.append({"role": "assistant", "content": response})
|
||||
@@ -0,0 +1,39 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "deepseek-r1:latest",
|
||||
"temperature": 0,
|
||||
"max_tokens": 8000,
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
},
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "ollama",
|
||||
"config": {
|
||||
"model": "nomic-embed-text:latest",
|
||||
"ollama_base_url": "http://localhost:11434",
|
||||
"embedding_dims": 1536
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
name="Knowledge Agent",
|
||||
instructions="You answer questions based on the provided knowledge.",
|
||||
knowledge=["kag-research-paper.pdf"], # Indexing
|
||||
knowledge_config=config,
|
||||
user_id="user1",
|
||||
llm="deepseek-r1"
|
||||
)
|
||||
|
||||
agent.start("What is KAG in one line?") # Retrieval
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
I am Mervin Praison, a software engineer and entrepreneur.
|
||||
|
||||
I am the founder of Praison AI, a company that is building AI agents to help people with their daily tasks.
|
||||
@@ -0,0 +1,69 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create generator and evaluator agents
|
||||
generator = Agent(
|
||||
name="Generator",
|
||||
role="Solution generator",
|
||||
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 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."
|
||||
)
|
||||
)
|
||||
|
||||
evaluator = Agent(
|
||||
name="Evaluator",
|
||||
role="Solution evaluator",
|
||||
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 4 or more, respond with 'done'.\n"
|
||||
"3. Otherwise, respond with 'more'.\n"
|
||||
"4. Return only the single word: 'done' or 'more'."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Create tasks for the feedback loop
|
||||
generate_task = Task(
|
||||
name="generate",
|
||||
description="Write 2 points about AI incuding if anything exiting from previous points",
|
||||
expected_output="2 points",
|
||||
agent=generator,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["evaluate"]
|
||||
)
|
||||
|
||||
evaluate_task = Task(
|
||||
name="evaluate",
|
||||
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,
|
||||
context=[generate_task],
|
||||
task_type="decision",
|
||||
condition={
|
||||
"more": ["generate"], # Continue to generate
|
||||
"done": [""] # Exit when optimization complete
|
||||
}
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[generator, evaluator],
|
||||
tasks=[generate_task, evaluate_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run optimization workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nEvaluator-Optimizer Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,151 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from typing import List, Dict, Union
|
||||
from duckduckgo_search import DDGS
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
from langchain_community.utilities import WikipediaAPIWrapper
|
||||
|
||||
# 1. Tool
|
||||
def internet_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles, URLs, and snippets.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
def youtube_search_tool(query: str, inspect: bool = False, max_results: int = 2):
|
||||
"""
|
||||
Provide a custom wrapper around the YouTubeSearchTool.
|
||||
|
||||
Args:
|
||||
query (str): The search query for YouTube.
|
||||
inspect (bool): If True, returns tool inspection info instead of search results.
|
||||
max_results (int): Maximum number of results to return (default: 2).
|
||||
Returns:
|
||||
Union[List[str], dict]: List of YouTube video URLs or tool inspection info.
|
||||
"""
|
||||
yt = YouTubeSearchTool()
|
||||
|
||||
if inspect:
|
||||
inspection_info = {
|
||||
"type": type(yt),
|
||||
"attributes": [attr for attr in dir(yt) if not attr.startswith('_')],
|
||||
"methods": {
|
||||
"run": getattr(yt, 'run', None),
|
||||
"arun": getattr(yt, 'arun', None)
|
||||
},
|
||||
"properties": {
|
||||
"name": getattr(yt, 'name', 'youtube_search'),
|
||||
"description": getattr(yt, 'description', 'Search YouTube videos'),
|
||||
"return_direct": getattr(yt, 'return_direct', False)
|
||||
}
|
||||
}
|
||||
return inspection_info
|
||||
|
||||
# Format query with max_results
|
||||
formatted_query = f"{query}, {max_results}"
|
||||
return yt.run(formatted_query)
|
||||
|
||||
def wikipedia_search_tool(query: str, inspect: bool = False, max_chars: int = 4000, top_k: int = 3):
|
||||
"""
|
||||
Provide a custom wrapper around langchain_community's WikipediaAPIWrapper.
|
||||
|
||||
Args:
|
||||
query (str): A search query for Wikipedia.
|
||||
inspect (bool): If True, returns tool inspection info instead of search results.
|
||||
max_chars (int): Maximum characters to return (default: 4000).
|
||||
top_k (int): Number of top results to consider (default: 3).
|
||||
Returns:
|
||||
Union[str, dict]: Summary from Wikipedia or tool inspection info if inspect=True.
|
||||
"""
|
||||
w = WikipediaAPIWrapper(
|
||||
top_k_results=top_k,
|
||||
doc_content_chars_max=max_chars,
|
||||
lang='en'
|
||||
)
|
||||
|
||||
if inspect:
|
||||
inspection_info = {
|
||||
"type": type(w),
|
||||
"attributes": [attr for attr in dir(w) if not attr.startswith('_')],
|
||||
"methods": {
|
||||
"run": getattr(w, 'run', None),
|
||||
"arun": getattr(w, 'arun', None)
|
||||
},
|
||||
"properties": {
|
||||
"name": "wikipedia",
|
||||
"description": "Search and get summaries from Wikipedia",
|
||||
"top_k": w.top_k_results,
|
||||
"lang": w.lang,
|
||||
"max_chars": w.doc_content_chars_max
|
||||
}
|
||||
}
|
||||
return inspection_info
|
||||
|
||||
try:
|
||||
result = w.run(query)
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error searching Wikipedia: {str(e)}"
|
||||
|
||||
# 2. Agent
|
||||
data_agent = Agent(
|
||||
name="DataCollector",
|
||||
role="Search Specialist",
|
||||
goal="Perform internet searches to collect relevant information.",
|
||||
backstory="Expert in finding and organizing internet data from multiple sources.",
|
||||
tools=[internet_search_tool, youtube_search_tool, wikipedia_search_tool],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 3. Tasks
|
||||
collect_task = Task(
|
||||
description="Perform an internet search using the query: 'AI job trends in 2024'. Return results as a list of title, URL, and snippet.",
|
||||
expected_output="List of search results with titles, URLs, and snippets.",
|
||||
agent=data_agent,
|
||||
name="collect_data",
|
||||
is_start=True,
|
||||
next_tasks=["validate_data"]
|
||||
)
|
||||
|
||||
validate_task = Task(
|
||||
description="""Validate the collected data. Check if:
|
||||
1. At least 5 results are returned.
|
||||
2. Each result contains a title and a URL.
|
||||
Return validation_result as 'valid' or 'invalid' only no other text.""",
|
||||
expected_output="Validation result indicating if data is valid or invalid.",
|
||||
agent=data_agent,
|
||||
name="validate_data",
|
||||
task_type="decision",
|
||||
condition={
|
||||
"valid": [], # End the workflow on valid data
|
||||
"invalid": ["collect_data"] # Retry data collection on invalid data
|
||||
},
|
||||
)
|
||||
|
||||
# 4. Workflow
|
||||
agents = PraisonAIAgents(
|
||||
agents=[data_agent],
|
||||
tasks=[collect_task, validate_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
agents.start()
|
||||
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import get_stock_price, get_stock_info, get_historical_data
|
||||
|
||||
agent = Agent(instructions="You are a Research Agent", tools=[get_stock_price, get_stock_info, get_historical_data])
|
||||
agent.start("Understand current stock price and historical data of Apple and Google. Tell me if I can invest in them")
|
||||
@@ -0,0 +1,34 @@
|
||||
import gradio as gr
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
def research(query):
|
||||
agent = Agent(instructions="You are a Research Agent", tools=[duckduckgo])
|
||||
result = agent.start(query)
|
||||
# Format the result with enhanced markdown
|
||||
formatted_result = f"""
|
||||
{result}
|
||||
----
|
||||
*Generated by PraisonAI Research Assistant*
|
||||
"""
|
||||
return formatted_result
|
||||
|
||||
# Create a simple Gradio interface
|
||||
demo = gr.Interface(
|
||||
fn=research,
|
||||
inputs=gr.Textbox(
|
||||
label="Research Query",
|
||||
placeholder="Enter your research topic...",
|
||||
lines=2
|
||||
),
|
||||
outputs=gr.Markdown(
|
||||
show_copy_button=True,
|
||||
height=500,
|
||||
container=True
|
||||
),
|
||||
title="AI Research Assistant",
|
||||
description="Enter your research query below to get started!",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
Whitespace-only changes.
@@ -0,0 +1,159 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
import os
|
||||
import json
|
||||
|
||||
class ProjectEvaluation(BaseModel):
|
||||
innovation_score: int # 0-100
|
||||
technical_complexity: int # 0-100
|
||||
presentation_quality: int # 0-100
|
||||
user_experience: int # 0-100
|
||||
completeness: int # 0-100
|
||||
overall_score: int # 0-100
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
notable_features: List[str]
|
||||
technical_highlights: List[str]
|
||||
recommendations: List[str]
|
||||
market_potential: str
|
||||
scalability_assessment: str
|
||||
|
||||
# Create Vision Analysis Agent
|
||||
hackathon_judge = Agent(
|
||||
name="HackathonJudge",
|
||||
role="Technical Project Evaluator",
|
||||
goal="Evaluate hackathon projects through video demonstrations",
|
||||
backstory="""You are an experienced hackathon judge and technical expert.
|
||||
You excel at evaluating innovation, technical implementation, and presentation quality.
|
||||
You provide constructive feedback and identify both strengths and areas for improvement.""",
|
||||
llm="gpt-4o-mini", # Using vision-capable model
|
||||
self_reflect=False,
|
||||
knowledge=""
|
||||
)
|
||||
|
||||
def evaluate_project(video_path: str) -> ProjectEvaluation:
|
||||
"""
|
||||
Evaluate a hackathon project based on its video demonstration
|
||||
"""
|
||||
evaluation_task = Task(
|
||||
name="project_evaluation",
|
||||
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
|
||||
|
||||
1. Score the following aspects (0-100):
|
||||
- Innovation and Creativity
|
||||
- Technical Complexity
|
||||
- Presentation Quality
|
||||
- User Experience
|
||||
- Project Completeness
|
||||
|
||||
2. Identify:
|
||||
- Key strengths and standout features
|
||||
- Areas that could be improved
|
||||
- Notable technical implementations
|
||||
- Market potential and scalability
|
||||
|
||||
3. Provide:
|
||||
- Specific recommendations for improvement
|
||||
- Technical suggestions
|
||||
- Potential future enhancements""",
|
||||
expected_output="Detailed project evaluation with scores and feedback",
|
||||
agent=hackathon_judge,
|
||||
output_pydantic=ProjectEvaluation,
|
||||
images=[video_path] # Video input for multimodal analysis
|
||||
)
|
||||
|
||||
# Initialize and run evaluation
|
||||
agents = PraisonAIAgents(
|
||||
agents=[hackathon_judge],
|
||||
tasks=[evaluation_task],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
response = agents.start()
|
||||
|
||||
try:
|
||||
# If response contains task_results, extract the Pydantic model directly
|
||||
if isinstance(response, dict) and 'task_results' in response:
|
||||
task_output = response['task_results'][0]
|
||||
if hasattr(task_output, 'pydantic'):
|
||||
return task_output.pydantic
|
||||
elif hasattr(task_output, 'raw'):
|
||||
# Extract JSON from raw string if it's wrapped in ```json
|
||||
raw_text = task_output.raw
|
||||
if raw_text.startswith('```json'):
|
||||
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
|
||||
evaluation_data = json.loads(raw_text)
|
||||
else:
|
||||
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
|
||||
# If response is a string, try to parse it as JSON
|
||||
elif isinstance(response, str):
|
||||
evaluation_data = json.loads(response)
|
||||
# If response is a dict with task_status
|
||||
elif isinstance(response, dict) and 'task_status' in response:
|
||||
content = response['task_status']
|
||||
if isinstance(content, dict):
|
||||
evaluation_data = content
|
||||
else:
|
||||
evaluation_data = json.loads(content) if isinstance(content, str) else content
|
||||
else:
|
||||
evaluation_data = response
|
||||
|
||||
print(f"Debug - Parsed evaluation_data: {evaluation_data}")
|
||||
|
||||
# Create and return ProjectEvaluation instance
|
||||
return ProjectEvaluation(
|
||||
innovation_score=int(evaluation_data.get('innovation_score', 0)),
|
||||
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
|
||||
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
|
||||
user_experience=int(evaluation_data.get('user_experience', 0)),
|
||||
completeness=int(evaluation_data.get('completeness', 0)),
|
||||
overall_score=int(evaluation_data.get('overall_score', 0)),
|
||||
key_strengths=evaluation_data.get('key_strengths', []),
|
||||
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
|
||||
notable_features=evaluation_data.get('notable_features', []),
|
||||
technical_highlights=evaluation_data.get('technical_highlights', []),
|
||||
recommendations=evaluation_data.get('recommendations', []),
|
||||
market_potential=str(evaluation_data.get('market_potential', '')),
|
||||
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Debug - Raw response: {response}")
|
||||
print(f"Error processing response: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
video_path = os.path.join(current_dir, "presentation.mp4")
|
||||
result = evaluate_project(video_path) # Now returns ProjectEvaluation directly
|
||||
|
||||
print("\nHackathon Project Evaluation")
|
||||
print("===========================")
|
||||
print(f"\nOverall Score: {result.overall_score}/100")
|
||||
print("\nDetailed Scores:")
|
||||
print(f"Innovation: {result.innovation_score}/100")
|
||||
print(f"Technical Complexity: {result.technical_complexity}/100")
|
||||
print(f"Presentation: {result.presentation_quality}/100")
|
||||
print(f"User Experience: {result.user_experience}/100")
|
||||
print(f"Completeness: {result.completeness}/100")
|
||||
|
||||
print("\nKey Strengths:")
|
||||
for strength in result.key_strengths:
|
||||
print(f"- {strength}")
|
||||
|
||||
print("\nAreas for Improvement:")
|
||||
for area in result.areas_for_improvement:
|
||||
print(f"- {area}")
|
||||
|
||||
print("\nTechnical Highlights:")
|
||||
for highlight in result.technical_highlights:
|
||||
print(f"- {highlight}")
|
||||
|
||||
print("\nRecommendations:")
|
||||
for rec in result.recommendations:
|
||||
print(f"- {rec}")
|
||||
|
||||
print(f"\nMarket Potential: {result.market_potential}")
|
||||
print(f"\nScalability Assessment: {result.scalability_assessment}")
|
||||
@@ -0,0 +1,159 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
import os
|
||||
import json
|
||||
|
||||
class ProjectEvaluation(BaseModel):
|
||||
innovation_score: int # 0-100
|
||||
technical_complexity: int # 0-100
|
||||
presentation_quality: int # 0-100
|
||||
user_experience: int # 0-100
|
||||
completeness: int # 0-100
|
||||
overall_score: int # 0-100
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
notable_features: List[str]
|
||||
technical_highlights: List[str]
|
||||
recommendations: List[str]
|
||||
market_potential: str
|
||||
scalability_assessment: str
|
||||
|
||||
# Create Vision Analysis Agent
|
||||
hackathon_judge = Agent(
|
||||
name="HackathonJudge",
|
||||
role="Technical Project Evaluator",
|
||||
goal="Evaluate hackathon projects through video demonstrations",
|
||||
backstory="""You are an experienced hackathon judge and technical expert.
|
||||
You excel at evaluating innovation, technical implementation, and presentation quality.
|
||||
You provide constructive feedback and identify both strengths and areas for improvement.""",
|
||||
llm="gpt-4o-mini", # Using vision-capable model
|
||||
self_reflect=False,
|
||||
knowledge=""
|
||||
)
|
||||
|
||||
def evaluate_project(video_path: str) -> ProjectEvaluation:
|
||||
"""
|
||||
Evaluate a hackathon project based on its video demonstration
|
||||
"""
|
||||
evaluation_task = Task(
|
||||
name="project_evaluation",
|
||||
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
|
||||
|
||||
1. Score the following aspects (0-100):
|
||||
- Innovation and Creativity
|
||||
- Technical Complexity
|
||||
- Presentation Quality
|
||||
- User Experience
|
||||
- Project Completeness
|
||||
|
||||
2. Identify:
|
||||
- Key strengths and standout features
|
||||
- Areas that could be improved
|
||||
- Notable technical implementations
|
||||
- Market potential and scalability
|
||||
|
||||
3. Provide:
|
||||
- Specific recommendations for improvement
|
||||
- Technical suggestions
|
||||
- Potential future enhancements""",
|
||||
expected_output="Detailed project evaluation with scores and feedback",
|
||||
agent=hackathon_judge,
|
||||
output_pydantic=ProjectEvaluation,
|
||||
images=[video_path] # Video input for multimodal analysis
|
||||
)
|
||||
|
||||
# Initialize and run evaluation
|
||||
agents = PraisonAIAgents(
|
||||
agents=[hackathon_judge],
|
||||
tasks=[evaluation_task],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
response = agents.start()
|
||||
|
||||
try:
|
||||
# If response contains task_results, extract the Pydantic model directly
|
||||
if isinstance(response, dict) and 'task_results' in response:
|
||||
task_output = response['task_results'][0]
|
||||
if hasattr(task_output, 'pydantic'):
|
||||
return task_output.pydantic
|
||||
elif hasattr(task_output, 'raw'):
|
||||
# Extract JSON from raw string if it's wrapped in ```json
|
||||
raw_text = task_output.raw
|
||||
if raw_text.startswith('```json'):
|
||||
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
|
||||
evaluation_data = json.loads(raw_text)
|
||||
else:
|
||||
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
|
||||
# If response is a string, try to parse it as JSON
|
||||
elif isinstance(response, str):
|
||||
evaluation_data = json.loads(response)
|
||||
# If response is a dict with task_status
|
||||
elif isinstance(response, dict) and 'task_status' in response:
|
||||
content = response['task_status']
|
||||
if isinstance(content, dict):
|
||||
evaluation_data = content
|
||||
else:
|
||||
evaluation_data = json.loads(content) if isinstance(content, str) else content
|
||||
else:
|
||||
evaluation_data = response
|
||||
|
||||
print(f"Debug - Parsed evaluation_data: {evaluation_data}")
|
||||
|
||||
# Create and return ProjectEvaluation instance
|
||||
return ProjectEvaluation(
|
||||
innovation_score=int(evaluation_data.get('innovation_score', 0)),
|
||||
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
|
||||
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
|
||||
user_experience=int(evaluation_data.get('user_experience', 0)),
|
||||
completeness=int(evaluation_data.get('completeness', 0)),
|
||||
overall_score=int(evaluation_data.get('overall_score', 0)),
|
||||
key_strengths=evaluation_data.get('key_strengths', []),
|
||||
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
|
||||
notable_features=evaluation_data.get('notable_features', []),
|
||||
technical_highlights=evaluation_data.get('technical_highlights', []),
|
||||
recommendations=evaluation_data.get('recommendations', []),
|
||||
market_potential=str(evaluation_data.get('market_potential', '')),
|
||||
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Debug - Raw response: {response}")
|
||||
print(f"Error processing response: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
video_path = os.path.join(current_dir, "presentation.mp4")
|
||||
result = evaluate_project(video_path) # Now returns ProjectEvaluation directly
|
||||
|
||||
print("\nHackathon Project Evaluation")
|
||||
print("===========================")
|
||||
print(f"\nOverall Score: {result.overall_score}/100")
|
||||
print("\nDetailed Scores:")
|
||||
print(f"Innovation: {result.innovation_score}/100")
|
||||
print(f"Technical Complexity: {result.technical_complexity}/100")
|
||||
print(f"Presentation: {result.presentation_quality}/100")
|
||||
print(f"User Experience: {result.user_experience}/100")
|
||||
print(f"Completeness: {result.completeness}/100")
|
||||
|
||||
print("\nKey Strengths:")
|
||||
for strength in result.key_strengths:
|
||||
print(f"- {strength}")
|
||||
|
||||
print("\nAreas for Improvement:")
|
||||
for area in result.areas_for_improvement:
|
||||
print(f"- {area}")
|
||||
|
||||
print("\nTechnical Highlights:")
|
||||
for highlight in result.technical_highlights:
|
||||
print(f"- {highlight}")
|
||||
|
||||
print("\nRecommendations:")
|
||||
for rec in result.recommendations:
|
||||
print(f"- {rec}")
|
||||
|
||||
print(f"\nMarket Potential: {result.market_potential}")
|
||||
print(f"\nScalability Assessment: {result.scalability_assessment}")
|
||||
@@ -0,0 +1,210 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
import tempfile
|
||||
import json
|
||||
|
||||
class ProjectEvaluation(BaseModel):
|
||||
innovation_score: int # 0-100
|
||||
technical_complexity: int # 0-100
|
||||
presentation_quality: int # 0-100
|
||||
user_experience: int # 0-100
|
||||
completeness: int # 0-100
|
||||
overall_score: int # 0-100
|
||||
key_strengths: List[str]
|
||||
areas_for_improvement: List[str]
|
||||
notable_features: List[str]
|
||||
technical_highlights: List[str]
|
||||
recommendations: List[str]
|
||||
market_potential: str
|
||||
scalability_assessment: str
|
||||
|
||||
# Set page config
|
||||
st.set_page_config(
|
||||
page_title="Hackathon Project Evaluator",
|
||||
page_icon="🏆",
|
||||
layout="wide"
|
||||
)
|
||||
|
||||
# Create Vision Analysis Agent
|
||||
@st.cache_resource
|
||||
def get_hackathon_judge():
|
||||
return Agent(
|
||||
name="HackathonJudge",
|
||||
role="Technical Project Evaluator",
|
||||
goal="Evaluate hackathon projects through video demonstrations",
|
||||
backstory="""You are an experienced hackathon judge and technical expert.
|
||||
You excel at evaluating innovation, technical implementation, and presentation quality.
|
||||
You provide constructive feedback and identify both strengths and areas for improvement.""",
|
||||
llm="gpt-4o-mini", # Using vision-capable model
|
||||
self_reflect=False,
|
||||
knowledge=""
|
||||
)
|
||||
|
||||
def evaluate_project(video_path: str) -> ProjectEvaluation:
|
||||
"""
|
||||
Evaluate a hackathon project based on its video demonstration
|
||||
"""
|
||||
hackathon_judge = get_hackathon_judge()
|
||||
|
||||
evaluation_task = Task(
|
||||
name="project_evaluation",
|
||||
description="""Analyze this hackathon project video demonstration and provide a comprehensive evaluation:
|
||||
|
||||
1. Score the following aspects (0-100):
|
||||
- Innovation and Creativity
|
||||
- Technical Complexity
|
||||
- Presentation Quality
|
||||
- User Experience
|
||||
- Project Completeness
|
||||
|
||||
2. Identify:
|
||||
- Key strengths and standout features
|
||||
- Areas that could be improved
|
||||
- Notable technical implementations
|
||||
- Market potential and scalability
|
||||
|
||||
3. Provide:
|
||||
- Specific recommendations for improvement
|
||||
- Technical suggestions
|
||||
- Potential future enhancements""",
|
||||
expected_output="Detailed project evaluation with scores and feedback",
|
||||
agent=hackathon_judge,
|
||||
output_pydantic=ProjectEvaluation,
|
||||
images=[video_path] # Video input for multimodal analysis
|
||||
)
|
||||
|
||||
# Initialize and run evaluation
|
||||
agents = PraisonAIAgents(
|
||||
agents=[hackathon_judge],
|
||||
tasks=[evaluation_task],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
response = agents.start()
|
||||
|
||||
try:
|
||||
# If response contains task_results, extract the Pydantic model directly
|
||||
if isinstance(response, dict) and 'task_results' in response:
|
||||
task_output = response['task_results'][0]
|
||||
if hasattr(task_output, 'pydantic'):
|
||||
return task_output.pydantic
|
||||
elif hasattr(task_output, 'raw'):
|
||||
# Extract JSON from raw string if it's wrapped in ```json
|
||||
raw_text = task_output.raw
|
||||
if raw_text.startswith('```json'):
|
||||
raw_text = raw_text.split('\n', 1)[1].rsplit('\n', 1)[0]
|
||||
evaluation_data = json.loads(raw_text)
|
||||
else:
|
||||
evaluation_data = json.loads(task_output) if isinstance(task_output, str) else task_output
|
||||
elif isinstance(response, str):
|
||||
evaluation_data = json.loads(response)
|
||||
elif isinstance(response, dict) and 'task_status' in response:
|
||||
content = response['task_status']
|
||||
if isinstance(content, dict):
|
||||
evaluation_data = content
|
||||
else:
|
||||
evaluation_data = json.loads(content) if isinstance(content, str) else content
|
||||
else:
|
||||
evaluation_data = response
|
||||
|
||||
# Create and return ProjectEvaluation instance
|
||||
return ProjectEvaluation(
|
||||
innovation_score=int(evaluation_data.get('innovation_score', 0)),
|
||||
technical_complexity=int(evaluation_data.get('technical_complexity', 0)),
|
||||
presentation_quality=int(evaluation_data.get('presentation_quality', 0)),
|
||||
user_experience=int(evaluation_data.get('user_experience', 0)),
|
||||
completeness=int(evaluation_data.get('completeness', 0)),
|
||||
overall_score=int(evaluation_data.get('overall_score', 0)),
|
||||
key_strengths=evaluation_data.get('key_strengths', []),
|
||||
areas_for_improvement=evaluation_data.get('areas_for_improvement', []),
|
||||
notable_features=evaluation_data.get('notable_features', []),
|
||||
technical_highlights=evaluation_data.get('technical_highlights', []),
|
||||
recommendations=evaluation_data.get('recommendations', []),
|
||||
market_potential=str(evaluation_data.get('market_potential', '')),
|
||||
scalability_assessment=str(evaluation_data.get('scalability_assessment', ''))
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Debug - Raw response: {response}")
|
||||
print(f"Error processing response: {e}")
|
||||
raise
|
||||
|
||||
# Title and description
|
||||
st.title("🏆 Hackathon Judge Agent")
|
||||
st.markdown("""
|
||||
Upload your hackathon project demonstration video for an AI-powered evaluation.
|
||||
Get comprehensive feedback on various aspects of your project.
|
||||
""")
|
||||
|
||||
# File uploader
|
||||
uploaded_file = st.file_uploader("Choose a video file", type=['mp4', 'avi', 'mov', 'mkv'])
|
||||
|
||||
if uploaded_file:
|
||||
# Create a temporary file to store the video
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.'+uploaded_file.name.split('.')[-1]) as tmp_file:
|
||||
tmp_file.write(uploaded_file.getvalue())
|
||||
video_path = tmp_file.name
|
||||
|
||||
with st.spinner("🤖 AI is evaluating your project..."):
|
||||
try:
|
||||
# Evaluate the project
|
||||
result = evaluate_project(video_path)
|
||||
|
||||
# Display results
|
||||
st.header("Overall Score")
|
||||
st.metric("Overall Score", f"{result.overall_score}/100")
|
||||
|
||||
# Display detailed scores
|
||||
st.header("Detailed Scores")
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("Innovation", f"{result.innovation_score}/100")
|
||||
st.metric("Technical Complexity", f"{result.technical_complexity}/100")
|
||||
with col2:
|
||||
st.metric("Presentation", f"{result.presentation_quality}/100")
|
||||
st.metric("User Experience", f"{result.user_experience}/100")
|
||||
with col3:
|
||||
st.metric("Completeness", f"{result.completeness}/100")
|
||||
|
||||
# Display qualitative feedback
|
||||
st.header("Key Strengths")
|
||||
for strength in result.key_strengths:
|
||||
st.write(f"• {strength}")
|
||||
|
||||
st.header("Areas for Improvement")
|
||||
for area in result.areas_for_improvement:
|
||||
st.write(f"• {area}")
|
||||
|
||||
st.header("Technical Highlights")
|
||||
for highlight in result.technical_highlights:
|
||||
st.write(f"• {highlight}")
|
||||
|
||||
st.header("Notable Features")
|
||||
for feature in result.notable_features:
|
||||
st.write(f"• {feature}")
|
||||
|
||||
st.header("Recommendations")
|
||||
for rec in result.recommendations:
|
||||
st.write(f"• {rec}")
|
||||
|
||||
# Market Analysis
|
||||
st.header("Market Analysis")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.subheader("Market Potential")
|
||||
st.write(result.market_potential)
|
||||
with col2:
|
||||
st.subheader("Scalability Assessment")
|
||||
st.write(result.scalability_assessment)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error evaluating the project: {str(e)}")
|
||||
finally:
|
||||
# Clean up the temporary file
|
||||
os.unlink(video_path)
|
||||
else:
|
||||
# Display placeholder content
|
||||
st.info("👆 Upload a video file to get started!")
|
||||
@@ -0,0 +1,47 @@
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agent = Agent(instructions="You are a Image Analysis Agent", tools=[duckduckgo])
|
||||
agent.start("I want to go London next week, find me a good hotel and flight")
|
||||
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create Image Analysis Agent
|
||||
image_agent = Agent(
|
||||
name="ImageAnalyst",
|
||||
role="Image Analysis Specialist",
|
||||
goal="Analyze images and videos to extract meaningful information",
|
||||
backstory="""You are an expert in computer vision and image analysis.
|
||||
You excel at describing images, detecting objects, and understanding visual content.""",
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 1. Task with Image URL
|
||||
task1 = Task(
|
||||
name="analyze_landmark",
|
||||
description="Describe this famous landmark and its architectural features.",
|
||||
expected_output="Detailed description of the landmark's architecture and significance",
|
||||
agent=image_agent,
|
||||
images=["https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg"]
|
||||
)
|
||||
|
||||
# 2. Task with Local Image File
|
||||
task2 = Task(
|
||||
name="analyze_local_image",
|
||||
description="What objects can you see in this image? Describe their arrangement.",
|
||||
expected_output="Detailed description of objects and their spatial relationships",
|
||||
agent=image_agent,
|
||||
images=["image.jpg"]
|
||||
)
|
||||
|
||||
# Create PraisonAIAgents instance
|
||||
agents = PraisonAIAgents(
|
||||
agents=[image_agent],
|
||||
tasks=[task1, task2],
|
||||
process="sequential",
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
agents.start()
|
||||
@@ -0,0 +1,47 @@
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agent = Agent(instructions="You are a Image Analysis Agent", tools=[duckduckgo])
|
||||
agent.start("I want to go London next week, find me a good hotel and flight")
|
||||
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create Image Analysis Agent
|
||||
image_agent = Agent(
|
||||
name="ImageAnalyst",
|
||||
role="Image Analysis Specialist",
|
||||
goal="Analyze images and videos to extract meaningful information",
|
||||
backstory="""You are an expert in computer vision and image analysis.
|
||||
You excel at describing images, detecting objects, and understanding visual content.""",
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 1. Task with Image URL
|
||||
task1 = Task(
|
||||
name="analyze_landmark",
|
||||
description="Describe this famous landmark and its architectural features.",
|
||||
expected_output="Detailed description of the landmark's architecture and significance",
|
||||
agent=image_agent,
|
||||
images=["https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg"]
|
||||
)
|
||||
|
||||
# 2. Task with Local Image File
|
||||
task2 = Task(
|
||||
name="analyze_local_image",
|
||||
description="What objects can you see in this image? Describe their arrangement.",
|
||||
expected_output="Detailed description of objects and their spatial relationships",
|
||||
agent=image_agent,
|
||||
images=["image.jpg"]
|
||||
)
|
||||
|
||||
# Create PraisonAIAgents instance
|
||||
agents = PraisonAIAgents(
|
||||
agents=[image_agent],
|
||||
tasks=[task1, task2],
|
||||
process="sequential",
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
agents.start()
|
||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Define the configuration for the Knowledge instance
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": os.path.abspath("./.praison"),
|
||||
"host": None,
|
||||
"port": None
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"embedding_dims": 1536
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0,
|
||||
"max_tokens": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create an agent with knowledge capabilities
|
||||
knowledge_agent = Agent(
|
||||
name="KnowledgeAgent",
|
||||
role="Information Specialist",
|
||||
goal="Store and retrieve knowledge efficiently",
|
||||
backstory="Expert in managing and utilizing stored knowledge",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Define a task for the agent
|
||||
knowledge_task = Task(
|
||||
name="knowledge_task",
|
||||
description="KAG",
|
||||
expected_output="Answer to the question",
|
||||
agent=knowledge_agent,
|
||||
context=[
|
||||
{
|
||||
"embedding_db_config": json.dumps(config["vector_store"])
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
# Create and start the agents
|
||||
agents = PraisonAIAgents(
|
||||
agents=[knowledge_agent],
|
||||
tasks=[knowledge_task],
|
||||
process="sequential",
|
||||
user_id="user1"
|
||||
)
|
||||
|
||||
# Start execution
|
||||
result = agents.start()
|
||||
@@ -0,0 +1,21 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
name="Knowledge Agent",
|
||||
instructions="You answer questions based on the provided knowledge.",
|
||||
knowledge=["large.pdf"],
|
||||
knowledge_config=config,
|
||||
user_id="user1"
|
||||
)
|
||||
|
||||
agent.start("What is KAG in one line?")
|
||||
@@ -0,0 +1,44 @@
|
||||
import logging
|
||||
from praisonaiagents.knowledge import Knowledge
|
||||
import os
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": os.path.abspath("./.praison"),
|
||||
"host": None,
|
||||
"port": None
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"embedding_dims": 1536
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0,
|
||||
"max_tokens": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
knowledge = Knowledge(config)
|
||||
|
||||
# Search for memories based on a query
|
||||
query = "KAG"
|
||||
logger.info(f"Searching for memories with query: {query}")
|
||||
search_results = knowledge.search(query, user_id="user1")
|
||||
logger.info(f"Search results: {search_results}")
|
||||
print(f"\nSearch results for '{query}':")
|
||||
print(search_results)
|
||||
@@ -0,0 +1,171 @@
|
||||
import logging
|
||||
from praisonaiagents.knowledge import Knowledge
|
||||
import os
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "test",
|
||||
"path": ".praison",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
knowledge = Knowledge(config)
|
||||
|
||||
# Store memories
|
||||
logger.info("Storing memories...")
|
||||
cricket_memory = knowledge.store("Likes to play cricket on weekends", user_id="alice", metadata={"category": "hobbies"})
|
||||
logger.info(f"Stored cricket memory: {cricket_memory}")
|
||||
tennis_memory = knowledge.store("Enjoys playing tennis with friends", user_id="alice", metadata={"category": "hobbies"})
|
||||
logger.info(f"Stored tennis memory: {tennis_memory}")
|
||||
work_memory = knowledge.store("Works as a software engineer", user_id="alice", metadata={"category": "work"})
|
||||
logger.info(f"Stored work memory: {work_memory}")
|
||||
|
||||
print("Stored memories:")
|
||||
print(cricket_memory)
|
||||
print(tennis_memory)
|
||||
print(work_memory)
|
||||
|
||||
# Retrieve all memories for a user
|
||||
logger.info("Retrieving all memories for Alice...")
|
||||
alice_memories = knowledge.get_all(user_id="alice")
|
||||
logger.info(f"Retrieved memories for Alice: {alice_memories}")
|
||||
print("\nAll memories for Alice:")
|
||||
for memory in alice_memories:
|
||||
print(memory)
|
||||
|
||||
# Retrieve a specific memory by ID
|
||||
logger.info(f"Retrieving memory with ID: {cricket_memory['results'][0]['id']}")
|
||||
retrieved_memory = knowledge.get(cricket_memory['results'][0]["id"])
|
||||
logger.info(f"Retrieved memory: {retrieved_memory}")
|
||||
print("\nRetrieved memory:")
|
||||
print(retrieved_memory)
|
||||
|
||||
# Search for memories based on a query
|
||||
query = "What are Alice's hobbies?"
|
||||
logger.info(f"Searching for memories with query: {query}")
|
||||
search_results = knowledge.search(query, user_id="alice")
|
||||
logger.info(f"Search results: {search_results}")
|
||||
print(f"\nSearch results for '{query}':")
|
||||
for memory in search_results:
|
||||
print(memory)
|
||||
|
||||
# Update a memory
|
||||
logger.info(f"Updating memory with ID: {tennis_memory['results'][0]['id']}")
|
||||
updated_memory = knowledge.update(memory_id=tennis_memory['results'][0]["id"], data="Loves playing tennis on weekends")
|
||||
logger.info(f"Updated memory: {updated_memory}")
|
||||
print("\nUpdated memory:")
|
||||
print(updated_memory)
|
||||
|
||||
# Get memory history
|
||||
logger.info(f"Retrieving memory history for ID: {tennis_memory['results'][0]['id']}")
|
||||
memory_history = knowledge.history(memory_id=tennis_memory['results'][0]["id"])
|
||||
logger.info(f"Memory history: {memory_history}")
|
||||
print("\nMemory history:")
|
||||
for entry in memory_history:
|
||||
print(entry)
|
||||
|
||||
# Delete a memory
|
||||
logger.info(f"Deleting memory with ID: {work_memory['results'][0]['id']}")
|
||||
knowledge.delete(memory_id=work_memory['results'][0]["id"])
|
||||
logger.info("Memory deleted")
|
||||
print("\nDeleted memory")
|
||||
|
||||
# Retrieve memories after deletion
|
||||
logger.info("Retrieving memories after deletion...")
|
||||
alice_memories = knowledge.get_all(user_id="alice")
|
||||
logger.info(f"Retrieved memories after deletion: {alice_memories}")
|
||||
print("\nMemories after deletion:")
|
||||
for memory in alice_memories:
|
||||
print(memory)
|
||||
|
||||
# Delete all memories for a user
|
||||
logger.info("Deleting all memories for Alice...")
|
||||
knowledge.delete_all(user_id="alice")
|
||||
logger.info("All memories for Alice deleted")
|
||||
print("\nAll memories for Alice deleted")
|
||||
|
||||
# Retrieve memories after deleting all
|
||||
logger.info("Retrieving memories after deleting all...")
|
||||
alice_memories = knowledge.get_all(user_id="alice")
|
||||
logger.info(f"Retrieved memories after deleting all: {alice_memories}")
|
||||
print("\nMemories after deleting all:")
|
||||
print(alice_memories)
|
||||
|
||||
# Reset all memories
|
||||
logger.info("Resetting all memories...")
|
||||
knowledge.reset()
|
||||
logger.info("All memories reset")
|
||||
print("\nAll memories reset")
|
||||
|
||||
# Test file handling
|
||||
logger.info("Testing file handling...")
|
||||
|
||||
try:
|
||||
# Get absolute path to test files
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
pdf_file = os.path.join(current_dir, "small.pdf")
|
||||
txt_file = os.path.join(current_dir, "sample.txt")
|
||||
|
||||
# Test text file
|
||||
logger.info(f"Testing text file: {txt_file}")
|
||||
# Read and display content
|
||||
with open(txt_file, 'r') as file:
|
||||
txt_content = file.read()
|
||||
print("\nText file content:")
|
||||
print(txt_content)
|
||||
# Store the text file using add
|
||||
txt_memory = knowledge.add(txt_file, user_id="bob", metadata={"type": "text"})
|
||||
logger.info(f"Stored text file: {txt_memory}")
|
||||
print("\nStored text content:")
|
||||
if txt_memory and 'results' in txt_memory and txt_memory['results']:
|
||||
for mem in txt_memory['results']:
|
||||
print(mem)
|
||||
else:
|
||||
print("No valid memories were stored from the text file")
|
||||
|
||||
# Verify text memories are stored
|
||||
if txt_memory and 'results' in txt_memory and txt_memory['results']:
|
||||
logger.info("Verifying text memories...")
|
||||
for mem in txt_memory['results']:
|
||||
if isinstance(mem, dict) and 'id' in mem:
|
||||
stored = knowledge.get(mem['id'])
|
||||
logger.info(f"Verified text memory: {stored}")
|
||||
else:
|
||||
logger.warning(f"Unexpected memory format: {mem}")
|
||||
else:
|
||||
logger.warning("No valid memories were stored from the text file")
|
||||
|
||||
# Test pdf file
|
||||
logger.info(f"Testing pdf file: {pdf_file}")
|
||||
pdf_memory = knowledge.add(pdf_file, user_id="bob", metadata={"type": "pdf"})
|
||||
logger.info(f"Stored pdf file: {pdf_memory}")
|
||||
print("\nStored pdf content:")
|
||||
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
|
||||
for mem in pdf_memory['results']:
|
||||
print(mem)
|
||||
else:
|
||||
print("No valid memories were stored from the PDF file")
|
||||
|
||||
# Verify stored content
|
||||
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
|
||||
for memory in pdf_memory['results']:
|
||||
if isinstance(memory, dict) and 'id' in memory:
|
||||
stored_memory = knowledge.get(memory['id'])
|
||||
logger.info(f"Verified stored memory: {stored_memory}")
|
||||
print("\nVerified content:")
|
||||
print(stored_memory)
|
||||
else:
|
||||
logger.warning(f"Unexpected memory format: {memory}")
|
||||
else:
|
||||
logger.warning("No valid memories were stored from the PDF file")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during file handling test: {str(e)}")
|
||||
raise
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import logging
|
||||
from praisonaiagents.knowledge import Knowledge
|
||||
import os
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
knowledge = Knowledge()
|
||||
try:
|
||||
# Get absolute path to test files
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
pdf_file = os.path.join(current_dir, "large.pdf")
|
||||
|
||||
# Test pdf file
|
||||
logger.info(f"Testing pdf file: {pdf_file}")
|
||||
pdf_memory = knowledge.add(pdf_file, user_id="bob", metadata={"type": "pdf"})
|
||||
logger.info(f"Stored pdf file: {pdf_memory}")
|
||||
print("\nStored pdf content:")
|
||||
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
|
||||
for mem in pdf_memory['results']:
|
||||
print(mem)
|
||||
else:
|
||||
print("No valid memories were stored from the PDF file")
|
||||
|
||||
# Verify stored content
|
||||
if pdf_memory and 'results' in pdf_memory and pdf_memory['results']:
|
||||
for memory in pdf_memory['results']:
|
||||
if isinstance(memory, dict) and 'id' in memory:
|
||||
stored_memory = knowledge.get(memory['id'])
|
||||
logger.info(f"Verified stored memory: {stored_memory}")
|
||||
print("\nVerified content:")
|
||||
print(stored_memory)
|
||||
else:
|
||||
logger.warning(f"Unexpected memory format: {memory}")
|
||||
else:
|
||||
logger.warning("No valid memories were stored from the PDF file")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during file handling test: {str(e)}")
|
||||
raise
|
||||
@@ -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],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,73 @@
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
from langchain_community.utilities import WikipediaAPIWrapper
|
||||
|
||||
def test_tools():
|
||||
"""Test both YouTube and Wikipedia tools"""
|
||||
|
||||
print("\n=== Testing YouTube Search ===")
|
||||
# Test YouTube
|
||||
yt = YouTubeSearchTool()
|
||||
|
||||
# Test basic search
|
||||
print("\nBasic YouTube Search:")
|
||||
query = "AI advancements 2024"
|
||||
print(f"Query: {query}")
|
||||
result = yt.run(query)
|
||||
print(f"Results: {result}")
|
||||
|
||||
# Test with max results
|
||||
print("\nYouTube Search with max results:")
|
||||
query = "AI advancements 2024, 3" # Format: query, max_results
|
||||
print(f"Query: {query}")
|
||||
result = yt.run(query)
|
||||
print(f"Results: {result}")
|
||||
|
||||
print("\n=== Testing Wikipedia Search ===")
|
||||
# Test Wikipedia
|
||||
wiki = WikipediaAPIWrapper(
|
||||
top_k_results=2,
|
||||
doc_content_chars_max=500
|
||||
)
|
||||
|
||||
# Test basic search
|
||||
print("\nBasic Wikipedia Search:")
|
||||
query = "Artificial Intelligence"
|
||||
print(f"Query: {query}")
|
||||
result = wiki.run(query)
|
||||
print(f"Results: {result[:200]}..." if result else "No result")
|
||||
|
||||
# Test with different parameters
|
||||
print("\nWikipedia Search with different language:")
|
||||
wiki_fr = WikipediaAPIWrapper(
|
||||
lang='fr',
|
||||
top_k_results=1,
|
||||
doc_content_chars_max=300
|
||||
)
|
||||
result = wiki_fr.run("Intelligence Artificielle")
|
||||
print(f"French Results: {result[:200]}..." if result else "No result")
|
||||
|
||||
def print_tool_info():
|
||||
"""Print detailed information about the tools"""
|
||||
|
||||
print("\n=== Tool Information ===")
|
||||
|
||||
# YouTube Tool Info
|
||||
print("\nYouTube Tool:")
|
||||
yt = YouTubeSearchTool()
|
||||
print("Type:", type(yt))
|
||||
print("Name:", getattr(yt, 'name', None))
|
||||
print("Description:", getattr(yt, 'description', None))
|
||||
print("Arguments:", getattr(yt, 'args', None))
|
||||
|
||||
# Wikipedia Tool Info
|
||||
print("\nWikipedia Tool:")
|
||||
wiki = WikipediaAPIWrapper()
|
||||
print("Type:", type(wiki))
|
||||
print("Available Settings:")
|
||||
print("- Language:", wiki.lang)
|
||||
print("- Top K Results:", wiki.top_k_results)
|
||||
print("- Max Characters:", wiki.doc_content_chars_max)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_tool_info()
|
||||
test_tools()
|
||||
Binary file not shown.
@@ -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)
|
||||
Whitespace-only changes.
@@ -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()
|
||||
@@ -0,0 +1,45 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Detailed LLM configuration
|
||||
llm_config = {
|
||||
"model": "gemini/gemini-1.5-flash-latest", # Model name without provider prefix
|
||||
|
||||
# Core settings
|
||||
"temperature": 0.7, # Controls randomness (like temperature)
|
||||
"timeout": 30, # Timeout in seconds
|
||||
"top_p": 0.9, # Nucleus sampling parameter
|
||||
"max_tokens": 1000, # Max tokens in response
|
||||
|
||||
# Advanced parameters
|
||||
"presence_penalty": 0.1, # Penalize repetition of topics (-2.0 to 2.0)
|
||||
"frequency_penalty": 0.1, # Penalize token repetition (-2.0 to 2.0)
|
||||
|
||||
# API settings (optional)
|
||||
"api_key": None, # Your API key (or use environment variable)
|
||||
"base_url": None, # Custom API endpoint if needed
|
||||
|
||||
# Response formatting
|
||||
"response_format": { # Force specific response format
|
||||
"type": "text" # Options: "text", "json_object"
|
||||
},
|
||||
|
||||
# Additional controls
|
||||
"seed": 42, # For reproducible responses
|
||||
"stop_phrases": ["##", "END"], # Custom stop sequences
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful Assistant specialized in scientific explanations. "
|
||||
"Provide clear, accurate, and engaging responses.",
|
||||
llm=llm_config, # Pass the detailed configuration
|
||||
verbose=True, # Enable detailed output
|
||||
markdown=True, # Format responses in markdown
|
||||
self_reflect=True, # Enable self-reflection
|
||||
max_reflect=3, # Maximum reflection iterations
|
||||
min_reflect=1 # Minimum reflection iterations
|
||||
)
|
||||
|
||||
# Test the agent
|
||||
response = agent.start("Why is the sky blue? Please explain in simple terms.")
|
||||
|
||||
print(response)
|
||||
@@ -0,0 +1,10 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gemini/gemini-1.5-flash-8b",
|
||||
self_reflect=True,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
agent.start("Why sky is Blue?")
|
||||
@@ -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()
|
||||
@@ -0,0 +1,4 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(instructions="You are a Markdown Agent, output in markdown format")
|
||||
agent.start("Write a blog post about AI")
|
||||
@@ -0,0 +1,5 @@
|
||||
from markitdown import MarkItDown
|
||||
|
||||
md = MarkItDown()
|
||||
result = md.convert("sample.txt")
|
||||
print(result.text_content)
|
||||
@@ -0,0 +1,220 @@
|
||||
from mem0 import Memory
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MemoryManager:
|
||||
def __init__(self):
|
||||
"""Initialize Mem0 Memory manager with configuration"""
|
||||
# Define the complete configuration with only supported fields
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"custom_prompt": None,
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": os.path.abspath("./.praison/storage"),
|
||||
"host": None,
|
||||
"port": None
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"embedding_dims": 1536
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0,
|
||||
"max_tokens": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure storage directory exists
|
||||
storage_path = os.path.abspath("./.praison/storage")
|
||||
if os.path.exists(storage_path):
|
||||
logger.info(f"Clearing existing storage at {storage_path}")
|
||||
import shutil
|
||||
shutil.rmtree(storage_path)
|
||||
os.makedirs(storage_path, exist_ok=True)
|
||||
logger.info(f"Created storage path: {storage_path}")
|
||||
|
||||
# Initialize memory
|
||||
self.memory = Memory.from_config(config)
|
||||
logger.info("Memory manager initialized")
|
||||
|
||||
def pretty_print(self, data: Any) -> None:
|
||||
"""Helper function to print memory data in a readable format"""
|
||||
if isinstance(data, (dict, list)):
|
||||
print(json.dumps(data, indent=2))
|
||||
else:
|
||||
print(data)
|
||||
|
||||
def extract_memory_id(self, result: Any) -> Optional[str]:
|
||||
"""Extract memory ID from the result, handling different formats"""
|
||||
try:
|
||||
if isinstance(result, dict):
|
||||
# v1.1 format
|
||||
memories = result.get('results', [])
|
||||
return memories[0]['id'] if memories else None
|
||||
elif isinstance(result, list):
|
||||
# v1.0 format
|
||||
return result[0]['id'] if result else None
|
||||
return None
|
||||
except (KeyError, IndexError) as e:
|
||||
logger.warning(f"Could not extract memory ID from result: {e}")
|
||||
return None
|
||||
|
||||
def prepare_metadata(self, metadata: Optional[Dict]) -> Dict:
|
||||
"""Prepare metadata by converting values to acceptable types"""
|
||||
if not metadata:
|
||||
return {}
|
||||
|
||||
clean_metadata = {}
|
||||
for key, value in metadata.items():
|
||||
if isinstance(value, (str, int, float, bool)):
|
||||
clean_metadata[key] = value
|
||||
elif isinstance(value, list):
|
||||
clean_metadata[key] = ','.join(map(str, value))
|
||||
elif isinstance(value, dict):
|
||||
clean_metadata[key] = json.dumps(value)
|
||||
else:
|
||||
clean_metadata[key] = str(value)
|
||||
return clean_metadata
|
||||
|
||||
def verify_memory_addition(self, user_id: str, max_retries: int = 3) -> bool:
|
||||
"""Verify that memory was added successfully"""
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
time.sleep(0.5) # Brief pause to allow indexing
|
||||
memories = self.memory.get_all(user_id=user_id)
|
||||
if len(memories.get('results', [])) > 0:
|
||||
return True
|
||||
logger.warning(f"Memory not found on attempt {attempt + 1}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Verification attempt {attempt + 1} failed: {e}")
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
def add_memory(self, text: str, user_id: Optional[str] = None, metadata: Optional[Dict] = None) -> Dict:
|
||||
"""Add a new memory with error handling and verification"""
|
||||
try:
|
||||
logger.info(f"Adding memory for user {user_id}")
|
||||
clean_metadata = self.prepare_metadata(metadata)
|
||||
logger.debug(f"Prepared metadata: {clean_metadata}")
|
||||
|
||||
messages = [{"role": "user", "content": text}]
|
||||
result = self.memory.add(
|
||||
messages=messages,
|
||||
user_id=user_id,
|
||||
metadata=clean_metadata
|
||||
)
|
||||
|
||||
if self.verify_memory_addition(user_id):
|
||||
logger.info("Memory added and verified successfully")
|
||||
else:
|
||||
logger.warning("Memory addition could not be verified")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add memory: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_all_memories(self, user_id: Optional[str] = None) -> Dict:
|
||||
"""Retrieve all memories for a user"""
|
||||
try:
|
||||
logger.info(f"Retrieving all memories for user {user_id}")
|
||||
memories = self.memory.get_all(user_id=user_id)
|
||||
count = len(memories.get('results', []))
|
||||
logger.info(f"Retrieved {count} memories")
|
||||
return memories
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to retrieve memories: {str(e)}")
|
||||
raise
|
||||
|
||||
def search_memories(self, query: str, user_id: Optional[str] = None, limit: int = 5) -> Dict:
|
||||
"""Search memories with a query"""
|
||||
try:
|
||||
logger.info(f"Searching memories with query: {query}")
|
||||
results = self.memory.search(
|
||||
query=query,
|
||||
user_id=user_id,
|
||||
limit=limit
|
||||
)
|
||||
count = len(results.get('results', []))
|
||||
logger.info(f"Found {count} matching memories")
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Search failed: {str(e)}")
|
||||
raise
|
||||
|
||||
def main():
|
||||
try:
|
||||
# Initialize memory manager
|
||||
manager = MemoryManager()
|
||||
|
||||
# Add a test memory
|
||||
print("\n1. Adding a new memory:")
|
||||
metadata = {
|
||||
"category": "hobbies",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"tags": "tennis,learning",
|
||||
"priority": 1,
|
||||
"source": "user_input"
|
||||
}
|
||||
|
||||
result = manager.add_memory(
|
||||
text="I am working on improving my tennis skills. Suggest some online courses.",
|
||||
user_id="alice",
|
||||
metadata=metadata
|
||||
)
|
||||
manager.pretty_print(result)
|
||||
|
||||
# Wait briefly for indexing
|
||||
time.sleep(1)
|
||||
|
||||
print("\n2. Retrieving all memories:")
|
||||
all_memories = manager.get_all_memories(user_id="alice")
|
||||
manager.pretty_print(all_memories)
|
||||
|
||||
print("\n3. Searching memories:")
|
||||
search_results = manager.search_memories(
|
||||
query="What are Alice's hobbies?",
|
||||
user_id="alice",
|
||||
limit=5
|
||||
)
|
||||
manager.pretty_print(search_results)
|
||||
|
||||
# Print summary
|
||||
print("\nMemory Store Summary:")
|
||||
total_memories = len(all_memories.get('results', []))
|
||||
print(f"Total memories stored: {total_memories}")
|
||||
if total_memories > 0:
|
||||
print("Available memories:")
|
||||
for mem in all_memories['results']:
|
||||
print(f"- {mem.get('memory', 'No content')}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Main process failed: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,143 @@
|
||||
from mem0 import Memory
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MemorySearcher:
|
||||
def __init__(self):
|
||||
"""Initialize Mem0 Memory searcher with configuration"""
|
||||
# Define the configuration
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": os.path.abspath("./.praison/storage"),
|
||||
"host": None,
|
||||
"port": None
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small",
|
||||
"embedding_dims": 1536
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0,
|
||||
"max_tokens": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize memory
|
||||
self.memory = Memory.from_config(config)
|
||||
logger.info("Memory searcher initialized")
|
||||
|
||||
def pretty_print(self, data: Any) -> None:
|
||||
"""Helper function to print data in a readable format"""
|
||||
if isinstance(data, (dict, list)):
|
||||
print(json.dumps(data, indent=2))
|
||||
else:
|
||||
print(data)
|
||||
|
||||
def get_all_memories(self, user_id: Optional[str] = None) -> Dict:
|
||||
"""Retrieve all memories for a user"""
|
||||
try:
|
||||
logger.info(f"Retrieving all memories for user {user_id}")
|
||||
memories = self.memory.get_all(user_id=user_id)
|
||||
count = len(memories.get('results', []))
|
||||
logger.info(f"Retrieved {count} memories")
|
||||
return memories
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to retrieve memories: {str(e)}")
|
||||
raise
|
||||
|
||||
def search_memories(self, query: str, user_id: Optional[str] = None, limit: int = 5) -> Dict:
|
||||
"""Search memories with a query"""
|
||||
try:
|
||||
logger.info(f"Searching memories with query: {query}")
|
||||
results = self.memory.search(
|
||||
query=query,
|
||||
user_id=user_id,
|
||||
limit=limit
|
||||
)
|
||||
count = len(results.get('results', []))
|
||||
logger.info(f"Found {count} matching memories")
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Search failed: {str(e)}")
|
||||
raise
|
||||
|
||||
def list_unique_users(self) -> list:
|
||||
"""List all unique users in the memory store"""
|
||||
try:
|
||||
all_memories = self.memory.get_all()
|
||||
users = set()
|
||||
for memory in all_memories.get('results', []):
|
||||
if 'user_id' in memory:
|
||||
users.add(memory['user_id'])
|
||||
return sorted(list(users))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list users: {str(e)}")
|
||||
raise
|
||||
|
||||
def main():
|
||||
try:
|
||||
# Initialize memory searcher
|
||||
searcher = MemorySearcher()
|
||||
|
||||
# Get list of users
|
||||
print("\n1. Available users in the memory store:")
|
||||
users = searcher.list_unique_users()
|
||||
searcher.pretty_print(users)
|
||||
|
||||
if not users:
|
||||
print("No users found in the memory store. Please add some memories first.")
|
||||
return
|
||||
|
||||
# Example user (using first user found)
|
||||
example_user = users[0]
|
||||
|
||||
print(f"\n2. All memories for user '{example_user}':")
|
||||
all_memories = searcher.get_all_memories(user_id=example_user)
|
||||
searcher.pretty_print(all_memories)
|
||||
|
||||
print(f"\n3. Searching memories for user '{example_user}':")
|
||||
search_results = searcher.search_memories(
|
||||
query="What are their hobbies?",
|
||||
user_id=example_user,
|
||||
limit=5
|
||||
)
|
||||
searcher.pretty_print(search_results)
|
||||
|
||||
# Print summary
|
||||
print("\nMemory Store Summary:")
|
||||
total_memories = len(all_memories.get('results', []))
|
||||
print(f"Total memories for {example_user}: {total_memories}")
|
||||
if total_memories > 0:
|
||||
print("Available memories:")
|
||||
for mem in all_memories['results']:
|
||||
print(f"- {mem.get('memory', 'No content')}")
|
||||
if 'metadata' in mem:
|
||||
print(f" Metadata: {mem['metadata']}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Main process failed: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,26 @@
|
||||
from mem0 import Memory
|
||||
import os
|
||||
import json
|
||||
|
||||
# Basic configuration
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": ".praison",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize memory
|
||||
memory = Memory.from_config(config)
|
||||
|
||||
# Search Alice's hobbies
|
||||
search_results = memory.search(
|
||||
query="KAG",
|
||||
user_id="user1",
|
||||
limit=5
|
||||
)
|
||||
|
||||
print(search_results)
|
||||
@@ -0,0 +1,46 @@
|
||||
from mem0 import Memory
|
||||
import os
|
||||
import json
|
||||
|
||||
# Basic configuration
|
||||
config = {
|
||||
"version": "v1.1",
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": "praison",
|
||||
"path": os.path.abspath("./.praison/storage")
|
||||
}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small"
|
||||
}
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "gpt-4o-mini"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize memory
|
||||
memory = Memory.from_config(config)
|
||||
|
||||
# Search Alice's hobbies
|
||||
search_results = memory.search(
|
||||
query="What are Alice's hobbies?",
|
||||
user_id="alice",
|
||||
limit=5
|
||||
)
|
||||
|
||||
# Print results in a readable format
|
||||
print("\nAlice's Memories:")
|
||||
for memory in search_results['results']:
|
||||
print(f"\nMemory: {memory['memory']}")
|
||||
print(f"Score: {memory['score']}")
|
||||
if 'metadata' in memory:
|
||||
print("Metadata:")
|
||||
print(json.dumps(memory['metadata'], indent=2))
|
||||
@@ -0,0 +1,183 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import logging
|
||||
import os
|
||||
|
||||
def main():
|
||||
# Initialize memory config
|
||||
memory_config = {
|
||||
"provider": "rag",
|
||||
"use_embedding": True,
|
||||
"storage": {
|
||||
"type": "sqlite",
|
||||
"path": "./.praison/memory.db"
|
||||
},
|
||||
"rag_db_path": "./.praison/chroma_db"
|
||||
}
|
||||
|
||||
# Test facts
|
||||
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
|
||||
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
|
||||
fact3 = "The year the first Josinga was released is 2007"
|
||||
|
||||
# # Check if database exists
|
||||
# if os.path.exists("./memory.db"):
|
||||
# logger.info("Found existing memory database")
|
||||
# else:
|
||||
# logger.info("Creating new memory database")
|
||||
|
||||
# Create task config (without memory config since it's moved to PraisonAIAgents)
|
||||
task_config = {}
|
||||
|
||||
# Create agents with different roles
|
||||
researcher = Agent(
|
||||
role="Research Analyst",
|
||||
goal="Research and document key information about topics",
|
||||
backstory="Expert at analyzing and storing information in memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
retriever = Agent(
|
||||
role="Information Retriever",
|
||||
goal="Retrieve and verify stored information from memory",
|
||||
backstory="Specialist in searching and validating information from memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Task 1: Process the facts
|
||||
store_task = Task(
|
||||
description=f"""
|
||||
Process and analyze this information:
|
||||
1. {fact1}
|
||||
2. {fact2}
|
||||
3. {fact3}
|
||||
|
||||
Provide a clear summary of each fact.
|
||||
""",
|
||||
expected_output="""
|
||||
Clear statements summarizing each fact.
|
||||
Example format:
|
||||
1. [Summary of fact 1]
|
||||
2. [Summary of fact 2]
|
||||
3. [Summary of fact 3]
|
||||
""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Task 2: Write essay about AI
|
||||
verify_task = Task(
|
||||
description="""
|
||||
write few points about AI
|
||||
""",
|
||||
expected_output="Points about AI",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Task 3: Query memory
|
||||
query_task = Task(
|
||||
description="""
|
||||
Using ONLY information found in memory:
|
||||
1. What is stored in memory about Hahanu?
|
||||
2. What ingredients for proloder are recorded in memory?
|
||||
3. What year is stored in memory for the Josinga release?
|
||||
|
||||
For each answer, cite the memory record you found.
|
||||
""",
|
||||
expected_output="Answers based solely on memory records with citations",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Task 4: Query both short-term and long-term memory
|
||||
query_both_task = Task(
|
||||
description="""
|
||||
Using ONLY information found in memory:
|
||||
1. What is stored in both short-term and long-term memory about Jujuha?
|
||||
2. What ingredients for proloder are recorded in both short-term and long-term memory?
|
||||
3. What year is stored in both short-term and long-term memory for the Josinga release?
|
||||
|
||||
For each answer, cite the memory record you found.
|
||||
""",
|
||||
expected_output="Answers based solely on memory records with citations",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Initialize PraisonAIAgents with memory configuration
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, retriever],
|
||||
tasks=[store_task, verify_task, query_task, query_both_task],
|
||||
verbose=True, # Use same verbose level as memory
|
||||
memory=True,
|
||||
embedder={
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# agents = PraisonAIAgents(
|
||||
# agents=[researcher, retriever],
|
||||
# tasks=[store_task, verify_task, query_task, query_both_task],
|
||||
# verbose=True, # Use same verbose level as memory
|
||||
# memory=True
|
||||
# )
|
||||
|
||||
# Execute tasks
|
||||
print("\nExecuting Memory Test Tasks...")
|
||||
print("-" * 50)
|
||||
agents.start()
|
||||
|
||||
# Use shared memory for final checks
|
||||
memory = agents.shared_memory
|
||||
|
||||
# Test memory retrieval with different quality thresholds
|
||||
if memory:
|
||||
print("\nFinal Memory Check:")
|
||||
print("-" * 50)
|
||||
|
||||
queries = ["Jujuha", "proloder", "Josinga"]
|
||||
for query in queries:
|
||||
print(f"\nSearching memory for: {query}")
|
||||
|
||||
# Search in both short-term and long-term memory
|
||||
print("\nShort-term memory results:")
|
||||
stm_results = memory.search_short_term(query)
|
||||
if stm_results:
|
||||
for item in stm_results:
|
||||
print(f"Content: {item.get('content', '')[:200]}")
|
||||
if 'meta' in item:
|
||||
print(f"Metadata: {item['meta']}")
|
||||
print("-" * 20)
|
||||
else:
|
||||
print("No results found in short-term memory")
|
||||
|
||||
print("\nLong-term memory results:")
|
||||
ltm_results = memory.search_long_term(query)
|
||||
if ltm_results:
|
||||
for item in ltm_results:
|
||||
print(f"Content: {item.get('text', '')[:200]}")
|
||||
if 'metadata' in item:
|
||||
print(f"Metadata: {item['metadata']}")
|
||||
print("-" * 20)
|
||||
else:
|
||||
print("No results found in long-term memory")
|
||||
|
||||
# Also check ChromaDB if using RAG
|
||||
if memory.use_rag and hasattr(memory, "chroma_col"):
|
||||
print("\nChromaDB results:")
|
||||
try:
|
||||
all_items = memory.chroma_col.get()
|
||||
print(f"Found {len(all_items['ids'])} items in ChromaDB")
|
||||
for i in range(len(all_items['ids'])):
|
||||
print(f"ID: {all_items['ids'][i]}")
|
||||
print(f"Content: {all_items['documents'][i][:200]}")
|
||||
print(f"Metadata: {all_items['metadatas'][i]}")
|
||||
print("-" * 20)
|
||||
except Exception as e:
|
||||
print(f"Error querying ChromaDB: {e}")
|
||||
|
||||
print("-" * 30)
|
||||
else:
|
||||
print("\nNo memory available for final checks")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
from praisonaiagents.agents.agents import Agent, Task, PraisonAIAgents
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
# Test facts
|
||||
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
|
||||
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
|
||||
fact3 = "The year the first Josinga was released is 2007"
|
||||
|
||||
fact_agent = Agent(
|
||||
name="Fact Agent",
|
||||
instructions="You are a fact agent, you store and retrieve facts in memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
research_agent = Agent(
|
||||
name="Research Agent",
|
||||
instructions="You are a research analyst, you research and document key points about topics",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
blog_agent = Agent(
|
||||
name="Blog Agent",
|
||||
instructions="You are a blog writer, you write a blog post about the research",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
fact_task = Task(
|
||||
description="Store the following facts in memory: " + fact1 + ", " + fact2 + ", " + fact3,
|
||||
agent=fact_agent
|
||||
)
|
||||
|
||||
research_task = Task(
|
||||
description="Research and document 2 key points about AI",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task2 = Task(
|
||||
description="Research and document 2 key points about AI in healthcare",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task3 = Task(
|
||||
description="Research and document 2 key points about AI in education",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task4 = Task(
|
||||
description="Research and document 2 key points about AI in finance",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
blog_task = Task(
|
||||
description="Write a blog post about Jujuha",
|
||||
agent=blog_agent
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[fact_agent, research_agent, blog_agent],
|
||||
tasks=[fact_task, research_task, research_task2, research_task3, research_task4, blog_task],
|
||||
memory=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,6 @@
|
||||
from praisonaiagents import Agent, Agents, Tools
|
||||
|
||||
research_agent = Agent(instructions="You are a research agent to search internet about AI 2024", tools=[Tools.internet_search])
|
||||
summarise_agent = Agent(instructions="You are a summarize agent to summarise in points")
|
||||
agents = Agents(agents=[research_agent, summarise_agent])
|
||||
agents.start()
|
||||
@@ -51,9 +51,4 @@ agents = PraisonAIAgents(
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
result = agents.start()
|
||||
|
||||
# Print results
|
||||
for task_id, task_result in result["task_results"].items():
|
||||
print(f"\nTask {task_id} Result:")
|
||||
print(task_result.raw)
|
||||
agents.start()
|
||||
@@ -0,0 +1,111 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_time_check():
|
||||
current_time = int(time.time())
|
||||
if current_time % 3 == 0:
|
||||
result = 1
|
||||
elif current_time % 3 == 1:
|
||||
result = 2
|
||||
else:
|
||||
result = 3
|
||||
print(f"Time: {current_time}, Result: {result}")
|
||||
return result
|
||||
|
||||
# Create orchestrator and worker agents
|
||||
router = Agent(
|
||||
name="Router",
|
||||
role="Task router",
|
||||
goal="Distribute tasks to based on response from get_time_check",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
worker1 = Agent(
|
||||
name="Worker 1",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 1",
|
||||
)
|
||||
|
||||
worker2 = Agent(
|
||||
name="Worker 2",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 2",
|
||||
)
|
||||
|
||||
worker3 = Agent(
|
||||
name="Worker 3",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 3",
|
||||
)
|
||||
|
||||
synthesizer = Agent(
|
||||
name="Synthesizer",
|
||||
role="Result synthesizer",
|
||||
goal="Combine and synthesize worker outputs",
|
||||
)
|
||||
|
||||
# Create orchestrated workflow tasks
|
||||
router_task = Task(
|
||||
name="route_task",
|
||||
description="Analyze input from get_time_check and route to appropriate workers",
|
||||
expected_output="Task routing decision, 1 , 2 or 3",
|
||||
agent=router,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["worker1_task", "worker2_task", "worker3_task"],
|
||||
condition={
|
||||
"1": ["worker1_task"],
|
||||
"2": ["worker2_task"],
|
||||
"3": ["worker3_task"]
|
||||
}
|
||||
)
|
||||
|
||||
worker1_task = Task(
|
||||
name="worker1_task",
|
||||
description="Process type 1 operation",
|
||||
expected_output="Worker 1 result",
|
||||
agent=worker1,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
worker2_task = Task(
|
||||
name="worker2_task",
|
||||
description="Process type 2 operation",
|
||||
expected_output="Worker 2 result",
|
||||
agent=worker2,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
worker3_task = Task(
|
||||
name="worker3_task",
|
||||
description="Process type 3 operation",
|
||||
expected_output="Worker 3 result",
|
||||
agent=worker3,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
synthesize_task = Task(
|
||||
name="synthesize_task",
|
||||
description="Synthesize worker results into final output",
|
||||
expected_output="Final synthesized result",
|
||||
agent=synthesizer,
|
||||
context=[worker1_task, worker2_task, worker3_task]
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[router, worker1, worker2, worker3, synthesizer],
|
||||
tasks=[router_task, worker1_task, worker2_task, worker3_task, synthesize_task],
|
||||
process="workflow",
|
||||
verbose=True,
|
||||
max_iter=10
|
||||
)
|
||||
|
||||
# Run orchestrated workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nOrchestrator-Workers Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,95 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
def process_time():
|
||||
"""Simulate processing"""
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"Processing at: {current_time}")
|
||||
return f"Processed at {current_time}"
|
||||
|
||||
# Create parallel processing agents
|
||||
agent1 = Agent(
|
||||
name="Processor 1",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
agent2 = Agent(
|
||||
name="Processor 2",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
agent3 = Agent(
|
||||
name="Processor 3",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
aggregator = Agent(
|
||||
name="Aggregator",
|
||||
role="Result aggregator",
|
||||
goal="Collect all the processed time from all tasks"
|
||||
)
|
||||
|
||||
# Create parallel tasks with memory disabled
|
||||
task1 = Task(
|
||||
name="process_1",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent1,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="process_2",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent2,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
task3 = Task(
|
||||
name="process_3",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent3,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
aggregate_task = Task(
|
||||
name="aggregate",
|
||||
description="Collect all the processed time from all tasks",
|
||||
expected_output="Output all the processed time from all tasks and just the time",
|
||||
agent=aggregator,
|
||||
context=[task1, task2, task3]
|
||||
)
|
||||
|
||||
async def main():
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[agent1, agent2, agent3, aggregator],
|
||||
tasks=[task1, task2, task3, aggregate_task],
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
# Run parallel workflow
|
||||
results = await workflow.astart()
|
||||
|
||||
# Print results
|
||||
print("\nParallel Processing Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
|
||||
# Run the async main function
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,41 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create PDF Analysis Agent
|
||||
pdf_agent = Agent(
|
||||
name="PDFAnalyst",
|
||||
role="PDF Document Specialist",
|
||||
goal="Analyze PDF documents to extract meaningful information",
|
||||
backstory="""You are an expert in PDF document analysis and text extraction.
|
||||
You excel at understanding document structure, extracting content, and analyzing textual information.""",
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 1. Task with PDF URL
|
||||
task1 = Task(
|
||||
name="analyze_pdf_url",
|
||||
description="Extract and analyze content from this PDF document.",
|
||||
expected_output="Detailed analysis of the PDF content and structure",
|
||||
agent=pdf_agent,
|
||||
input=["https://example.com/document.pdf"]
|
||||
)
|
||||
|
||||
# 2. Task with Local PDF File
|
||||
task2 = Task(
|
||||
name="analyze_local_pdf",
|
||||
description="What information can you extract from this PDF? Analyze its content.",
|
||||
expected_output="Detailed analysis of the PDF content and structure",
|
||||
agent=pdf_agent,
|
||||
input=["document.pdf"]
|
||||
)
|
||||
|
||||
# Create PraisonAIAgents instance
|
||||
agents = PraisonAIAgents(
|
||||
agents=[pdf_agent],
|
||||
tasks=[task1, task2],
|
||||
process="sequential",
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
agents.start()
|
||||
@@ -0,0 +1,5 @@
|
||||
from praisonaiagents import Agent, Tools
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agent = Agent(instructions="You are a Planning Agent", tools=[duckduckgo])
|
||||
agent.start("I want to go London next week, find me a good hotel and flight")
|
||||
@@ -5,6 +5,10 @@ Praison AI Agents - A package for hierarchical AI agent task execution
|
||||
from .agent.agent import Agent
|
||||
from .agents.agents import PraisonAIAgents
|
||||
from .task.task import Task
|
||||
from .tools.tools import Tools
|
||||
from .agents.autoagents import AutoAgents
|
||||
from .knowledge.knowledge import Knowledge
|
||||
from .knowledge.chunking import Chunking
|
||||
from .main import (
|
||||
TaskOutput,
|
||||
ReflectionOutput,
|
||||
@@ -21,12 +25,18 @@ from .main import (
|
||||
async_display_callbacks,
|
||||
)
|
||||
|
||||
# Add Agents as an alias for PraisonAIAgents
|
||||
Agents = PraisonAIAgents
|
||||
|
||||
__all__ = [
|
||||
'Agent',
|
||||
'PraisonAIAgents',
|
||||
'Agents',
|
||||
'Tools',
|
||||
'Task',
|
||||
'TaskOutput',
|
||||
'ReflectionOutput',
|
||||
'AutoAgents',
|
||||
'display_interaction',
|
||||
'display_self_reflection',
|
||||
'display_instruction',
|
||||
@@ -38,4 +48,6 @@ __all__ = [
|
||||
'register_display_callback',
|
||||
'sync_display_callbacks',
|
||||
'async_display_callbacks',
|
||||
'Knowledge',
|
||||
'Chunking'
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,5 @@
|
||||
"""Agents module for managing multiple AI agents"""
|
||||
from .agents import PraisonAIAgents
|
||||
from .autoagents import AutoAgents
|
||||
|
||||
__all__ = ['PraisonAIAgents']
|
||||
__all__ = ['PraisonAIAgents', 'AutoAgents']
|
||||
@@ -12,6 +12,10 @@ from ..agent.agent import Agent
|
||||
from ..task.task import Task
|
||||
from ..process.process import Process, LoopItems
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def encode_file_to_base64(file_path: str) -> str:
|
||||
"""Base64-encode a file."""
|
||||
@@ -41,7 +45,29 @@ def process_video(video_path: str, seconds_per_frame=2):
|
||||
return base64_frames
|
||||
|
||||
class PraisonAIAgents:
|
||||
def __init__(self, agents, tasks, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None):
|
||||
def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None, max_iter=10):
|
||||
# Add check at the start if memory is requested
|
||||
if memory:
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
MEMORY_AVAILABLE = True
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Memory features requested but memory dependencies not installed. "
|
||||
"Please install with: pip install \"praisonaiagents[memory]\""
|
||||
)
|
||||
|
||||
if not agents:
|
||||
raise ValueError("At least one agent must be provided")
|
||||
|
||||
self.run_id = str(uuid.uuid4()) # Auto-generate run_id
|
||||
self.user_id = user_id or "praison" # Optional user_id
|
||||
self.max_iter = max_iter # Add max_iter parameter
|
||||
|
||||
# Pass user_id to each agent
|
||||
for agent in agents:
|
||||
agent.user_id = self.user_id
|
||||
|
||||
self.agents = agents
|
||||
self.tasks = {}
|
||||
if max_retries < 3:
|
||||
@@ -51,13 +77,111 @@ class PraisonAIAgents:
|
||||
self.verbose = verbose
|
||||
self.max_retries = max_retries
|
||||
self.process = process
|
||||
if not manager_llm:
|
||||
logging.debug("No manager_llm provided. Using OPENAI_MODEL_NAME environment variable or defaulting to 'gpt-4o'")
|
||||
self.manager_llm = manager_llm if manager_llm else os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
|
||||
# Check for manager_llm in environment variable if not provided
|
||||
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
|
||||
# Set logger level based on verbose
|
||||
if verbose >= 5:
|
||||
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)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
|
||||
if self.verbose:
|
||||
logger.info(f"Using model {self.manager_llm} for manager")
|
||||
|
||||
# If no tasks provided, generate them from agents
|
||||
if tasks is None:
|
||||
tasks = []
|
||||
for agent in self.agents:
|
||||
task = agent.generate_task()
|
||||
tasks.append(task)
|
||||
logger.info(f"Auto-generated {len(tasks)} tasks from agents")
|
||||
else:
|
||||
# Validate tasks for backward compatibility
|
||||
if not tasks:
|
||||
raise ValueError("If tasks are provided, at least one task must be present")
|
||||
logger.info(f"Using {len(tasks)} provided tasks")
|
||||
|
||||
# Add tasks and set their status
|
||||
for task in tasks:
|
||||
self.add_task(task)
|
||||
task.status = "not started"
|
||||
|
||||
# If tasks were auto-generated from agents or process is sequential, set up sequential flow
|
||||
if len(tasks) > 1 and (process == "sequential" or all(task.next_tasks == [] for task in tasks)):
|
||||
for i in range(len(tasks) - 1):
|
||||
# Set up next task relationship
|
||||
tasks[i].next_tasks = [tasks[i + 1].name]
|
||||
# Set up context for the next task to include the current task
|
||||
if tasks[i + 1].context is None:
|
||||
tasks[i + 1].context = []
|
||||
tasks[i + 1].context.append(tasks[i])
|
||||
logger.info("Set up sequential flow with automatic context passing")
|
||||
|
||||
self._state = {} # Add state storage at PraisonAIAgents level
|
||||
|
||||
# Initialize memory system
|
||||
self.shared_memory = None
|
||||
if memory:
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
|
||||
# Get memory config from parameter or first task
|
||||
mem_cfg = memory_config
|
||||
if not mem_cfg:
|
||||
mem_cfg = next((t.config.get('memory_config') for t in tasks if hasattr(t, 'config') and t.config), None)
|
||||
|
||||
# Set default memory config if none provided
|
||||
if not mem_cfg:
|
||||
mem_cfg = {
|
||||
"provider": "rag",
|
||||
"use_embedding": True,
|
||||
"storage": {
|
||||
"type": "sqlite",
|
||||
"path": "./.praison/memory.db"
|
||||
},
|
||||
"rag_db_path": "./.praison/chroma_db"
|
||||
}
|
||||
|
||||
# Add embedder config if provided
|
||||
if embedder:
|
||||
if isinstance(embedder, dict):
|
||||
mem_cfg = mem_cfg or {}
|
||||
mem_cfg["embedder"] = embedder
|
||||
else:
|
||||
# Handle direct embedder function
|
||||
mem_cfg = mem_cfg or {}
|
||||
mem_cfg["embedder_function"] = embedder
|
||||
|
||||
if mem_cfg:
|
||||
# Pass verbose level to Memory
|
||||
self.shared_memory = Memory(config=mem_cfg, verbose=verbose)
|
||||
if verbose >= 5:
|
||||
logger.info("Initialized shared memory for PraisonAIAgents")
|
||||
|
||||
# Distribute memory to tasks
|
||||
for task in tasks:
|
||||
if not task.memory:
|
||||
task.memory = self.shared_memory
|
||||
if verbose >= 5:
|
||||
logger.info(f"Assigned shared memory to task {task.id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize shared memory: {e}")
|
||||
|
||||
# Update tasks with shared memory
|
||||
if self.shared_memory:
|
||||
for task in tasks:
|
||||
if not task.memory:
|
||||
task.memory = self.shared_memory
|
||||
logger.info(f"Assigned shared memory to task {task.id}")
|
||||
|
||||
def add_task(self, task):
|
||||
task_id = self.task_id_counter
|
||||
@@ -107,26 +231,62 @@ class PraisonAIAgents:
|
||||
|
||||
executor_agent = task.agent
|
||||
|
||||
# Ensure tools are available from both task and agent
|
||||
tools = task.tools or []
|
||||
if executor_agent and executor_agent.tools:
|
||||
tools.extend(executor_agent.tools)
|
||||
|
||||
task_prompt = f"""
|
||||
You need to do the following task: {task.description}.
|
||||
Expected Output: {task.expected_output}.
|
||||
"""
|
||||
"""
|
||||
if task.context:
|
||||
context_results = ""
|
||||
for context_task in task.context:
|
||||
if context_task.result:
|
||||
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
|
||||
else:
|
||||
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
|
||||
context_results = [] # Use list to avoid duplicates
|
||||
for context_item in task.context:
|
||||
if isinstance(context_item, str):
|
||||
context_results.append(f"Input Content:\n{context_item}")
|
||||
elif isinstance(context_item, list):
|
||||
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
|
||||
elif hasattr(context_item, 'result'): # Task object
|
||||
if context_item.result:
|
||||
context_results.append(
|
||||
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
|
||||
)
|
||||
else:
|
||||
context_results.append(
|
||||
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
|
||||
)
|
||||
elif isinstance(context_item, dict) and "vector_store" in context_item:
|
||||
from ..knowledge.knowledge import Knowledge
|
||||
try:
|
||||
# Handle both string and dict configs
|
||||
cfg = context_item["vector_store"]
|
||||
if isinstance(cfg, str):
|
||||
cfg = json.loads(cfg)
|
||||
|
||||
knowledge = Knowledge(config={"vector_store": cfg}, verbose=self.verbose)
|
||||
|
||||
# Only use user_id as filter
|
||||
db_results = knowledge.search(
|
||||
task.description,
|
||||
user_id=self.user_id if self.user_id else None
|
||||
)
|
||||
context_results.append(f"[DB Context]: {str(db_results)}")
|
||||
except Exception as e:
|
||||
context_results.append(f"[Vector DB Error]: {e}")
|
||||
|
||||
# Join unique context results
|
||||
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
|
||||
task_prompt += f"""
|
||||
Here are the results of previous tasks that might be useful:\n
|
||||
{context_results}
|
||||
"""
|
||||
Context:
|
||||
|
||||
{' '.join(unique_contexts)}
|
||||
"""
|
||||
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
|
||||
|
||||
if self.verbose >= 2:
|
||||
logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
@@ -163,10 +323,17 @@ Expected Output: {task.expected_output}.
|
||||
|
||||
agent_output = await executor_agent.achat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
tools=task.tools
|
||||
tools=tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
else:
|
||||
agent_output = await executor_agent.achat(task_prompt, tools=task.tools)
|
||||
agent_output = await executor_agent.achat(
|
||||
task_prompt,
|
||||
tools=tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
|
||||
if agent_output:
|
||||
task_output = TaskOutput(
|
||||
@@ -184,8 +351,8 @@ Expected Output: {task.expected_output}.
|
||||
task_output.json_dict = parsed
|
||||
task_output.output_format = "JSON"
|
||||
except:
|
||||
logging.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logging.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logger.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
|
||||
if task.output_pydantic:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
@@ -195,8 +362,8 @@ Expected Output: {task.expected_output}.
|
||||
task_output.pydantic = pyd_obj
|
||||
task_output.output_format = "Pydantic"
|
||||
except:
|
||||
logging.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logging.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
|
||||
task.result = task_output
|
||||
return task_output
|
||||
@@ -211,37 +378,59 @@ Expected Output: {task.expected_output}.
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
if task.status == "completed":
|
||||
logging.info(f"Task with ID {task_id} is already completed")
|
||||
logger.info(f"Task with ID {task_id} is already completed")
|
||||
return
|
||||
|
||||
retries = 0
|
||||
while task.status != "completed" and retries < self.max_retries:
|
||||
logging.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
logger.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
if task.status in ["not started", "in progress"]:
|
||||
task_output = await self.aexecute_task(task_id)
|
||||
if task_output and self.completion_checker(task, task_output.raw):
|
||||
task.status = "completed"
|
||||
# Run execute_callback for memory operations
|
||||
try:
|
||||
# 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)
|
||||
|
||||
# Run task callback if exists
|
||||
if task.callback:
|
||||
await task.execute_callback(task_output)
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(task.callback):
|
||||
if asyncio.get_event_loop().is_running():
|
||||
asyncio.create_task(task.callback(task_output))
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(task.callback(task_output))
|
||||
else:
|
||||
task.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing task callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
self.save_output_to_file(task, task_output)
|
||||
if self.verbose >= 1:
|
||||
logging.info(f"Task {task_id} completed successfully.")
|
||||
logger.info(f"Task {task_id} completed successfully.")
|
||||
else:
|
||||
task.status = "in progress"
|
||||
if self.verbose >= 1:
|
||||
logging.info(f"Task {task_id} not completed, retrying")
|
||||
logger.info(f"Task {task_id} not completed, retrying")
|
||||
await asyncio.sleep(1)
|
||||
retries += 1
|
||||
else:
|
||||
if task.status == "failed":
|
||||
logging.info("Task is failed, resetting to in-progress for another try...")
|
||||
logger.info("Task is failed, resetting to in-progress for another try...")
|
||||
task.status = "in progress"
|
||||
else:
|
||||
logging.info("Invalid Task status")
|
||||
logger.info("Invalid Task status")
|
||||
break
|
||||
|
||||
if retries == self.max_retries and task.status != "completed":
|
||||
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
|
||||
async def arun_all_tasks(self):
|
||||
"""Async version of run_all_tasks method"""
|
||||
@@ -249,15 +438,30 @@ Expected Output: {task.expected_output}.
|
||||
tasks=self.tasks,
|
||||
agents=self.agents,
|
||||
manager_llm=self.manager_llm,
|
||||
verbose=self.verbose
|
||||
verbose=self.verbose,
|
||||
max_iter=self.max_iter
|
||||
)
|
||||
|
||||
if self.process == "workflow":
|
||||
# Collect all tasks that should run in parallel
|
||||
parallel_tasks = []
|
||||
async for task_id in process.aworkflow():
|
||||
if self.tasks[task_id].async_execution:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
if self.tasks[task_id].async_execution and self.tasks[task_id].is_start:
|
||||
parallel_tasks.append(task_id)
|
||||
elif parallel_tasks:
|
||||
# Execute collected parallel tasks
|
||||
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
|
||||
parallel_tasks = []
|
||||
# Run the current non-parallel task
|
||||
if self.tasks[task_id].async_execution:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
|
||||
# Execute any remaining parallel tasks
|
||||
if parallel_tasks:
|
||||
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
|
||||
|
||||
elif self.process == "sequential":
|
||||
async for task_id in process.asequential():
|
||||
if self.tasks[task_id].async_execution:
|
||||
@@ -273,8 +477,16 @@ Expected Output: {task.expected_output}.
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
|
||||
async def astart(self):
|
||||
async def astart(self, content=None, **kwargs):
|
||||
"""Async version of start method"""
|
||||
if content:
|
||||
# Add content to context of all tasks
|
||||
for task in self.tasks.values():
|
||||
if isinstance(content, (str, list)):
|
||||
if not task.context:
|
||||
task.context = []
|
||||
task.context.append(content)
|
||||
|
||||
await self.arun_all_tasks()
|
||||
return {
|
||||
"task_status": self.get_all_tasks_status(),
|
||||
@@ -289,7 +501,7 @@ Expected Output: {task.expected_output}.
|
||||
with open(task.output_file, "w") as f:
|
||||
f.write(str(task_output))
|
||||
if self.verbose >= 1:
|
||||
logging.info(f"Task output saved to {task.output_file}")
|
||||
logger.info(f"Task output saved to {task.output_file}")
|
||||
except Exception as e:
|
||||
display_error(f"Error saving task output to file: {e}")
|
||||
|
||||
@@ -300,6 +512,15 @@ Expected Output: {task.expected_output}.
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
|
||||
logger.info(f"Starting execution of task {task_id}")
|
||||
logger.info(f"Task config: {task.config}")
|
||||
|
||||
# Initialize memory before task execution
|
||||
if not task.memory:
|
||||
task.memory = task.initialize_memory()
|
||||
|
||||
logger.info(f"Task memory status: {'Initialized' if task.memory else 'Not initialized'}")
|
||||
|
||||
# Only import multimodal dependencies if task has images
|
||||
if task.images and task.status == "not started":
|
||||
try:
|
||||
@@ -320,23 +541,64 @@ Expected Output: {task.expected_output}.
|
||||
task_prompt = f"""
|
||||
You need to do the following task: {task.description}.
|
||||
Expected Output: {task.expected_output}.
|
||||
"""
|
||||
"""
|
||||
if task.context:
|
||||
context_results = ""
|
||||
for context_task in task.context:
|
||||
if context_task.result:
|
||||
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
|
||||
else:
|
||||
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
|
||||
context_results = [] # Use list to avoid duplicates
|
||||
for context_item in task.context:
|
||||
if isinstance(context_item, str):
|
||||
context_results.append(f"Input Content:\n{context_item}")
|
||||
elif isinstance(context_item, list):
|
||||
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
|
||||
elif hasattr(context_item, 'result'): # Task object
|
||||
if context_item.result:
|
||||
context_results.append(
|
||||
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
|
||||
)
|
||||
else:
|
||||
context_results.append(
|
||||
f"Previous task {context_item.name if context_item.name else context_item.description} has no result yet."
|
||||
)
|
||||
elif isinstance(context_item, dict) and "vector_store" in context_item:
|
||||
from ..knowledge.knowledge import Knowledge
|
||||
try:
|
||||
# Handle both string and dict configs
|
||||
cfg = context_item["vector_store"]
|
||||
if isinstance(cfg, str):
|
||||
cfg = json.loads(cfg)
|
||||
|
||||
knowledge = Knowledge(config={"vector_store": cfg}, verbose=self.verbose)
|
||||
|
||||
# Only use user_id as filter
|
||||
db_results = knowledge.search(
|
||||
task.description,
|
||||
user_id=self.user_id if self.user_id else None
|
||||
)
|
||||
context_results.append(f"[DB Context]: {str(db_results)}")
|
||||
except Exception as e:
|
||||
context_results.append(f"[Vector DB Error]: {e}")
|
||||
|
||||
# Join unique context results
|
||||
unique_contexts = list(dict.fromkeys(context_results)) # Remove duplicates
|
||||
task_prompt += f"""
|
||||
Here are the results of previous tasks that might be useful:\n
|
||||
{context_results}
|
||||
"""
|
||||
Context:
|
||||
|
||||
{' '.join(unique_contexts)}
|
||||
"""
|
||||
|
||||
# Add memory context if available
|
||||
if task.memory:
|
||||
try:
|
||||
memory_context = task.memory.build_context_for_task(task.description)
|
||||
if memory_context:
|
||||
task_prompt += f"\n\nRelevant memory context:\n{memory_context}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting memory context: {e}")
|
||||
|
||||
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
|
||||
|
||||
if self.verbose >= 2:
|
||||
logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
@@ -373,12 +635,30 @@ Expected Output: {task.expected_output}.
|
||||
|
||||
agent_output = executor_agent.chat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
tools=task.tools
|
||||
tools=task.tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
else:
|
||||
agent_output = executor_agent.chat(task_prompt, tools=task.tools)
|
||||
agent_output = executor_agent.chat(
|
||||
task_prompt,
|
||||
tools=task.tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
|
||||
if agent_output:
|
||||
# Store the response in memory
|
||||
if task.memory:
|
||||
try:
|
||||
task.store_in_memory(
|
||||
content=agent_output,
|
||||
agent_name=executor_agent.name,
|
||||
task_id=task_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store agent output in memory: {e}")
|
||||
|
||||
task_output = TaskOutput(
|
||||
description=task.description,
|
||||
summary=task.description[:10],
|
||||
@@ -394,8 +674,8 @@ Expected Output: {task.expected_output}.
|
||||
task_output.json_dict = parsed
|
||||
task_output.output_format = "JSON"
|
||||
except:
|
||||
logging.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logging.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logger.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
|
||||
if task.output_pydantic:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
@@ -405,8 +685,8 @@ Expected Output: {task.expected_output}.
|
||||
task_output.pydantic = pyd_obj
|
||||
task_output.output_format = "Pydantic"
|
||||
except:
|
||||
logging.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logging.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
|
||||
task.result = task_output
|
||||
return task_output
|
||||
@@ -421,37 +701,59 @@ Expected Output: {task.expected_output}.
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
if task.status == "completed":
|
||||
logging.info(f"Task with ID {task_id} is already completed")
|
||||
logger.info(f"Task with ID {task_id} is already completed")
|
||||
return
|
||||
|
||||
retries = 0
|
||||
while task.status != "completed" and retries < self.max_retries:
|
||||
logging.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
logger.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
if task.status in ["not started", "in progress"]:
|
||||
task_output = self.execute_task(task_id)
|
||||
if task_output and self.completion_checker(task, task_output.raw):
|
||||
task.status = "completed"
|
||||
# Run execute_callback for memory operations
|
||||
try:
|
||||
# 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)
|
||||
|
||||
# Run task callback if exists
|
||||
if task.callback:
|
||||
task.callback(task_output)
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(task.callback):
|
||||
if asyncio.get_event_loop().is_running():
|
||||
asyncio.create_task(task.callback(task_output))
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(task.callback(task_output))
|
||||
else:
|
||||
task.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing task callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
self.save_output_to_file(task, task_output)
|
||||
if self.verbose >= 1:
|
||||
logging.info(f"Task {task_id} completed successfully.")
|
||||
logger.info(f"Task {task_id} completed successfully.")
|
||||
else:
|
||||
task.status = "in progress"
|
||||
if self.verbose >= 1:
|
||||
logging.info(f"Task {task_id} not completed, retrying")
|
||||
logger.info(f"Task {task_id} not completed, retrying")
|
||||
time.sleep(1)
|
||||
retries += 1
|
||||
else:
|
||||
if task.status == "failed":
|
||||
logging.info("Task is failed, resetting to in-progress for another try...")
|
||||
logger.info("Task is failed, resetting to in-progress for another try...")
|
||||
task.status = "in progress"
|
||||
else:
|
||||
logging.info("Invalid Task status")
|
||||
logger.info("Invalid Task status")
|
||||
break
|
||||
|
||||
if retries == self.max_retries and task.status != "completed":
|
||||
logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
|
||||
def run_all_tasks(self):
|
||||
"""Synchronous version of run_all_tasks method"""
|
||||
@@ -459,7 +761,8 @@ Expected Output: {task.expected_output}.
|
||||
tasks=self.tasks,
|
||||
agents=self.agents,
|
||||
manager_llm=self.manager_llm,
|
||||
verbose=self.verbose
|
||||
verbose=self.verbose,
|
||||
max_iter=self.max_iter
|
||||
)
|
||||
|
||||
if self.process == "workflow":
|
||||
@@ -498,12 +801,24 @@ Expected Output: {task.expected_output}.
|
||||
return str(agent[0])
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
def start(self, content=None, **kwargs):
|
||||
"""Start agent execution with optional content and config"""
|
||||
if content:
|
||||
# Add content to context of all tasks
|
||||
for task in self.tasks.values():
|
||||
if isinstance(content, (str, list)):
|
||||
# If context is empty, initialize it
|
||||
if not task.context:
|
||||
task.context = []
|
||||
# Add content to context
|
||||
task.context.append(content)
|
||||
|
||||
# Run tasks as before
|
||||
self.run_all_tasks()
|
||||
return {
|
||||
"task_status": self.get_all_tasks_status(),
|
||||
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
|
||||
}
|
||||
}
|
||||
|
||||
def set_state(self, key: str, value: Any) -> None:
|
||||
"""Set a state value"""
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
AutoAgents - A class for automatically creating and managing AI agents and tasks.
|
||||
|
||||
This class provides a simplified interface for creating and running AI agents with tasks.
|
||||
It automatically handles agent creation, task setup, and execution flow.
|
||||
"""
|
||||
|
||||
from .agents import PraisonAIAgents
|
||||
from ..agent.agent import Agent
|
||||
from ..task.task import Task
|
||||
from typing import List, Any, Optional, Dict, Union
|
||||
import logging
|
||||
import os
|
||||
from pydantic import BaseModel, Field
|
||||
from ..main import display_instruction, display_tool_call, display_interaction, client
|
||||
|
||||
# Define Pydantic models for structured output
|
||||
class TaskConfig(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
expected_output: str
|
||||
tools: List[str]
|
||||
|
||||
class AgentConfig(BaseModel):
|
||||
name: str
|
||||
role: str
|
||||
goal: str
|
||||
backstory: str
|
||||
tools: List[str]
|
||||
tasks: List[TaskConfig]
|
||||
|
||||
class AutoAgentsConfig(BaseModel):
|
||||
main_instruction: str
|
||||
process_type: str
|
||||
agents: List[AgentConfig]
|
||||
|
||||
class AutoAgents(PraisonAIAgents):
|
||||
def __init__(
|
||||
self,
|
||||
instructions: str,
|
||||
tools: Optional[List[Any]] = None,
|
||||
verbose: bool = False,
|
||||
process: str = "sequential",
|
||||
manager_llm: Optional[str] = None,
|
||||
max_retries: int = 5,
|
||||
completion_checker: Optional[Any] = None,
|
||||
allow_code_execution: bool = False,
|
||||
memory: bool = True,
|
||||
markdown: bool = True,
|
||||
self_reflect: bool = False,
|
||||
max_reflect: int = 3,
|
||||
min_reflect: int = 1,
|
||||
llm: Optional[str] = None,
|
||||
function_calling_llm: Optional[str] = None,
|
||||
respect_context_window: bool = True,
|
||||
code_execution_mode: str = "safe",
|
||||
embedder_config: Optional[Dict[str, Any]] = None,
|
||||
knowledge_sources: Optional[List[Any]] = None,
|
||||
use_system_prompt: bool = True,
|
||||
cache: bool = True,
|
||||
allow_delegation: bool = False,
|
||||
step_callback: Optional[Any] = None,
|
||||
system_template: Optional[str] = None,
|
||||
prompt_template: Optional[str] = None,
|
||||
response_template: Optional[str] = None,
|
||||
max_rpm: Optional[int] = None,
|
||||
max_execution_time: Optional[int] = None,
|
||||
max_iter: int = 20,
|
||||
reflect_llm: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
max_agents: int = 3 # New parameter for maximum number of agents
|
||||
):
|
||||
"""Initialize AutoAgents with configuration for automatic agent and task creation."""
|
||||
if max_agents < 1:
|
||||
raise ValueError("max_agents must be at least 1")
|
||||
if max_agents > 10:
|
||||
raise ValueError("max_agents cannot exceed 10")
|
||||
|
||||
# Store all configuration parameters first
|
||||
self.instructions = instructions
|
||||
self.tools = tools or []
|
||||
self.verbose = verbose # Store verbose parameter
|
||||
self.max_agents = max_agents # Store max_agents parameter
|
||||
self.allow_code_execution = allow_code_execution
|
||||
self.memory = memory
|
||||
self.markdown = markdown
|
||||
self.self_reflect = self_reflect
|
||||
self.max_reflect = max_reflect
|
||||
self.min_reflect = min_reflect
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
self.function_calling_llm = function_calling_llm
|
||||
self.respect_context_window = respect_context_window
|
||||
self.code_execution_mode = code_execution_mode
|
||||
self.embedder_config = embedder_config
|
||||
self.knowledge_sources = knowledge_sources
|
||||
self.use_system_prompt = use_system_prompt
|
||||
self.cache = cache
|
||||
self.allow_delegation = allow_delegation
|
||||
self.step_callback = step_callback
|
||||
self.system_template = system_template
|
||||
self.prompt_template = prompt_template
|
||||
self.response_template = response_template
|
||||
self.max_rpm = max_rpm
|
||||
self.max_execution_time = max_execution_time
|
||||
self.max_iter = max_iter
|
||||
self.reflect_llm = reflect_llm
|
||||
|
||||
# Display initial instruction
|
||||
if self.verbose:
|
||||
display_instruction(f"🎯 Main Task: {self.instructions}")
|
||||
display_instruction(f"📊 Maximum Agents: {self.max_agents}")
|
||||
if self.tools:
|
||||
tool_names = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
display_tool_call(f"🛠️ Available Tools: {', '.join(tool_names)}")
|
||||
|
||||
# Generate agents and tasks configuration
|
||||
config = self._generate_config()
|
||||
agents, tasks = self._create_agents_and_tasks(config)
|
||||
|
||||
# Display agents and their tasks
|
||||
if self.verbose:
|
||||
self._display_agents_and_tasks(agents, tasks)
|
||||
|
||||
# Initialize parent class with generated agents and tasks
|
||||
super().__init__(
|
||||
agents=agents,
|
||||
tasks=tasks,
|
||||
verbose=verbose,
|
||||
completion_checker=completion_checker,
|
||||
max_retries=max_retries,
|
||||
process=process,
|
||||
manager_llm=manager_llm
|
||||
)
|
||||
|
||||
def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]):
|
||||
"""Display the created agents and their assigned tasks"""
|
||||
display_instruction("\n🤖 Generated Agents and Tasks:")
|
||||
|
||||
# Create a mapping of agents to their tasks
|
||||
agent_tasks = {}
|
||||
for task in tasks:
|
||||
if task.agent not in agent_tasks:
|
||||
agent_tasks[task.agent] = []
|
||||
agent_tasks[task.agent].append(task)
|
||||
|
||||
# Display each agent and their tasks
|
||||
for agent in agents:
|
||||
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in agent.tools]
|
||||
display_interaction(
|
||||
f"\n👤 Agent: {agent.name}",
|
||||
f"""Role: {agent.role}
|
||||
Goal: {agent.goal}
|
||||
Tools: {', '.join(agent_tools)}"""
|
||||
)
|
||||
|
||||
# Display tasks for this agent
|
||||
if agent in agent_tasks:
|
||||
for i, task in enumerate(agent_tasks[agent], 1):
|
||||
task_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in task.tools]
|
||||
display_instruction(
|
||||
f""" 📋 Task {i}: {task.name}
|
||||
Description: {task.description}
|
||||
Expected Output: {task.expected_output}
|
||||
Tools: {', '.join(task_tools)}"""
|
||||
)
|
||||
|
||||
def _get_available_tools(self) -> List[str]:
|
||||
"""Get list of available tools"""
|
||||
if not self.tools:
|
||||
return []
|
||||
return [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
|
||||
def _get_tool_by_name(self, tool_name: str) -> Optional[Any]:
|
||||
"""Get tool object by its name"""
|
||||
for tool in self.tools:
|
||||
if (hasattr(tool, '__name__') and tool.__name__ == tool_name) or str(tool) == tool_name:
|
||||
return tool
|
||||
return None
|
||||
|
||||
def _assign_tools_to_agent(self, agent_config: AgentConfig) -> List[Any]:
|
||||
"""
|
||||
Assign appropriate tools to an agent based on its role and tasks.
|
||||
|
||||
Args:
|
||||
agent_config: The agent configuration containing role and required tools
|
||||
|
||||
Returns:
|
||||
List of tool objects assigned to this agent
|
||||
"""
|
||||
assigned_tools = []
|
||||
tool_names = set(agent_config.tools)
|
||||
|
||||
# Also look at task requirements
|
||||
for task in agent_config.tasks:
|
||||
tool_names.update(task.tools)
|
||||
|
||||
# Assign tools that match the requirements
|
||||
for tool_name in tool_names:
|
||||
tool = self._get_tool_by_name(tool_name)
|
||||
if tool:
|
||||
assigned_tools.append(tool)
|
||||
|
||||
# If no specific tools matched but we have tools available,
|
||||
# assign all tools to ensure functionality
|
||||
if not assigned_tools and self.tools:
|
||||
assigned_tools = self.tools
|
||||
|
||||
return assigned_tools
|
||||
|
||||
def _generate_config(self) -> AutoAgentsConfig:
|
||||
"""Generate the configuration for agents and tasks"""
|
||||
prompt = f"""
|
||||
Generate a configuration for AI agents to accomplish this task: "{self.instructions}"
|
||||
|
||||
The configuration should include:
|
||||
1. A main instruction that clearly states the overall goal
|
||||
2. A process type (sequential, workflow, or hierarchical)
|
||||
3. A list of maximum {self.max_agents} agents (no more, no less), each with:
|
||||
- Name, role, goal, and backstory
|
||||
- List of required tools from: {self._get_available_tools()}
|
||||
- Only add tools that are needed for the agent to perform its task
|
||||
- Only one task per agent. Add more than one task if absolutely necessary.
|
||||
- List of specific tasks they need to perform
|
||||
- Whether they should self-reflect or allow delegation
|
||||
|
||||
Requirements:
|
||||
1. Each agent should have clear, focused responsibilities
|
||||
2. Tasks should be broken down into manageable steps
|
||||
3. Tool selection should be appropriate for each task
|
||||
4. The process type should match the task requirements
|
||||
5. Generate maximum {self.max_agents} agents to handle this task efficiently
|
||||
|
||||
Return the configuration in a structured JSON format matching the AutoAgentsConfig schema.
|
||||
"""
|
||||
|
||||
try:
|
||||
response = client.beta.chat.completions.parse(
|
||||
model=self.llm,
|
||||
response_format=AutoAgentsConfig,
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a helpful assistant designed to generate AI agent configurations."},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
)
|
||||
|
||||
# Ensure we have exactly max_agents number of agents
|
||||
if len(response.choices[0].message.parsed.agents) > self.max_agents:
|
||||
response.choices[0].message.parsed.agents = response.choices[0].message.parsed.agents[:self.max_agents]
|
||||
elif len(response.choices[0].message.parsed.agents) < self.max_agents:
|
||||
logging.warning(f"Generated {len(response.choices[0].message.parsed.agents)} agents, expected {self.max_agents}")
|
||||
|
||||
return response.choices[0].message.parsed
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating configuration: {e}")
|
||||
raise
|
||||
|
||||
def _create_agents_and_tasks(self, config: AutoAgentsConfig) -> tuple[List[Agent], List[Task]]:
|
||||
"""Create agents and tasks from configuration"""
|
||||
agents = []
|
||||
tasks = []
|
||||
|
||||
for agent_config in config.agents:
|
||||
# Get appropriate tools for this agent
|
||||
agent_tools = self._assign_tools_to_agent(agent_config)
|
||||
|
||||
# Create the agent with all parameters
|
||||
agent = Agent(
|
||||
name=agent_config.name,
|
||||
role=agent_config.role,
|
||||
goal=agent_config.goal,
|
||||
backstory=agent_config.backstory,
|
||||
tools=agent_tools, # Use assigned tools
|
||||
verbose=self.verbose >= 1,
|
||||
allow_code_execution=self.allow_code_execution,
|
||||
memory=self.memory,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
llm=self.llm,
|
||||
function_calling_llm=self.function_calling_llm,
|
||||
respect_context_window=self.respect_context_window,
|
||||
code_execution_mode=self.code_execution_mode,
|
||||
embedder_config=self.embedder_config,
|
||||
knowledge_sources=self.knowledge_sources,
|
||||
use_system_prompt=self.use_system_prompt,
|
||||
cache=self.cache,
|
||||
allow_delegation=self.allow_delegation,
|
||||
step_callback=self.step_callback,
|
||||
system_template=self.system_template,
|
||||
prompt_template=self.prompt_template,
|
||||
response_template=self.response_template,
|
||||
max_rpm=self.max_rpm,
|
||||
max_execution_time=self.max_execution_time,
|
||||
max_iter=self.max_iter,
|
||||
reflect_llm=self.reflect_llm
|
||||
)
|
||||
agents.append(agent)
|
||||
|
||||
# Create tasks for this agent
|
||||
for task_config in agent_config.tasks:
|
||||
# Get task-specific tools
|
||||
task_tools = [self._get_tool_by_name(t) for t in task_config.tools]
|
||||
task_tools = [t for t in task_tools if t] # Remove None values
|
||||
|
||||
# If no specific tools matched, use agent's tools
|
||||
if not task_tools:
|
||||
task_tools = agent_tools
|
||||
|
||||
task = Task(
|
||||
name=task_config.name,
|
||||
description=task_config.description,
|
||||
expected_output=task_config.expected_output,
|
||||
agent=agent,
|
||||
tools=task_tools # Use task-specific tools
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
return agents, tasks
|
||||
|
||||
async def astart(self):
|
||||
"""
|
||||
Async version of start() method.
|
||||
Creates tasks based on the instructions, then starts execution.
|
||||
Returns the task status and results dictionary.
|
||||
"""
|
||||
return await super().astart()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Creates tasks based on the instructions, then starts execution.
|
||||
Returns the task status and results dictionary.
|
||||
"""
|
||||
return super().start()
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
PraisonAI Knowledge - Advanced knowledge management system with configurable features
|
||||
"""
|
||||
|
||||
from praisonaiagents.knowledge.knowledge import Knowledge
|
||||
from praisonaiagents.knowledge.chunking import Chunking
|
||||
|
||||
__all__ = ["Knowledge", "Chunking"]
|
||||
@@ -0,0 +1,182 @@
|
||||
from typing import List, Union, Optional, Dict, Any
|
||||
from functools import cached_property
|
||||
import importlib
|
||||
|
||||
class Chunking:
|
||||
"""A unified class for text chunking with various chunking strategies."""
|
||||
|
||||
CHUNKER_PARAMS = {
|
||||
'token': ['chunk_size', 'chunk_overlap', 'tokenizer'],
|
||||
'word': ['chunk_size', 'chunk_overlap', 'tokenizer'],
|
||||
'sentence': ['chunk_size', 'chunk_overlap', 'tokenizer'],
|
||||
'semantic': ['chunk_size', 'embedding_model', 'tokenizer'],
|
||||
'sdpm': ['chunk_size', 'embedding_model', 'tokenizer'],
|
||||
'late': ['chunk_size', 'embedding_model', 'tokenizer'],
|
||||
'recursive': ['chunk_size', 'tokenizer']
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def SUPPORTED_CHUNKERS(self) -> Dict[str, Any]:
|
||||
"""Lazy load chunker classes."""
|
||||
try:
|
||||
from chonkie.chunker import (
|
||||
TokenChunker,
|
||||
WordChunker,
|
||||
SentenceChunker,
|
||||
SemanticChunker,
|
||||
SDPMChunker,
|
||||
LateChunker,
|
||||
RecursiveChunker
|
||||
)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"chonkie package not found. Please install it using: pip install 'praisonaiagents[knowledge]'"
|
||||
)
|
||||
|
||||
return {
|
||||
'token': TokenChunker,
|
||||
'word': WordChunker,
|
||||
'sentence': SentenceChunker,
|
||||
'semantic': SemanticChunker,
|
||||
'sdpm': SDPMChunker,
|
||||
'late': LateChunker,
|
||||
'recursive': RecursiveChunker
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chunker_type: str = 'token',
|
||||
chunk_size: int = 512,
|
||||
chunk_overlap: int = 128,
|
||||
tokenizer: str = "gpt2",
|
||||
embedding_model: Optional[Union[str, Any]] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""Initialize the Chunking class."""
|
||||
if chunker_type not in self.CHUNKER_PARAMS:
|
||||
raise ValueError(
|
||||
f"Unsupported chunker type: {chunker_type}. "
|
||||
f"Must be one of: {list(self.CHUNKER_PARAMS.keys())}"
|
||||
)
|
||||
|
||||
self.chunker_type = chunker_type
|
||||
self.chunk_size = chunk_size
|
||||
self.chunk_overlap = chunk_overlap
|
||||
self.tokenizer = tokenizer
|
||||
self._embedding_model = embedding_model
|
||||
self.kwargs = kwargs
|
||||
|
||||
# Initialize these as None for lazy loading
|
||||
self._chunker = None
|
||||
self._embeddings = None
|
||||
|
||||
@cached_property
|
||||
def embedding_model(self):
|
||||
"""Lazy load the embedding model."""
|
||||
if self._embedding_model is None and self.chunker_type in ['semantic', 'sdpm', 'late']:
|
||||
from chonkie.embeddings import AutoEmbeddings
|
||||
return AutoEmbeddings.get_embeddings("all-MiniLM-L6-v2")
|
||||
elif isinstance(self._embedding_model, str):
|
||||
from chonkie.embeddings import AutoEmbeddings
|
||||
return AutoEmbeddings.get_embeddings(self._embedding_model)
|
||||
return self._embedding_model
|
||||
|
||||
def _get_chunker_params(self) -> Dict[str, Any]:
|
||||
"""Get the appropriate parameters for the current chunker type."""
|
||||
allowed_params = self.CHUNKER_PARAMS[self.chunker_type]
|
||||
params = {'chunk_size': self.chunk_size}
|
||||
|
||||
if 'chunk_overlap' in allowed_params:
|
||||
params['chunk_overlap'] = self.chunk_overlap
|
||||
|
||||
if 'tokenizer' in allowed_params:
|
||||
if self.chunker_type in ['semantic', 'sdpm', 'late']:
|
||||
params['tokenizer'] = self.embedding_model.get_tokenizer_or_token_counter()
|
||||
else:
|
||||
params['tokenizer'] = self.tokenizer
|
||||
|
||||
if 'embedding_model' in allowed_params:
|
||||
params['embedding_model'] = self.embedding_model
|
||||
|
||||
# Add any additional kwargs that are in allowed_params
|
||||
for key, value in self.kwargs.items():
|
||||
if key in allowed_params:
|
||||
params[key] = value
|
||||
|
||||
return params
|
||||
|
||||
@cached_property
|
||||
def chunker(self):
|
||||
"""Lazy load the chunker instance."""
|
||||
if self._chunker is None:
|
||||
chunker_cls = self.SUPPORTED_CHUNKERS[self.chunker_type]
|
||||
common_params = self._get_chunker_params()
|
||||
self._chunker = chunker_cls(**common_params)
|
||||
|
||||
return self._chunker
|
||||
|
||||
def _get_overlap_refinery(self, context_size: Optional[int] = None, **kwargs):
|
||||
"""Lazy load the overlap refinery."""
|
||||
try:
|
||||
from chonkie.refinery import OverlapRefinery
|
||||
except ImportError:
|
||||
raise ImportError("Failed to import OverlapRefinery from chonkie.refinery")
|
||||
|
||||
if context_size is None:
|
||||
context_size = self.chunk_overlap
|
||||
|
||||
return OverlapRefinery(
|
||||
context_size=context_size,
|
||||
tokenizer=self.chunker.tokenizer,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def add_overlap_context(
|
||||
self,
|
||||
chunks: List[Any],
|
||||
context_size: int = None,
|
||||
mode: str = "suffix",
|
||||
merge_context: bool = True
|
||||
) -> List[Any]:
|
||||
"""Add overlap context to chunks using OverlapRefinery."""
|
||||
refinery = self._get_overlap_refinery(
|
||||
context_size=context_size,
|
||||
mode=mode,
|
||||
merge_context=merge_context
|
||||
)
|
||||
return refinery.refine(chunks)
|
||||
|
||||
def chunk(
|
||||
self,
|
||||
text: Union[str, List[str]],
|
||||
add_context: bool = False,
|
||||
context_params: Optional[Dict[str, Any]] = None
|
||||
) -> Union[List[Any], List[List[Any]]]:
|
||||
"""Chunk text using the configured chunking strategy."""
|
||||
chunks = self.chunker(text)
|
||||
|
||||
if add_context:
|
||||
context_params = context_params or {}
|
||||
if isinstance(text, str):
|
||||
chunks = self.add_overlap_context(chunks, **context_params)
|
||||
else:
|
||||
chunks = [self.add_overlap_context(c, **context_params) for c in chunks]
|
||||
|
||||
return chunks
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
text: Union[str, List[str]],
|
||||
add_context: bool = False,
|
||||
context_params: Optional[Dict[str, Any]] = None
|
||||
) -> Union[List[Any], List[List[Any]]]:
|
||||
"""Make the Chunking instance callable."""
|
||||
return self.chunk(text, add_context, context_params)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation of the Chunking instance."""
|
||||
return (
|
||||
f"Chunking(chunker_type='{self.chunker_type}', "
|
||||
f"chunk_size={self.chunk_size}, "
|
||||
f"chunk_overlap={self.chunk_overlap})"
|
||||
)
|
||||
@@ -0,0 +1,321 @@
|
||||
import os
|
||||
import logging
|
||||
import uuid
|
||||
import time
|
||||
from .chunking import Chunking
|
||||
from functools import cached_property
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CustomMemory:
|
||||
@classmethod
|
||||
def from_config(cls, config):
|
||||
from mem0 import Memory
|
||||
return type('CustomMemory', (Memory,), {
|
||||
'_add_to_vector_store': cls._add_to_vector_store
|
||||
}).from_config(config)
|
||||
|
||||
@staticmethod
|
||||
def _add_to_vector_store(self, messages, metadata, filters):
|
||||
# Custom implementation that doesn't use LLM
|
||||
parsed_messages = "\n".join([msg["content"] for msg in messages])
|
||||
|
||||
# Create a simple fact without using LLM
|
||||
new_retrieved_facts = [parsed_messages]
|
||||
|
||||
# Process embeddings and continue with vector store operations
|
||||
new_message_embeddings = {}
|
||||
for new_mem in new_retrieved_facts:
|
||||
messages_embeddings = self.embedding_model.embed(new_mem)
|
||||
new_message_embeddings[new_mem] = messages_embeddings
|
||||
|
||||
# Create the memory
|
||||
memory_id = self._create_memory(
|
||||
data=parsed_messages,
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
return [{
|
||||
"id": memory_id,
|
||||
"memory": parsed_messages,
|
||||
"event": "ADD"
|
||||
}]
|
||||
|
||||
class Knowledge:
|
||||
def __init__(self, config=None, verbose=None):
|
||||
self._config = config
|
||||
self._verbose = verbose or 0
|
||||
os.environ['ANONYMIZED_TELEMETRY'] = 'False' # Chromadb
|
||||
|
||||
# Configure logging levels based on verbose setting
|
||||
if not self._verbose:
|
||||
# Suppress logs from all relevant dependencies
|
||||
for logger_name in [
|
||||
'mem0',
|
||||
'chromadb',
|
||||
'local_persistent_hnsw',
|
||||
'_client',
|
||||
'main'
|
||||
]:
|
||||
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
||||
|
||||
# Disable OpenAI API request logging
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
|
||||
# Set root logger to warning to catch any uncategorized logs
|
||||
logging.getLogger().setLevel(logging.WARNING)
|
||||
|
||||
@cached_property
|
||||
def _deps(self):
|
||||
try:
|
||||
from markitdown import MarkItDown
|
||||
import chromadb
|
||||
return {
|
||||
'chromadb': chromadb,
|
||||
'markdown': MarkItDown()
|
||||
}
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Required packages not installed. Please install using: "
|
||||
'pip install "praisonaiagents[knowledge]"'
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def config(self):
|
||||
# Generate unique collection name for each instance (only if not provided in config)
|
||||
default_collection = f"test_{int(time.time())}_{str(uuid.uuid4())[:8]}"
|
||||
persist_dir = ".praison"
|
||||
|
||||
# Create persistent client config
|
||||
base_config = {
|
||||
"vector_store": {
|
||||
"provider": "chroma",
|
||||
"config": {
|
||||
"collection_name": default_collection,
|
||||
"path": persist_dir,
|
||||
"client": self._deps['chromadb'].PersistentClient(path=persist_dir),
|
||||
"host": None,
|
||||
"port": None
|
||||
}
|
||||
},
|
||||
"version": "v1.1",
|
||||
"custom_prompt": "Return {{\"facts\": [text]}} where text is the exact input provided and json response"
|
||||
}
|
||||
|
||||
# If config is provided, merge it with base config
|
||||
if self._config:
|
||||
# Merge version if provided
|
||||
if "version" in self._config:
|
||||
base_config["version"] = self._config["version"]
|
||||
|
||||
# Merge vector_store config
|
||||
if "vector_store" in self._config:
|
||||
if "provider" in self._config["vector_store"]:
|
||||
base_config["vector_store"]["provider"] = self._config["vector_store"]["provider"]
|
||||
|
||||
if "config" in self._config["vector_store"]:
|
||||
config_copy = self._config["vector_store"]["config"].copy()
|
||||
# Only exclude client as it's managed internally
|
||||
if "client" in config_copy:
|
||||
del config_copy["client"]
|
||||
base_config["vector_store"]["config"].update(config_copy)
|
||||
|
||||
# Merge embedder config if provided
|
||||
if "embedder" in self._config:
|
||||
base_config["embedder"] = self._config["embedder"]
|
||||
|
||||
# Merge llm config if provided
|
||||
if "llm" in self._config:
|
||||
base_config["llm"] = self._config["llm"]
|
||||
return base_config
|
||||
|
||||
@cached_property
|
||||
def memory(self):
|
||||
try:
|
||||
return CustomMemory.from_config(self.config)
|
||||
except (NotImplementedError, ValueError) as e:
|
||||
if "list_collections" in str(e) or "Extra fields not allowed" in str(e):
|
||||
# Keep only allowed fields
|
||||
vector_store_config = {
|
||||
"collection_name": self.config["vector_store"]["config"]["collection_name"],
|
||||
"path": self.config["vector_store"]["config"]["path"]
|
||||
}
|
||||
self.config["vector_store"]["config"] = vector_store_config
|
||||
from mem0 import Memory
|
||||
return Memory.from_config(self.config)
|
||||
raise
|
||||
|
||||
@cached_property
|
||||
def markdown(self):
|
||||
return self._deps['markdown']
|
||||
|
||||
@cached_property
|
||||
def chunker(self):
|
||||
return Chunking(
|
||||
chunker_type='recursive',
|
||||
chunk_size=512,
|
||||
chunk_overlap=50
|
||||
)
|
||||
|
||||
def _log(self, message, level=2):
|
||||
"""Internal logging helper"""
|
||||
if self._verbose and self._verbose >= level:
|
||||
logger.info(message)
|
||||
|
||||
def store(self, content, user_id=None, agent_id=None, run_id=None, metadata=None):
|
||||
"""Store a memory."""
|
||||
try:
|
||||
if isinstance(content, str):
|
||||
if any(content.lower().endswith(ext) for ext in ['.pdf', '.doc', '.docx', '.txt']):
|
||||
self._log(f"Content appears to be a file path, processing file: {content}")
|
||||
return self.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
|
||||
|
||||
content = content.strip()
|
||||
if not content:
|
||||
return []
|
||||
|
||||
result = self.memory.add(content, user_id=user_id, agent_id=agent_id, run_id=run_id, metadata=metadata)
|
||||
self._log(f"Store operation result: {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing content: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_all(self, user_id=None, agent_id=None, run_id=None):
|
||||
"""Retrieve all memories."""
|
||||
return self.memory.get_all(user_id=user_id, agent_id=agent_id, run_id=run_id)
|
||||
|
||||
def get(self, memory_id):
|
||||
"""Retrieve a specific memory by ID."""
|
||||
return self.memory.get(memory_id)
|
||||
|
||||
def search(self, query, user_id=None, agent_id=None, run_id=None):
|
||||
"""Search for memories related to a query."""
|
||||
return self.memory.search(query, user_id=user_id, agent_id=agent_id, run_id=run_id)
|
||||
|
||||
def update(self, memory_id, data):
|
||||
"""Update a memory."""
|
||||
return self.memory.update(memory_id, data)
|
||||
|
||||
def history(self, memory_id):
|
||||
"""Get the history of changes for a memory."""
|
||||
return self.memory.history(memory_id)
|
||||
|
||||
def delete(self, memory_id):
|
||||
"""Delete a memory."""
|
||||
self.memory.delete(memory_id)
|
||||
|
||||
def delete_all(self, user_id=None, agent_id=None, run_id=None):
|
||||
"""Delete all memories."""
|
||||
self.memory.delete_all(user_id=user_id, agent_id=agent_id, run_id=run_id)
|
||||
|
||||
def reset(self):
|
||||
"""Reset all memories."""
|
||||
self.memory.reset()
|
||||
|
||||
def normalize_content(self, content):
|
||||
"""Normalize content for consistent storage."""
|
||||
# Example normalization: strip whitespace, convert to lowercase
|
||||
return content.strip().lower()
|
||||
|
||||
def add(self, file_path, user_id=None, agent_id=None, run_id=None, metadata=None):
|
||||
"""Read file content and store it in memory.
|
||||
|
||||
Args:
|
||||
file_path: Can be:
|
||||
- A string path to local file
|
||||
- A URL string
|
||||
- A list containing file paths and/or URLs
|
||||
"""
|
||||
if isinstance(file_path, (list, tuple)):
|
||||
results = []
|
||||
for path in file_path:
|
||||
result = self._process_single_input(path, user_id, agent_id, run_id, metadata)
|
||||
results.extend(result.get('results', []))
|
||||
return {'results': results, 'relations': []}
|
||||
|
||||
return self._process_single_input(file_path, user_id, agent_id, run_id, metadata)
|
||||
|
||||
def _process_single_input(self, input_path, user_id=None, agent_id=None, run_id=None, metadata=None):
|
||||
"""Process a single input which can be a file path or URL."""
|
||||
try:
|
||||
# Define supported file extensions
|
||||
DOCUMENT_EXTENSIONS = {
|
||||
'document': ('.pdf', '.ppt', '.pptx', '.doc', '.docx', '.xls', '.xlsx'),
|
||||
'media': ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.mp3', '.wav', '.ogg', '.m4a'),
|
||||
'text': ('.txt', '.csv', '.json', '.xml', '.md', '.html', '.htm'),
|
||||
'archive': '.zip'
|
||||
}
|
||||
|
||||
# Check if input is URL
|
||||
if isinstance(input_path, str) and (input_path.startswith('http://') or input_path.startswith('https://')):
|
||||
self._log(f"Processing URL: {input_path}")
|
||||
raise NotImplementedError("URL processing not yet implemented")
|
||||
|
||||
# Check if input ends with any supported extension
|
||||
is_supported_file = any(input_path.lower().endswith(ext)
|
||||
for exts in DOCUMENT_EXTENSIONS.values()
|
||||
for ext in (exts if isinstance(exts, tuple) else (exts,)))
|
||||
|
||||
if is_supported_file:
|
||||
self._log(f"Processing as file path: {input_path}")
|
||||
if not os.path.exists(input_path):
|
||||
logger.error(f"File not found: {input_path}")
|
||||
raise FileNotFoundError(f"File not found: {input_path}")
|
||||
|
||||
file_ext = '.' + input_path.lower().split('.')[-1] # Get extension reliably
|
||||
|
||||
# Process file based on type
|
||||
if file_ext in DOCUMENT_EXTENSIONS['text']:
|
||||
with open(input_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read().strip()
|
||||
if not content:
|
||||
raise ValueError("Empty text file")
|
||||
memories = [self.normalize_content(content)]
|
||||
else:
|
||||
# Use MarkItDown for documents and media
|
||||
result = self.markdown.convert(input_path)
|
||||
content = result.text_content
|
||||
if not content:
|
||||
raise ValueError("No content could be extracted from file")
|
||||
chunks = self.chunker.chunk(content)
|
||||
memories = [chunk.text.strip() if hasattr(chunk, 'text') else str(chunk).strip()
|
||||
for chunk in chunks if chunk]
|
||||
|
||||
# Set metadata for file
|
||||
if not metadata:
|
||||
metadata = {}
|
||||
metadata['file_type'] = file_ext.lstrip('.')
|
||||
metadata['filename'] = os.path.basename(input_path)
|
||||
else:
|
||||
# Treat as raw text content only if no file extension
|
||||
memories = [self.normalize_content(input_path)]
|
||||
|
||||
# Create progress display
|
||||
progress = Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
TaskProgressColumn(),
|
||||
transient=True
|
||||
)
|
||||
|
||||
# Store memories with progress bar
|
||||
all_results = []
|
||||
with progress:
|
||||
store_task = progress.add_task(f"Adding to Knowledge from {os.path.basename(input_path)}", total=len(memories))
|
||||
for memory in memories:
|
||||
if memory:
|
||||
memory_result = self.store(memory, user_id=user_id, agent_id=agent_id,
|
||||
run_id=run_id, metadata=metadata)
|
||||
if memory_result:
|
||||
all_results.extend(memory_result.get('results', []))
|
||||
progress.advance(store_task)
|
||||
|
||||
return {'results': all_results, 'relations': []}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing input {input_path}: {str(e)}", exc_info=True)
|
||||
raise
|
||||
@@ -0,0 +1,20 @@
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
# Suppress all relevant logs at module level
|
||||
logging.getLogger("litellm").setLevel(logging.ERROR)
|
||||
logging.getLogger("openai").setLevel(logging.ERROR)
|
||||
logging.getLogger("httpx").setLevel(logging.ERROR)
|
||||
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
||||
logging.getLogger("pydantic").setLevel(logging.ERROR)
|
||||
|
||||
# Suppress pydantic warnings
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic")
|
||||
|
||||
# Configure logging to suppress all INFO messages
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
# Import after suppressing warnings
|
||||
from .llm import LLM, LLMContextLengthExceededException
|
||||
|
||||
__all__ = ["LLM", "LLMContextLengthExceededException"]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,15 @@ from rich.logging import RichHandler
|
||||
from rich.live import Live
|
||||
import asyncio
|
||||
|
||||
# # Configure root logger
|
||||
# logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
# Suppress litellm logs
|
||||
logging.getLogger("litellm").handlers = []
|
||||
logging.getLogger("litellm.utils").handlers = []
|
||||
logging.getLogger("litellm").propagate = False
|
||||
logging.getLogger("litellm.utils").propagate = False
|
||||
|
||||
LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -23,6 +32,10 @@ logging.basicConfig(
|
||||
handlers=[RichHandler(rich_tracebacks=True)]
|
||||
)
|
||||
|
||||
# Add these lines to suppress markdown parser debug logs
|
||||
logging.getLogger('markdown_it').setLevel(logging.WARNING)
|
||||
logging.getLogger('rich.markdown').setLevel(logging.WARNING)
|
||||
|
||||
# Global list to store error logs
|
||||
error_logs = []
|
||||
|
||||
@@ -116,10 +129,10 @@ def display_interaction(message, response, markdown=True, generation_time=None,
|
||||
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
|
||||
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Message", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
|
||||
else:
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Message", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
|
||||
|
||||
def display_self_reflection(message: str, console=None):
|
||||
@@ -135,7 +148,7 @@ def display_self_reflection(message: str, console=None):
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
|
||||
|
||||
def display_instruction(message: str, console=None):
|
||||
def display_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
@@ -146,7 +159,19 @@ def display_instruction(message: str, console=None):
|
||||
if 'instruction' in sync_display_callbacks:
|
||||
sync_display_callbacks['instruction'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
# Display agent info if available
|
||||
if agent_name:
|
||||
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
|
||||
if agent_role:
|
||||
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
|
||||
if agent_tools:
|
||||
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
|
||||
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
|
||||
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
|
||||
|
||||
# Only print if log level is DEBUG
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
|
||||
def display_tool_call(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
@@ -222,10 +247,10 @@ async def adisplay_interaction(message, response, markdown=True, generation_time
|
||||
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
|
||||
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Message", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
|
||||
else:
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Message", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
|
||||
|
||||
async def adisplay_self_reflection(message: str, console=None):
|
||||
@@ -241,7 +266,7 @@ async def adisplay_self_reflection(message: str, console=None):
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
|
||||
|
||||
async def adisplay_instruction(message: str, console=None):
|
||||
async def adisplay_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
|
||||
"""Async version of display_instruction."""
|
||||
if not message or not message.strip():
|
||||
return
|
||||
@@ -252,7 +277,19 @@ async def adisplay_instruction(message: str, console=None):
|
||||
if 'instruction' in async_display_callbacks:
|
||||
await async_display_callbacks['instruction'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
# Display agent info if available
|
||||
if agent_name:
|
||||
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
|
||||
if agent_role:
|
||||
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
|
||||
if agent_tools:
|
||||
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
|
||||
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
|
||||
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
|
||||
|
||||
# Only print if log level is DEBUG
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
|
||||
async def adisplay_tool_call(message: str, console=None):
|
||||
"""Async version of display_tool_call."""
|
||||
@@ -316,7 +353,7 @@ class ReflectionOutput(BaseModel):
|
||||
reflection: str
|
||||
satisfactory: Literal["yes", "no"]
|
||||
|
||||
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
||||
client = OpenAI(api_key=(os.environ["OPENAI_API_KEY"] if os.environ.get("OPENAI_API_KEY") else "xxxx"))
|
||||
|
||||
class TaskOutput(BaseModel):
|
||||
description: str
|
||||
|
||||
@@ -0,0 +1,931 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import json
|
||||
import time
|
||||
import shutil
|
||||
from typing import Any, Dict, List, Optional, Union, Literal
|
||||
import logging
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import chromadb
|
||||
from chromadb.config import Settings as ChromaSettings
|
||||
CHROMADB_AVAILABLE = True
|
||||
except ImportError:
|
||||
CHROMADB_AVAILABLE = False
|
||||
pass
|
||||
|
||||
try:
|
||||
import mem0
|
||||
MEM0_AVAILABLE = True
|
||||
except ImportError:
|
||||
MEM0_AVAILABLE = False
|
||||
|
||||
try:
|
||||
import openai
|
||||
OPENAI_AVAILABLE = True
|
||||
except ImportError:
|
||||
OPENAI_AVAILABLE = False
|
||||
|
||||
|
||||
|
||||
|
||||
class Memory:
|
||||
"""
|
||||
A single-file memory manager covering:
|
||||
- Short-term memory (STM) for ephemeral context
|
||||
- Long-term memory (LTM) for persistent knowledge
|
||||
- Entity memory (structured data about named entities)
|
||||
- User memory (preferences/history for each user)
|
||||
- Quality score logic for deciding which data to store in LTM
|
||||
- Context building from multiple memory sources
|
||||
|
||||
Config example:
|
||||
{
|
||||
"provider": "rag" or "mem0" or "none",
|
||||
"use_embedding": True,
|
||||
"short_db": "short_term.db",
|
||||
"long_db": "long_term.db",
|
||||
"rag_db_path": "rag_db", # optional path for local embedding store
|
||||
"config": {
|
||||
"api_key": "...", # if mem0 usage
|
||||
"org_id": "...",
|
||||
"project_id": "...",
|
||||
...
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], verbose: int = 0):
|
||||
self.cfg = config or {}
|
||||
self.verbose = verbose
|
||||
|
||||
# Set logger level based on verbose
|
||||
if verbose >= 5:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# Also set ChromaDB and OpenAI client loggers to WARNING
|
||||
logging.getLogger('chromadb').setLevel(logging.WARNING)
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
logging.getLogger('chromadb.segment.impl.vector.local_persistent_hnsw').setLevel(logging.ERROR)
|
||||
|
||||
self.provider = self.cfg.get("provider", "rag")
|
||||
self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
|
||||
self.use_rag = (self.provider.lower() == "rag") and CHROMADB_AVAILABLE and self.cfg.get("use_embedding", False)
|
||||
|
||||
# Create .praison directory if it doesn't exist
|
||||
os.makedirs(".praison", exist_ok=True)
|
||||
|
||||
# Short-term DB
|
||||
self.short_db = self.cfg.get("short_db", ".praison/short_term.db")
|
||||
self._init_stm()
|
||||
|
||||
# Long-term DB
|
||||
self.long_db = self.cfg.get("long_db", ".praison/long_term.db")
|
||||
self._init_ltm()
|
||||
|
||||
# Conditionally init Mem0 or local RAG
|
||||
if self.use_mem0:
|
||||
self._init_mem0()
|
||||
elif self.use_rag:
|
||||
self._init_chroma()
|
||||
|
||||
def _log_verbose(self, msg: str, level: int = logging.INFO):
|
||||
"""Only log if verbose >= 5"""
|
||||
if self.verbose >= 5:
|
||||
logger.log(level, msg)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Initialization
|
||||
# -------------------------------------------------------------------------
|
||||
def _init_stm(self):
|
||||
"""Creates or verifies short-term memory table."""
|
||||
os.makedirs(os.path.dirname(self.short_db) or ".", exist_ok=True)
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
c = conn.cursor()
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS short_mem (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT,
|
||||
meta TEXT,
|
||||
created_at REAL
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def _init_ltm(self):
|
||||
"""Creates or verifies long-term memory table."""
|
||||
os.makedirs(os.path.dirname(self.long_db) or ".", exist_ok=True)
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
c = conn.cursor()
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS long_mem (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT,
|
||||
meta TEXT,
|
||||
created_at REAL
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def _init_mem0(self):
|
||||
"""Initialize Mem0 client for agent or user memory."""
|
||||
from mem0 import MemoryClient
|
||||
mem_cfg = self.cfg.get("config", {})
|
||||
api_key = mem_cfg.get("api_key", os.getenv("MEM0_API_KEY"))
|
||||
org_id = mem_cfg.get("org_id")
|
||||
proj_id = mem_cfg.get("project_id")
|
||||
if org_id and proj_id:
|
||||
self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
|
||||
else:
|
||||
self.mem0_client = MemoryClient(api_key=api_key)
|
||||
|
||||
def _init_chroma(self):
|
||||
"""Initialize a local Chroma client for embedding-based search."""
|
||||
try:
|
||||
# Create directory if it doesn't exist
|
||||
rag_path = self.cfg.get("rag_db_path", "chroma_db")
|
||||
os.makedirs(rag_path, exist_ok=True)
|
||||
|
||||
# Initialize ChromaDB with persistent storage
|
||||
self.chroma_client = chromadb.PersistentClient(
|
||||
path=rag_path,
|
||||
settings=ChromaSettings(
|
||||
anonymized_telemetry=False,
|
||||
allow_reset=True
|
||||
)
|
||||
)
|
||||
|
||||
collection_name = "memory_store"
|
||||
try:
|
||||
self.chroma_col = self.chroma_client.get_collection(name=collection_name)
|
||||
self._log_verbose("Using existing ChromaDB collection")
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Collection '{collection_name}' not found. Creating new collection. Error: {e}")
|
||||
self.chroma_col = self.chroma_client.create_collection(
|
||||
name=collection_name,
|
||||
metadata={"hnsw:space": "cosine"}
|
||||
)
|
||||
self._log_verbose("Created new ChromaDB collection")
|
||||
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Failed to initialize ChromaDB: {e}", logging.ERROR)
|
||||
self.use_rag = False
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Basic Quality Score Computation
|
||||
# -------------------------------------------------------------------------
|
||||
def compute_quality_score(
|
||||
self,
|
||||
completeness: float,
|
||||
relevance: float,
|
||||
clarity: float,
|
||||
accuracy: float,
|
||||
weights: Dict[str, float] = None
|
||||
) -> float:
|
||||
"""
|
||||
Combine multiple sub-metrics into one final score, as an example.
|
||||
|
||||
Args:
|
||||
completeness (float): 0-1
|
||||
relevance (float): 0-1
|
||||
clarity (float): 0-1
|
||||
accuracy (float): 0-1
|
||||
weights (Dict[str, float]): optional weighting like {"completeness": 0.25, "relevance": 0.3, ...}
|
||||
|
||||
Returns:
|
||||
float: Weighted average 0-1
|
||||
"""
|
||||
if not weights:
|
||||
weights = {
|
||||
"completeness": 0.25,
|
||||
"relevance": 0.25,
|
||||
"clarity": 0.25,
|
||||
"accuracy": 0.25
|
||||
}
|
||||
total = (completeness * weights["completeness"]
|
||||
+ relevance * weights["relevance"]
|
||||
+ clarity * weights["clarity"]
|
||||
+ accuracy * weights["accuracy"]
|
||||
)
|
||||
return round(total, 3) # e.g. round to 3 decimal places
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Short-Term Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_short_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Dict[str, Any] = None,
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
):
|
||||
"""Store in short-term memory with optional quality metrics"""
|
||||
logger.info(f"Storing in short-term memory: {text[:100]}...")
|
||||
logger.info(f"Metadata: {metadata}")
|
||||
|
||||
metadata = self._process_quality_metrics(
|
||||
metadata, completeness, relevance, clarity,
|
||||
accuracy, weights, evaluator_quality
|
||||
)
|
||||
logger.info(f"Processed metadata: {metadata}")
|
||||
|
||||
# Existing store logic
|
||||
try:
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
ident = str(time.time_ns())
|
||||
conn.execute(
|
||||
"INSERT INTO short_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
|
||||
(ident, text, json.dumps(metadata), time.time())
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Successfully stored in short-term memory with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in short-term memory: {e}")
|
||||
raise
|
||||
|
||||
def search_short_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
min_quality: float = 0.0,
|
||||
relevance_cutoff: float = 0.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search short-term memory with optional quality filter"""
|
||||
self._log_verbose(f"Searching short memory for: {query}")
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
results = self.mem0_client.search(query=query, limit=limit)
|
||||
filtered = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
|
||||
return filtered
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
|
||||
response = client.embeddings.create(
|
||||
input=query,
|
||||
model="text-embedding-3-small"
|
||||
)
|
||||
query_embedding = response.data[0].embedding
|
||||
|
||||
resp = self.chroma_col.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=limit
|
||||
)
|
||||
|
||||
results = []
|
||||
if resp["ids"]:
|
||||
for i in range(len(resp["ids"][0])):
|
||||
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
|
||||
quality = metadata.get("quality", 0.0)
|
||||
score = 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
|
||||
if quality >= min_quality and score >= relevance_cutoff:
|
||||
results.append({
|
||||
"id": resp["ids"][0][i],
|
||||
"text": resp["documents"][0][i],
|
||||
"metadata": metadata,
|
||||
"score": score
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
|
||||
return []
|
||||
|
||||
else:
|
||||
# Local fallback
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
c = conn.cursor()
|
||||
rows = c.execute(
|
||||
"SELECT id, content, meta FROM short_mem WHERE content LIKE ? LIMIT ?",
|
||||
(f"%{query}%", limit)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
results = []
|
||||
for row in rows:
|
||||
meta = json.loads(row[2] or "{}")
|
||||
quality = meta.get("quality", 0.0)
|
||||
if quality >= min_quality:
|
||||
results.append({
|
||||
"id": row[0],
|
||||
"text": row[1],
|
||||
"metadata": meta
|
||||
})
|
||||
return results
|
||||
|
||||
def reset_short_term(self):
|
||||
"""Completely clears short-term memory."""
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
conn.execute("DELETE FROM short_mem")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Long-Term Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def _sanitize_metadata(self, metadata: Dict) -> Dict:
|
||||
"""Sanitize metadata for ChromaDB - convert to acceptable types"""
|
||||
sanitized = {}
|
||||
for k, v in metadata.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, (str, int, float, bool)):
|
||||
sanitized[k] = v
|
||||
elif isinstance(v, dict):
|
||||
# Convert dict to string representation
|
||||
sanitized[k] = str(v)
|
||||
else:
|
||||
# Convert other types to string
|
||||
sanitized[k] = str(v)
|
||||
return sanitized
|
||||
|
||||
def store_long_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Dict[str, Any] = None,
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
):
|
||||
"""Store in long-term memory with optional quality metrics"""
|
||||
logger.info(f"Storing in long-term memory: {text[:100]}...")
|
||||
logger.info(f"Initial metadata: {metadata}")
|
||||
|
||||
# Process metadata
|
||||
metadata = metadata or {}
|
||||
metadata = self._process_quality_metrics(
|
||||
metadata, completeness, relevance, clarity,
|
||||
accuracy, weights, evaluator_quality
|
||||
)
|
||||
logger.info(f"Processed metadata: {metadata}")
|
||||
|
||||
# Generate unique ID
|
||||
ident = str(time.time_ns())
|
||||
created = time.time()
|
||||
|
||||
# Store in SQLite
|
||||
try:
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
conn.execute(
|
||||
"INSERT INTO long_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
|
||||
(ident, text, json.dumps(metadata), created)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Successfully stored in SQLite with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in SQLite: {e}")
|
||||
return
|
||||
|
||||
# Store in vector database if enabled
|
||||
if self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Ensure API key is correctly set
|
||||
|
||||
logger.info("Getting embeddings from OpenAI...")
|
||||
logger.debug(f"Embedding input text: {text}") # Log the input text
|
||||
|
||||
response = client.embeddings.create(
|
||||
input=text,
|
||||
model="text-embedding-3-small"
|
||||
)
|
||||
embedding = response.data[0].embedding
|
||||
logger.info("Successfully got embeddings")
|
||||
logger.debug(f"Received embedding of length: {len(embedding)}") # Log embedding details
|
||||
|
||||
# Sanitize metadata for ChromaDB
|
||||
sanitized_metadata = self._sanitize_metadata(metadata)
|
||||
|
||||
# Store in ChromaDB with embedding
|
||||
self.chroma_col.add(
|
||||
documents=[text],
|
||||
metadatas=[sanitized_metadata],
|
||||
ids=[ident],
|
||||
embeddings=[embedding]
|
||||
)
|
||||
logger.info(f"Successfully stored in ChromaDB with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in ChromaDB: {e}")
|
||||
|
||||
elif self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
try:
|
||||
self.mem0_client.add(text, metadata=metadata)
|
||||
logger.info("Successfully stored in Mem0")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in Mem0: {e}")
|
||||
|
||||
|
||||
def search_long_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
relevance_cutoff: float = 0.0,
|
||||
min_quality: float = 0.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search long-term memory with optional quality filter"""
|
||||
self._log_verbose(f"Searching long memory for: {query}")
|
||||
self._log_verbose(f"Min quality: {min_quality}")
|
||||
|
||||
found = []
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
results = self.mem0_client.search(query=query, limit=limit)
|
||||
# Filter by quality
|
||||
filtered = [r for r in results if r.get("metadata", {}).get("quality", 0.0) >= min_quality]
|
||||
logger.info(f"Found {len(filtered)} results in Mem0")
|
||||
return filtered
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
|
||||
# Get query embedding
|
||||
response = client.embeddings.create(
|
||||
input=query,
|
||||
model="text-embedding-3-small" # Using consistent model
|
||||
)
|
||||
query_embedding = response.data[0].embedding
|
||||
|
||||
# Search ChromaDB with embedding
|
||||
resp = self.chroma_col.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=limit,
|
||||
include=["documents", "metadatas", "distances"]
|
||||
)
|
||||
|
||||
results = []
|
||||
if resp["ids"]:
|
||||
for i in range(len(resp["ids"][0])):
|
||||
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
|
||||
text = resp["documents"][0][i]
|
||||
# Add memory record citation
|
||||
text = f"{text} (Memory record: {text})"
|
||||
found.append({
|
||||
"id": resp["ids"][0][i],
|
||||
"text": text,
|
||||
"metadata": metadata,
|
||||
"score": 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
|
||||
})
|
||||
logger.info(f"Found {len(found)} results in ChromaDB")
|
||||
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
|
||||
|
||||
# Always try SQLite as fallback or additional source
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
c = conn.cursor()
|
||||
rows = c.execute(
|
||||
"SELECT id, content, meta, created_at FROM long_mem WHERE content LIKE ? LIMIT ?",
|
||||
(f"%{query}%", limit)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
for row in rows:
|
||||
meta = json.loads(row[2] or "{}")
|
||||
text = row[1]
|
||||
# Add memory record citation if not already present
|
||||
if "(Memory record:" not in text:
|
||||
text = f"{text} (Memory record: {text})"
|
||||
# Only add if not already found by ChromaDB/Mem0
|
||||
if not any(f["id"] == row[0] for f in found):
|
||||
found.append({
|
||||
"id": row[0],
|
||||
"text": text,
|
||||
"metadata": meta,
|
||||
"created_at": row[3]
|
||||
})
|
||||
logger.info(f"Found {len(found)} total results after SQLite")
|
||||
|
||||
results = found
|
||||
|
||||
# Filter by quality if needed
|
||||
if min_quality > 0:
|
||||
self._log_verbose(f"Found {len(results)} initial results")
|
||||
results = [
|
||||
r for r in results
|
||||
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
|
||||
]
|
||||
self._log_verbose(f"After quality filter: {len(results)} results")
|
||||
|
||||
# Apply relevance cutoff if specified
|
||||
if relevance_cutoff > 0:
|
||||
results = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
|
||||
logger.info(f"After relevance filter: {len(results)} results")
|
||||
|
||||
return results[:limit]
|
||||
|
||||
def reset_long_term(self):
|
||||
"""Clear local LTM DB, plus Chroma or mem0 if in use."""
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
conn.execute("DELETE FROM long_mem")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
# Mem0 has no universal reset API. Could implement partial or no-op.
|
||||
pass
|
||||
if self.use_rag and hasattr(self, "chroma_client"):
|
||||
self.chroma_client.reset() # entire DB
|
||||
self._init_chroma() # re-init fresh
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Entity Memory Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_entity(self, name: str, type_: str, desc: str, relations: str):
|
||||
"""
|
||||
Save entity info in LTM (or mem0/rag).
|
||||
We'll label the metadata type = entity for easy filtering.
|
||||
"""
|
||||
data = f"Entity {name}({type_}): {desc} | relationships: {relations}"
|
||||
self.store_long_term(data, metadata={"category": "entity"})
|
||||
|
||||
def search_entity(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Filter to items that have metadata 'category=entity'.
|
||||
"""
|
||||
all_hits = self.search_long_term(query, limit=20) # gather more
|
||||
ents = []
|
||||
for h in all_hits:
|
||||
meta = h.get("metadata") or {}
|
||||
if meta.get("category") == "entity":
|
||||
ents.append(h)
|
||||
return ents[:limit]
|
||||
|
||||
def reset_entity_only(self):
|
||||
"""
|
||||
If you only want to drop entity items from LTM, you'd do a custom
|
||||
delete from local DB where meta LIKE '%category=entity%'.
|
||||
For brevity, we do a full LTM reset here.
|
||||
"""
|
||||
self.reset_long_term()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# User Memory Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_user_memory(self, user_id: str, text: str, extra: Dict[str, Any] = None):
|
||||
"""
|
||||
If mem0 is used, do user-based addition. Otherwise store in LTM with user in metadata.
|
||||
"""
|
||||
meta = {"user_id": user_id}
|
||||
if extra:
|
||||
meta.update(extra)
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
self.mem0_client.add(text, user_id=user_id, metadata=meta)
|
||||
else:
|
||||
self.store_long_term(text, metadata=meta)
|
||||
|
||||
def search_user_memory(self, user_id: str, query: str, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
If mem0 is used, pass user_id in. Otherwise fallback to local filter on user in metadata.
|
||||
"""
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
return self.mem0_client.search(query=query, limit=limit, user_id=user_id)
|
||||
else:
|
||||
hits = self.search_long_term(query, limit=20)
|
||||
filtered = []
|
||||
for h in hits:
|
||||
meta = h.get("metadata", {})
|
||||
if meta.get("user_id") == user_id:
|
||||
filtered.append(h)
|
||||
return filtered[:limit]
|
||||
|
||||
def reset_user_memory(self):
|
||||
"""
|
||||
Clear all user-based info. For simplicity, we do a full LTM reset.
|
||||
Real usage might filter only metadata "user_id".
|
||||
"""
|
||||
self.reset_long_term()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Putting it all Together: Task Finalization
|
||||
# -------------------------------------------------------------------------
|
||||
def finalize_task_output(
|
||||
self,
|
||||
content: str,
|
||||
agent_name: str,
|
||||
quality_score: float,
|
||||
threshold: float = 0.7,
|
||||
metrics: Dict[str, Any] = None,
|
||||
task_id: str = None
|
||||
):
|
||||
"""Store task output in memory with appropriate metadata"""
|
||||
logger.info(f"Finalizing task output: {content[:100]}...")
|
||||
logger.info(f"Agent: {agent_name}, Quality: {quality_score}, Threshold: {threshold}")
|
||||
|
||||
metadata = {
|
||||
"task_id": task_id,
|
||||
"agent": agent_name,
|
||||
"quality": quality_score,
|
||||
"metrics": metrics,
|
||||
"task_type": "output",
|
||||
"stored_at": time.time()
|
||||
}
|
||||
logger.info(f"Prepared metadata: {metadata}")
|
||||
|
||||
# Always store in short-term memory
|
||||
try:
|
||||
logger.info("Storing in short-term memory...")
|
||||
self.store_short_term(
|
||||
text=content,
|
||||
metadata=metadata
|
||||
)
|
||||
logger.info("Successfully stored in short-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in short-term memory: {e}")
|
||||
|
||||
# Store in long-term memory if quality meets threshold
|
||||
if quality_score >= threshold:
|
||||
try:
|
||||
logger.info(f"Quality score {quality_score} >= {threshold}, storing in long-term memory...")
|
||||
self.store_long_term(
|
||||
text=content,
|
||||
metadata=metadata
|
||||
)
|
||||
logger.info("Successfully stored in long-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in long-term memory: {e}")
|
||||
else:
|
||||
logger.info(f"Quality score {quality_score} < {threshold}, skipping long-term storage")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Building Context (Short, Long, Entities, User)
|
||||
# -------------------------------------------------------------------------
|
||||
def build_context_for_task(
|
||||
self,
|
||||
task_descr: str,
|
||||
user_id: Optional[str] = None,
|
||||
additional: str = "",
|
||||
max_items: int = 3
|
||||
) -> str:
|
||||
"""
|
||||
Merges relevant short-term, long-term, entity, user memories
|
||||
into a single text block with deduplication and clean formatting.
|
||||
"""
|
||||
q = (task_descr + " " + additional).strip()
|
||||
lines = []
|
||||
seen_contents = set() # Track unique contents
|
||||
|
||||
def normalize_content(content: str) -> str:
|
||||
"""Normalize content for deduplication"""
|
||||
# Extract just the main content without citations for comparison
|
||||
normalized = content.split("(Memory record:")[0].strip()
|
||||
# Keep more characters to reduce false duplicates
|
||||
normalized = ''.join(c.lower() for c in normalized if not c.isspace())
|
||||
return normalized
|
||||
|
||||
def format_content(content: str, max_len: int = 150) -> str:
|
||||
"""Format content with clean truncation at word boundaries"""
|
||||
if not content:
|
||||
return ""
|
||||
|
||||
# Clean up content by removing extra whitespace and newlines
|
||||
content = ' '.join(content.split())
|
||||
|
||||
# If content contains a memory citation, preserve it
|
||||
if "(Memory record:" in content:
|
||||
return content # Keep original citation format
|
||||
|
||||
# Regular content truncation
|
||||
if len(content) <= max_len:
|
||||
return content
|
||||
|
||||
truncate_at = content.rfind(' ', 0, max_len - 3)
|
||||
if truncate_at == -1:
|
||||
truncate_at = max_len - 3
|
||||
return content[:truncate_at] + "..."
|
||||
|
||||
def add_section(title: str, hits: List[Any]) -> None:
|
||||
"""Add a section of memory hits with deduplication"""
|
||||
if not hits:
|
||||
return
|
||||
|
||||
formatted_hits = []
|
||||
for h in hits:
|
||||
content = h.get('text', '') if isinstance(h, dict) else str(h)
|
||||
if not content:
|
||||
continue
|
||||
|
||||
# Keep original format if it has a citation
|
||||
if "(Memory record:" in content:
|
||||
formatted = content
|
||||
else:
|
||||
formatted = format_content(content)
|
||||
|
||||
# Only add if we haven't seen this normalized content before
|
||||
normalized = normalize_content(formatted)
|
||||
if normalized not in seen_contents:
|
||||
seen_contents.add(normalized)
|
||||
formatted_hits.append(formatted)
|
||||
|
||||
if formatted_hits:
|
||||
# Add section header
|
||||
if lines:
|
||||
lines.append("") # Space before new section
|
||||
lines.append(title)
|
||||
lines.append("=" * len(title)) # Underline the title
|
||||
lines.append("") # Space after title
|
||||
|
||||
# Add formatted content with bullet points
|
||||
for content in formatted_hits:
|
||||
lines.append(f" • {content}")
|
||||
|
||||
# Add each section
|
||||
# First get all results
|
||||
short_term = self.search_short_term(q, limit=max_items)
|
||||
long_term = self.search_long_term(q, limit=max_items)
|
||||
entities = self.search_entity(q, limit=max_items)
|
||||
user_mem = self.search_user_memory(user_id, q, limit=max_items) if user_id else []
|
||||
|
||||
# Add sections in order of priority
|
||||
add_section("Short-term Memory Context", short_term)
|
||||
add_section("Long-term Memory Context", long_term)
|
||||
add_section("Entity Context", entities)
|
||||
if user_id:
|
||||
add_section("User Context", user_mem)
|
||||
|
||||
return "\n".join(lines) if lines else ""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Master Reset (Everything)
|
||||
# -------------------------------------------------------------------------
|
||||
def reset_all(self):
|
||||
"""
|
||||
Fully wipes short-term, long-term, and any memory in mem0 or rag.
|
||||
"""
|
||||
self.reset_short_term()
|
||||
self.reset_long_term()
|
||||
# Entities & user memory are stored in LTM or mem0, so no separate step needed.
|
||||
|
||||
def _process_quality_metrics(
|
||||
self,
|
||||
metadata: Dict[str, Any],
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Process and store quality metrics in metadata"""
|
||||
metadata = metadata or {}
|
||||
|
||||
# Handle sub-metrics if provided
|
||||
if None not in [completeness, relevance, clarity, accuracy]:
|
||||
metadata.update({
|
||||
"completeness": completeness,
|
||||
"relevance": relevance,
|
||||
"clarity": clarity,
|
||||
"accuracy": accuracy,
|
||||
"quality": self.compute_quality_score(
|
||||
completeness, relevance, clarity, accuracy, weights
|
||||
)
|
||||
})
|
||||
# Handle external evaluator quality if provided
|
||||
elif evaluator_quality is not None:
|
||||
metadata["quality"] = evaluator_quality
|
||||
|
||||
return metadata
|
||||
|
||||
def calculate_quality_metrics(
|
||||
self,
|
||||
output: str,
|
||||
expected_output: str,
|
||||
llm: Optional[str] = None,
|
||||
custom_prompt: Optional[str] = None
|
||||
) -> Dict[str, float]:
|
||||
"""Calculate quality metrics using LLM"""
|
||||
logger.info("Calculating quality metrics for output")
|
||||
logger.info(f"Output: {output[:100]}...")
|
||||
logger.info(f"Expected: {expected_output[:100]}...")
|
||||
|
||||
# Default evaluation prompt
|
||||
default_prompt = f"""
|
||||
Evaluate the following output against expected output.
|
||||
Score each metric from 0.0 to 1.0:
|
||||
- Completeness: Does it address all requirements?
|
||||
- Relevance: Does it match expected output?
|
||||
- Clarity: Is it clear and well-structured?
|
||||
- Accuracy: Is it factually correct?
|
||||
|
||||
Expected: {expected_output}
|
||||
Actual: {output}
|
||||
|
||||
Return ONLY a JSON with these keys: completeness, relevance, clarity, accuracy
|
||||
Example: {{"completeness": 0.95, "relevance": 0.8, "clarity": 0.9, "accuracy": 0.85}}
|
||||
"""
|
||||
|
||||
try:
|
||||
# Use OpenAI client from main.py
|
||||
from ..main import client
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=llm or "gpt-4o",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": custom_prompt or default_prompt
|
||||
}],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.3
|
||||
)
|
||||
|
||||
metrics = json.loads(response.choices[0].message.content)
|
||||
|
||||
# Validate metrics
|
||||
required = ["completeness", "relevance", "clarity", "accuracy"]
|
||||
if not all(k in metrics for k in required):
|
||||
raise ValueError("Missing required metrics in LLM response")
|
||||
|
||||
logger.info(f"Calculated metrics: {metrics}")
|
||||
return metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating metrics: {e}")
|
||||
return {
|
||||
"completeness": 0.0,
|
||||
"relevance": 0.0,
|
||||
"clarity": 0.0,
|
||||
"accuracy": 0.0
|
||||
}
|
||||
|
||||
def store_quality(
|
||||
self,
|
||||
text: str,
|
||||
quality_score: float,
|
||||
task_id: Optional[str] = None,
|
||||
iteration: Optional[int] = None,
|
||||
metrics: Optional[Dict[str, float]] = None,
|
||||
memory_type: Literal["short", "long"] = "long"
|
||||
) -> None:
|
||||
"""Store quality metrics in memory"""
|
||||
logger.info(f"Attempting to store in {memory_type} memory: {text[:100]}...")
|
||||
|
||||
metadata = {
|
||||
"quality": quality_score,
|
||||
"task_id": task_id,
|
||||
"iteration": iteration
|
||||
}
|
||||
|
||||
if metrics:
|
||||
metadata.update({
|
||||
k: v for k, v in metrics.items() # Remove metric_ prefix
|
||||
})
|
||||
|
||||
logger.info(f"With metadata: {metadata}")
|
||||
|
||||
try:
|
||||
if memory_type == "short":
|
||||
self.store_short_term(text, metadata=metadata)
|
||||
logger.info("Successfully stored in short-term memory")
|
||||
else:
|
||||
self.store_long_term(text, metadata=metadata)
|
||||
logger.info("Successfully stored in long-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in memory: {e}")
|
||||
|
||||
def search_with_quality(
|
||||
self,
|
||||
query: str,
|
||||
min_quality: float = 0.0,
|
||||
memory_type: Literal["short", "long"] = "long",
|
||||
limit: int = 5
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search with quality filter"""
|
||||
logger.info(f"Searching {memory_type} memory for: {query}")
|
||||
logger.info(f"Min quality: {min_quality}")
|
||||
|
||||
search_func = (
|
||||
self.search_short_term if memory_type == "short"
|
||||
else self.search_long_term
|
||||
)
|
||||
|
||||
results = search_func(query, limit=limit)
|
||||
logger.info(f"Found {len(results)} initial results")
|
||||
|
||||
filtered = [
|
||||
r for r in results
|
||||
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
|
||||
]
|
||||
logger.info(f"After quality filter: {len(filtered)} results")
|
||||
|
||||
return filtered
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,20 +1,25 @@
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine
|
||||
from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine, Literal
|
||||
from pydantic import BaseModel
|
||||
from ..main import TaskOutput
|
||||
from ..agent.agent import Agent
|
||||
import uuid
|
||||
import os
|
||||
import time
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Task:
|
||||
def __init__(
|
||||
self,
|
||||
description: str,
|
||||
expected_output: str,
|
||||
expected_output: Optional[str] = None,
|
||||
agent: Optional[Agent] = None,
|
||||
name: Optional[str] = None,
|
||||
tools: Optional[List[Any]] = None,
|
||||
context: Optional[List["Task"]] = None,
|
||||
context: Optional[List[Union[str, List, 'Task']]] = None,
|
||||
async_execution: Optional[bool] = False,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
output_file: Optional[str] = None,
|
||||
@@ -30,12 +35,28 @@ class Task:
|
||||
task_type: str = "task",
|
||||
condition: Optional[Dict[str, List[str]]] = None,
|
||||
is_start: bool = False,
|
||||
loop_state: Optional[Dict[str, Union[str, int]]] = None
|
||||
loop_state: Optional[Dict[str, Union[str, int]]] = None,
|
||||
memory=None,
|
||||
quality_check=True,
|
||||
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')):
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
MEMORY_AVAILABLE = True
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Memory features requested in Task but memory dependencies not installed. "
|
||||
"Please install with: pip install \"praisonaiagents[memory]\""
|
||||
)
|
||||
|
||||
self.input_file = input_file
|
||||
self.id = str(uuid.uuid4()) if id is None else str(id)
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.expected_output = expected_output
|
||||
self.expected_output = expected_output if expected_output is not None else "Complete the task successfully"
|
||||
self.agent = agent
|
||||
self.tools = tools if tools else []
|
||||
self.context = context if context else []
|
||||
@@ -54,6 +75,22 @@ class Task:
|
||||
self.condition = condition if condition else {}
|
||||
self.is_start = is_start
|
||||
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)
|
||||
if verbose >= 5:
|
||||
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)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
|
||||
if self.output_json and self.output_pydantic:
|
||||
raise ValueError("Only one output type can be defined")
|
||||
@@ -61,14 +98,227 @@ class Task:
|
||||
# Track previous tasks based on next_tasks relationships
|
||||
self.previous_tasks = []
|
||||
|
||||
# If task_type="decision" and output_pydantic is not set
|
||||
if self.task_type == "decision" and not self.output_pydantic:
|
||||
from pydantic import BaseModel
|
||||
from typing import Literal
|
||||
|
||||
# Gather condition keys for the "decision" field
|
||||
condition_keys = list(self.condition.keys())
|
||||
if not condition_keys:
|
||||
# Fall back to placeholders if nothing is specified
|
||||
condition_keys = ["next_task", "exit"]
|
||||
|
||||
# Create a dynamic literal type from condition keys
|
||||
DecisionLiteral = Literal.__getitem__(tuple(condition_keys))
|
||||
|
||||
class DecisionModel(BaseModel):
|
||||
response: str
|
||||
decision: DecisionLiteral
|
||||
|
||||
self.output_pydantic = DecisionModel
|
||||
|
||||
# If task_type="loop" and output_pydantic is not set
|
||||
if self.task_type == "loop" and not self.output_pydantic:
|
||||
from pydantic import BaseModel
|
||||
from typing import Literal
|
||||
|
||||
# Gather condition keys for the "decision" field
|
||||
condition_keys = list(self.condition.keys())
|
||||
if not condition_keys:
|
||||
# Fall back to placeholders if nothing is specified
|
||||
condition_keys = ["next_item", "exit"]
|
||||
|
||||
# Create a dynamic literal type
|
||||
LoopLiteral = Literal.__getitem__(tuple(condition_keys))
|
||||
|
||||
class LoopModel(BaseModel):
|
||||
response: str
|
||||
decision: LoopLiteral
|
||||
loop_id: str # Additional field for loop
|
||||
|
||||
self.output_pydantic = LoopModel
|
||||
|
||||
def __str__(self):
|
||||
return f"Task(name='{self.name if self.name else 'None'}', description='{self.description}', agent='{self.agent.name if self.agent else 'None'}', status='{self.status}')"
|
||||
|
||||
def initialize_memory(self):
|
||||
"""Initialize memory if config exists but memory doesn't"""
|
||||
if not self.memory and self.config.get('memory_config'):
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
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")
|
||||
else:
|
||||
logger.error(f"Task {self.id}: Failed to create memory database!")
|
||||
return self.memory
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to initialize memory: {e}")
|
||||
logger.exception(e)
|
||||
return None
|
||||
|
||||
def store_in_memory(self, content: str, agent_name: str = None, task_id: str = None):
|
||||
"""Store content in memory with metadata"""
|
||||
if self.memory:
|
||||
try:
|
||||
logger.info(f"Task {self.id}: Storing content in memory...")
|
||||
self.memory.store_long_term(
|
||||
text=content,
|
||||
metadata={
|
||||
"agent_name": agent_name or "Agent",
|
||||
"task_id": task_id or self.id,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
)
|
||||
logger.info(f"Task {self.id}: Content stored in memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to store content in memory: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
async def execute_callback(self, task_output: TaskOutput) -> None:
|
||||
"""Execute the callback function, handling both sync and async callbacks"""
|
||||
"""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}")
|
||||
# Store task output in memory
|
||||
try:
|
||||
logger.info(f"Task {self.id}: Storing task output in memory...")
|
||||
self.store_in_memory(
|
||||
content=task_output.raw,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
task_id=self.id
|
||||
)
|
||||
logger.info(f"Task {self.id}: Task output stored in memory")
|
||||
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(
|
||||
content=task_output.raw,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
quality_score=quality_score,
|
||||
threshold=0.7, # Only high quality outputs in long-term memory
|
||||
metrics=metrics,
|
||||
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(
|
||||
text=task_output.raw,
|
||||
quality_score=quality_score,
|
||||
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,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
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...")
|
||||
context = self.memory.build_context_for_task(
|
||||
task_descr=task_output.raw,
|
||||
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}")
|
||||
logger.exception(e) # Print full stack trace
|
||||
# Continue execution even if memory operations fail
|
||||
|
||||
# Execute original callback
|
||||
if self.callback:
|
||||
if asyncio.iscoroutinefunction(self.callback):
|
||||
await self.callback(task_output)
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(self.callback):
|
||||
await self.callback(task_output)
|
||||
else:
|
||||
self.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to execute callback: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
task_prompt = f"""
|
||||
You need to do the following task: {self.description}.
|
||||
Expected Output: {self.expected_output}.
|
||||
"""
|
||||
if self.context:
|
||||
context_results = [] # Use list to avoid duplicates
|
||||
for context_item in self.context:
|
||||
if isinstance(context_item, str):
|
||||
context_results.append(f"Input Content:\n{context_item}")
|
||||
elif isinstance(context_item, list):
|
||||
context_results.append(f"Input Content: {' '.join(str(x) for x in context_item)}")
|
||||
elif hasattr(context_item, 'result'): # Task object
|
||||
if context_item.result:
|
||||
context_results.append(
|
||||
f"Result of previous task {context_item.name if context_item.name else context_item.description}:\n{context_item.result.raw}"
|
||||
)
|
||||
else:
|
||||
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 = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, self.callback, task_output)
|
||||
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))
|
||||
@@ -0,0 +1,149 @@
|
||||
# PraisonAI Tools Guide
|
||||
|
||||
Welcome to the PraisonAI Tools directory! This guide will help you understand how our tools work and how to create new ones, whether you're a beginner or an experienced programmer.
|
||||
|
||||
## What is a Tool?
|
||||
|
||||
A tool is a piece of code that helps our AI agents perform specific tasks. Think of tools as special abilities that we give to our agents. For example:
|
||||
- An internet search tool lets agents search the web
|
||||
- A stock market tool lets agents check stock prices
|
||||
- A weather tool lets agents check the weather
|
||||
|
||||
## Creating New Tools: The Two Approaches
|
||||
|
||||
### 1. Function-Based Approach (Simple Tools)
|
||||
|
||||
Best for simple tools that do one specific thing. Like a calculator that just adds numbers.
|
||||
|
||||
**When to use:**
|
||||
- Tool does one simple task
|
||||
- Doesn't need to remember information between uses
|
||||
- Doesn't need to share information with other tools
|
||||
- Quick, one-time operations
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
def internet_search(query: str):
|
||||
# Search the internet and return results
|
||||
return search_results
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from praisonaiagents.tools import internet_search
|
||||
|
||||
results = internet_search("AI news")
|
||||
```
|
||||
|
||||
### 2. Class-Based Approach (Complex Tools)
|
||||
|
||||
Best for tools that do multiple related things or need to remember information. Like a smart calculator that remembers your previous calculations and can do many different math operations.
|
||||
|
||||
**When to use:**
|
||||
- Tool has multiple related functions
|
||||
- Needs to remember or share information
|
||||
- Needs to manage resources efficiently
|
||||
- Has complex setup requirements
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
class StockTools:
|
||||
def get_stock_price(self, symbol):
|
||||
# Get current stock price
|
||||
return price
|
||||
|
||||
def get_stock_info(self, symbol):
|
||||
# Get detailed stock information
|
||||
return info
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from praisonaiagents.tools import get_stock_price, get_stock_info
|
||||
|
||||
price = get_stock_price("AAPL")
|
||||
info = get_stock_info("AAPL")
|
||||
```
|
||||
|
||||
## How to Choose Your Approach
|
||||
|
||||
Ask yourself these questions:
|
||||
|
||||
1. **Is your tool doing one simple thing?**
|
||||
- Yes → Use Function-Based Approach
|
||||
- No → Consider Class-Based Approach
|
||||
|
||||
2. **Does your tool need to remember information?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
3. **Are your tool's operations related to each other?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
4. **Does your tool need to manage resources efficiently?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### Internet Search Tool (Function-Based)
|
||||
- Does one thing: searches the internet
|
||||
- Doesn't need to remember previous searches
|
||||
- Each search is independent
|
||||
- Simple input/output operation
|
||||
|
||||
### Stock Market Tool (Class-Based)
|
||||
- Does multiple things: check prices, get company info, get historical data
|
||||
- Remembers stock information to avoid repeated downloads
|
||||
- Operations are related (all about stocks)
|
||||
- Manages connections efficiently
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Choose Your Approach** based on the guidelines above
|
||||
|
||||
2. **Create Your Tool File**:
|
||||
- Name it descriptively (e.g., `weather_tools.py`)
|
||||
- Place it in the `praisonaiagents/tools` directory
|
||||
|
||||
3. **Write Your Tool**:
|
||||
- Add clear documentation
|
||||
- Include type hints for better understanding
|
||||
- Handle errors gracefully
|
||||
|
||||
4. **Test Your Tool**:
|
||||
- Make sure it works as expected
|
||||
- Test error cases
|
||||
- Check performance
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Documentation**:
|
||||
- Explain what your tool does
|
||||
- Provide examples
|
||||
- List any requirements
|
||||
|
||||
2. **Error Handling**:
|
||||
- Always handle possible errors
|
||||
- Return helpful error messages
|
||||
- Don't let your tool crash
|
||||
|
||||
3. **Performance**:
|
||||
- Keep it efficient
|
||||
- Don't waste resources
|
||||
- Cache when helpful
|
||||
|
||||
4. **User-Friendly**:
|
||||
- Make it easy to use
|
||||
- Use clear function/method names
|
||||
- Keep it simple
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check existing tools for examples
|
||||
- Ask in our community
|
||||
- Read the documentation
|
||||
- Don't hesitate to ask questions!
|
||||
|
||||
Remember: The goal is to make tools that are easy to use and maintain. Choose the approach that makes the most sense for your specific tool's needs.
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Tools package for PraisonAI Agents"""
|
||||
from importlib import import_module
|
||||
from typing import Any
|
||||
|
||||
# Map of function names to their module and class (if any)
|
||||
TOOL_MAPPINGS = {
|
||||
# Direct functions
|
||||
'internet_search': ('.duckduckgo_tools', None),
|
||||
'duckduckgo': ('.duckduckgo_tools', None),
|
||||
|
||||
# arXiv Tools
|
||||
'search_arxiv': ('.arxiv_tools', None),
|
||||
'get_arxiv_paper': ('.arxiv_tools', None),
|
||||
'get_papers_by_author': ('.arxiv_tools', None),
|
||||
'get_papers_by_category': ('.arxiv_tools', None),
|
||||
'arxiv_tools': ('.arxiv_tools', None),
|
||||
|
||||
# Wikipedia Tools
|
||||
'wiki_search': ('.wikipedia_tools', None),
|
||||
'wiki_summary': ('.wikipedia_tools', None),
|
||||
'wiki_page': ('.wikipedia_tools', None),
|
||||
'wiki_random': ('.wikipedia_tools', None),
|
||||
'wiki_language': ('.wikipedia_tools', None),
|
||||
'wikipedia_tools': ('.wikipedia_tools', None),
|
||||
|
||||
# Newspaper Tools
|
||||
'get_article': ('.newspaper_tools', None),
|
||||
'get_news_sources': ('.newspaper_tools', None),
|
||||
'get_articles_from_source': ('.newspaper_tools', None),
|
||||
'get_trending_topics': ('.newspaper_tools', None),
|
||||
'newspaper_tools': ('.newspaper_tools', None),
|
||||
|
||||
# Spider Tools
|
||||
'scrape_page': ('.spider_tools', None),
|
||||
'extract_links': ('.spider_tools', None),
|
||||
'crawl': ('.spider_tools', None),
|
||||
'extract_text': ('.spider_tools', None),
|
||||
'spider_tools': ('.spider_tools', None),
|
||||
|
||||
# DuckDB Tools
|
||||
'query': ('.duckdb_tools', None),
|
||||
'create_table': ('.duckdb_tools', None),
|
||||
'load_data': ('.duckdb_tools', None),
|
||||
'export_data': ('.duckdb_tools', None),
|
||||
'get_table_info': ('.duckdb_tools', None),
|
||||
'analyze_data': ('.duckdb_tools', None),
|
||||
'duckdb_tools': ('.duckdb_tools', None),
|
||||
|
||||
# Shell Tools
|
||||
'execute_command': ('.shell_tools', None),
|
||||
'list_processes': ('.shell_tools', None),
|
||||
'kill_process': ('.shell_tools', None),
|
||||
'get_system_info': ('.shell_tools', None),
|
||||
'shell_tools': ('.shell_tools', None),
|
||||
|
||||
# Calculator Tools
|
||||
'evaluate': ('.calculator_tools', None),
|
||||
'solve_equation': ('.calculator_tools', None),
|
||||
'convert_units': ('.calculator_tools', None),
|
||||
'calculate_statistics': ('.calculator_tools', None),
|
||||
'calculate_financial': ('.calculator_tools', None),
|
||||
'calculator_tools': ('.calculator_tools', None),
|
||||
|
||||
# Class methods from YFinance
|
||||
'get_stock_price': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'get_stock_info': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'get_historical_data': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'yfinance': ('.yfinance_tools', 'YFinanceTools'),
|
||||
|
||||
# File Tools
|
||||
'read_file': ('.file_tools', 'FileTools'),
|
||||
'write_file': ('.file_tools', 'FileTools'),
|
||||
'list_files': ('.file_tools', 'FileTools'),
|
||||
'get_file_info': ('.file_tools', 'FileTools'),
|
||||
'copy_file': ('.file_tools', 'FileTools'),
|
||||
'move_file': ('.file_tools', 'FileTools'),
|
||||
'delete_file': ('.file_tools', 'FileTools'),
|
||||
'file_tools': ('.file_tools', 'FileTools'),
|
||||
|
||||
# CSV Tools
|
||||
'read_csv': ('.csv_tools', 'CSVTools'),
|
||||
'write_csv': ('.csv_tools', 'CSVTools'),
|
||||
'merge_csv': ('.csv_tools', 'CSVTools'),
|
||||
'analyze_csv': ('.csv_tools', 'CSVTools'),
|
||||
'split_csv': ('.csv_tools', 'CSVTools'),
|
||||
'csv_tools': ('.csv_tools', 'CSVTools'),
|
||||
|
||||
# JSON Tools
|
||||
'read_json': ('.json_tools', 'JSONTools'),
|
||||
'write_json': ('.json_tools', 'JSONTools'),
|
||||
'merge_json': ('.json_tools', 'JSONTools'),
|
||||
'validate_json': ('.json_tools', 'JSONTools'),
|
||||
'analyze_json': ('.json_tools', 'JSONTools'),
|
||||
'transform_json': ('.json_tools', 'JSONTools'),
|
||||
'json_tools': ('.json_tools', 'JSONTools'),
|
||||
|
||||
# Excel Tools
|
||||
'read_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'write_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'merge_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'create_chart': ('.excel_tools', 'ExcelTools'),
|
||||
'add_chart_to_sheet': ('.excel_tools', 'ExcelTools'),
|
||||
'excel_tools': ('.excel_tools', 'ExcelTools'),
|
||||
|
||||
# XML Tools
|
||||
'read_xml': ('.xml_tools', 'XMLTools'),
|
||||
'write_xml': ('.xml_tools', 'XMLTools'),
|
||||
'transform_xml': ('.xml_tools', 'XMLTools'),
|
||||
'validate_xml': ('.xml_tools', 'XMLTools'),
|
||||
'xml_to_dict': ('.xml_tools', 'XMLTools'),
|
||||
'dict_to_xml': ('.xml_tools', 'XMLTools'),
|
||||
'xpath_query': ('.xml_tools', 'XMLTools'),
|
||||
'xml_tools': ('.xml_tools', 'XMLTools'),
|
||||
|
||||
# YAML Tools
|
||||
'read_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'write_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'merge_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'validate_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'analyze_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'transform_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'yaml_tools': ('.yaml_tools', 'YAMLTools'),
|
||||
|
||||
# Python Tools
|
||||
'execute_code': ('.python_tools', 'PythonTools'),
|
||||
'analyze_code': ('.python_tools', 'PythonTools'),
|
||||
'format_code': ('.python_tools', 'PythonTools'),
|
||||
'lint_code': ('.python_tools', 'PythonTools'),
|
||||
'disassemble_code': ('.python_tools', 'PythonTools'),
|
||||
'python_tools': ('.python_tools', 'PythonTools'),
|
||||
|
||||
# Pandas Tools
|
||||
'filter_data': ('.pandas_tools', 'PandasTools'),
|
||||
'get_summary': ('.pandas_tools', 'PandasTools'),
|
||||
'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
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Smart lazy loading of tools with class method support."""
|
||||
if name not in TOOL_MAPPINGS:
|
||||
raise AttributeError(f"module '{__package__}' has no attribute '{name}'")
|
||||
|
||||
module_path, class_name = TOOL_MAPPINGS[name]
|
||||
|
||||
if class_name is None:
|
||||
# Direct function import
|
||||
module = import_module(module_path, __package__)
|
||||
if name in [
|
||||
'duckduckgo', 'internet_search',
|
||||
'search_arxiv', 'get_arxiv_paper', 'get_papers_by_author', 'get_papers_by_category',
|
||||
'wiki_search', 'wiki_summary', 'wiki_page', 'wiki_random', 'wiki_language',
|
||||
'get_article', 'get_news_sources', 'get_articles_from_source', 'get_trending_topics',
|
||||
'scrape_page', 'extract_links', 'crawl', 'extract_text',
|
||||
'query', 'create_table', 'load_data', 'export_data', 'get_table_info', 'analyze_data',
|
||||
'execute_command', 'list_processes', 'kill_process', 'get_system_info',
|
||||
'evaluate', 'solve_equation', 'convert_units', 'calculate_statistics', 'calculate_financial'
|
||||
]:
|
||||
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', 'cot_tools']:
|
||||
return module # Returns the callable module
|
||||
return getattr(module, name)
|
||||
else:
|
||||
# Class method import
|
||||
if class_name not in _instances:
|
||||
module = import_module(module_path, __package__)
|
||||
class_ = getattr(module, class_name)
|
||||
_instances[class_name] = class_()
|
||||
|
||||
# Get the method and bind it to the instance
|
||||
method = getattr(_instances[class_name], name)
|
||||
return method
|
||||
|
||||
__all__ = list(TOOL_MAPPINGS.keys())
|
||||
@@ -0,0 +1,292 @@
|
||||
"""Tools for searching and retrieving papers from arXiv.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import arxiv_tools
|
||||
papers = arxiv_tools.search("quantum computing")
|
||||
paper = arxiv_tools.get_paper("2401.00123")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import search_arxiv, get_arxiv_paper
|
||||
papers = search_arxiv("quantum computing")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import json
|
||||
|
||||
# Map sort criteria to arxiv.SortCriterion
|
||||
SORT_CRITERIA = {
|
||||
"relevance": "Relevance",
|
||||
"lastupdateddate": "LastUpdatedDate",
|
||||
"submitteddate": "SubmittedDate"
|
||||
}
|
||||
|
||||
# Map sort order to arxiv.SortOrder
|
||||
SORT_ORDER = {
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending"
|
||||
}
|
||||
|
||||
class ArxivTools:
|
||||
"""Tools for searching and retrieving papers from arXiv."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize ArxivTools and check for arxiv package."""
|
||||
self._check_arxiv()
|
||||
|
||||
def _check_arxiv(self):
|
||||
"""Check if arxiv package is installed."""
|
||||
if util.find_spec("arxiv") is None:
|
||||
raise ImportError("arxiv package is not available. Please install it using: pip install arxiv")
|
||||
global arxiv
|
||||
import arxiv
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "relevance",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Search arXiv for papers matching the query.
|
||||
|
||||
Args:
|
||||
query: Search query (e.g., "quantum computing", "author:Einstein")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results. If None, includes all.
|
||||
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"]
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
try:
|
||||
import arxiv
|
||||
|
||||
# Configure search client
|
||||
client = arxiv.Client()
|
||||
|
||||
# Map sort criteria
|
||||
sort_by_enum = getattr(arxiv.SortCriterion, SORT_CRITERIA[sort_by.lower()])
|
||||
sort_order_enum = getattr(arxiv.SortOrder, SORT_ORDER[sort_order.lower()])
|
||||
|
||||
# Build search query
|
||||
search = arxiv.Search(
|
||||
query=query,
|
||||
max_results=max_results,
|
||||
sort_by=sort_by_enum,
|
||||
sort_order=sort_order_enum
|
||||
)
|
||||
|
||||
# Execute search
|
||||
results = []
|
||||
for result in client.results(search):
|
||||
# Convert to dict with selected fields
|
||||
paper = self._result_to_dict(result, include_fields)
|
||||
results.append(paper)
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error searching arXiv: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_paper(
|
||||
self,
|
||||
paper_id: str,
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[Dict[str, Any], Dict[str, str]]:
|
||||
"""
|
||||
Get details of a specific paper by its arXiv ID.
|
||||
|
||||
Args:
|
||||
paper_id: arXiv paper ID (e.g., "2401.00123")
|
||||
include_fields: List of fields to include in results. If None, includes all.
|
||||
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"]
|
||||
|
||||
Returns:
|
||||
Dict: Paper details or error dict
|
||||
"""
|
||||
try:
|
||||
import arxiv
|
||||
|
||||
# Configure client
|
||||
client = arxiv.Client()
|
||||
|
||||
# Get paper by ID
|
||||
search = arxiv.Search(id_list=[paper_id])
|
||||
results = list(client.results(search))
|
||||
|
||||
if not results:
|
||||
return {"error": f"Paper with ID {paper_id} not found"}
|
||||
|
||||
# Convert to dict with selected fields
|
||||
paper = self._result_to_dict(results[0], include_fields)
|
||||
return paper
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting paper {paper_id}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_papers_by_author(
|
||||
self,
|
||||
author: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "submittedDate",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Get papers by a specific author.
|
||||
|
||||
Args:
|
||||
author: Author name (e.g., "Einstein")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
query = f'au:"{author}"'
|
||||
return self.search(query, max_results, sort_by, sort_order, include_fields)
|
||||
|
||||
def get_papers_by_category(
|
||||
self,
|
||||
category: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "submittedDate",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Get papers from a specific category.
|
||||
|
||||
Args:
|
||||
category: arXiv category (e.g., "cs.AI", "physics.gen-ph")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
query = f'cat:{category}'
|
||||
return self.search(query, max_results, sort_by, sort_order, include_fields)
|
||||
|
||||
def _result_to_dict(
|
||||
self,
|
||||
result: Any,
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Convert arxiv.Result to dictionary with selected fields."""
|
||||
# Default fields to include
|
||||
if include_fields is None:
|
||||
include_fields = [
|
||||
"title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"
|
||||
]
|
||||
|
||||
# Build paper dict with selected fields
|
||||
paper = {}
|
||||
|
||||
# Always include these basic fields
|
||||
paper["arxiv_id"] = result.entry_id.split("/")[-1]
|
||||
paper["updated"] = result.updated.isoformat() if result.updated else None
|
||||
paper["published"] = result.published.isoformat() if result.published else None
|
||||
|
||||
# Add requested fields
|
||||
if "title" in include_fields:
|
||||
paper["title"] = result.title
|
||||
if "authors" in include_fields:
|
||||
paper["authors"] = [str(author) for author in result.authors]
|
||||
if "summary" in include_fields:
|
||||
paper["summary"] = result.summary
|
||||
if "comment" in include_fields:
|
||||
paper["comment"] = result.comment
|
||||
if "journal_ref" in include_fields:
|
||||
paper["journal_ref"] = result.journal_ref
|
||||
if "doi" in include_fields:
|
||||
paper["doi"] = result.doi
|
||||
if "primary_category" in include_fields:
|
||||
paper["primary_category"] = result.primary_category
|
||||
if "categories" in include_fields:
|
||||
paper["categories"] = result.categories
|
||||
if "links" in include_fields:
|
||||
paper["pdf_url"] = result.pdf_url
|
||||
paper["abstract_url"] = f"https://arxiv.org/abs/{paper['arxiv_id']}"
|
||||
|
||||
return paper
|
||||
|
||||
# Create instance for direct function access
|
||||
_arxiv_tools = ArxivTools()
|
||||
search_arxiv = _arxiv_tools.search
|
||||
get_arxiv_paper = _arxiv_tools.get_paper
|
||||
get_papers_by_author = _arxiv_tools.get_papers_by_author
|
||||
get_papers_by_category = _arxiv_tools.get_papers_by_category
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("ArxivTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Search for papers
|
||||
print("1. Searching for Papers")
|
||||
print("------------------------------")
|
||||
query = "quantum computing"
|
||||
papers = search_arxiv(query, max_results=3)
|
||||
print(f"Papers about {query}:")
|
||||
if isinstance(papers, list):
|
||||
print(json.dumps(papers, indent=2))
|
||||
else:
|
||||
print(papers) # Show error
|
||||
print()
|
||||
|
||||
# 2. Get specific paper
|
||||
print("2. Getting Specific Paper")
|
||||
print("------------------------------")
|
||||
if isinstance(papers, list) and papers:
|
||||
paper_id = papers[0]["arxiv_id"]
|
||||
paper = get_arxiv_paper(paper_id)
|
||||
print(f"Paper {paper_id}:")
|
||||
if "error" not in paper:
|
||||
print(json.dumps(paper, indent=2))
|
||||
else:
|
||||
print(paper) # Show error
|
||||
print()
|
||||
|
||||
# 3. Get papers by author
|
||||
print("3. Getting Papers by Author")
|
||||
print("------------------------------")
|
||||
author = "Yoshua Bengio"
|
||||
author_papers = get_papers_by_author(author, max_results=3)
|
||||
print(f"Papers by {author}:")
|
||||
if isinstance(author_papers, list):
|
||||
print(json.dumps(author_papers, indent=2))
|
||||
else:
|
||||
print(author_papers) # Show error
|
||||
print()
|
||||
|
||||
# 4. Get papers by category
|
||||
print("4. Getting Papers by Category")
|
||||
print("------------------------------")
|
||||
category = "cs.AI"
|
||||
category_papers = get_papers_by_category(category, max_results=3)
|
||||
print(f"Papers in category {category}:")
|
||||
if isinstance(category_papers, list):
|
||||
print(json.dumps(category_papers, indent=2))
|
||||
else:
|
||||
print(category_papers) # Show error
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
Loaded 100 of 372 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user