mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 03:31:28 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d67f0fdcf | ||
|
|
fb93b7d6d5 | ||
|
|
657fd93743 | ||
|
|
f0a2936b7f | ||
|
|
58778492ca | ||
|
|
08c444f3a8 | ||
|
|
34c1b39e95 | ||
|
|
5e4c58a9ac | ||
|
|
19a30ff3d9 | ||
|
|
50f12c7682 | ||
|
|
5a70b8abb9 | ||
|
|
b7d4d219b9 | ||
|
|
ff2d83ffdf | ||
|
|
2d58caa741 | ||
|
|
d31cf379e5 | ||
|
|
719ef04e7c | ||
|
|
4fb25a99d5 | ||
|
|
0fdacff2cf | ||
|
|
6a1fa19c6e | ||
|
|
a4725a027a | ||
|
|
dc9a8f3443 | ||
|
|
9421e28c8a | ||
|
|
c3d60c82ca | ||
|
|
8c509b439f | ||
|
|
3fbf1a353e | ||
|
|
e0f3a49e41 | ||
|
|
987b43877f | ||
|
|
ebb383208e | ||
|
|
c9e4f0738a | ||
|
|
db0a24555d | ||
|
|
e3fe35e2b6 | ||
|
|
74a7322b83 | ||
|
|
7ceb7f8cb9 | ||
|
|
84e5b6c693 | ||
|
|
5f00ddbc7c | ||
|
|
db551c5d65 | ||
|
|
aed6027256 | ||
|
|
a9d709645a | ||
|
|
a430e1b873 | ||
|
|
8e0a40db56 | ||
|
|
8d1e4637d4 | ||
|
|
e11d62dc89 | ||
|
|
13a9a3e07d | ||
|
|
b95f6b0665 | ||
|
|
e8c4410014 | ||
|
|
6c2d852ed1 | ||
|
|
6f95ae8d9d | ||
|
|
8e2dd09b3b | ||
|
|
8ecfad5c5a | ||
|
|
35a88a0913 | ||
|
|
6df3e32640 | ||
|
|
fb36e1c50c | ||
|
|
ac5454cb00 | ||
|
|
bbe95795d1 | ||
|
|
bdaf5340f9 | ||
|
|
5579614001 | ||
|
|
2c4dd0e2d4 | ||
|
|
fd23d62841 | ||
|
|
5f3bcb78ac | ||
|
|
3c40c2278e | ||
|
|
1921b03aab | ||
|
|
f33bf57662 | ||
|
|
df21c419ea | ||
|
|
fe997d525c |
No files matched your search
@@ -0,0 +1,204 @@
|
||||
name: Windows Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_MODEL_NAME: gpt-4o-mini
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install praisonaiagents
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
cd src/praisonai-agents
|
||||
pip install .
|
||||
shell: bash
|
||||
|
||||
- name: Install praisonai wrapper
|
||||
run: |
|
||||
cd src/praisonai
|
||||
pip install .[dev]
|
||||
shell: bash
|
||||
|
||||
# ==================== CORE IMPORTS ====================
|
||||
- name: Test core imports
|
||||
run: |
|
||||
python -c "from praisonaiagents import Agent, Agents, Workflow; print('Core imports OK')"
|
||||
python -c "from praisonaiagents import tool, Tools; print('Tool imports OK')"
|
||||
python -c "from praisonaiagents import Task, WorkflowStep; print('Task/Step imports OK')"
|
||||
|
||||
# ==================== AGENT FEATURES ====================
|
||||
- name: Test Agent instantiation
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Agent
|
||||
a = Agent(name='Test', instructions='You are helpful')
|
||||
print('Agent created:', a.name)
|
||||
a2 = Agent(name='ToolAgent', instructions='Use tools', tools=[])
|
||||
print('Agent with tools:', a2.name)
|
||||
"
|
||||
|
||||
- name: Test Agents orchestration
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Agent, Agents, Task
|
||||
a1 = Agent(name='Agent1', instructions='First agent')
|
||||
a2 = Agent(name='Agent2', instructions='Second agent')
|
||||
t = Task(description='Do something', agent=a1)
|
||||
agents = Agents(agents=[a1, a2], tasks=[t])
|
||||
print('Agents:', len(agents.agents), 'Tasks:', len(agents.tasks))
|
||||
"
|
||||
|
||||
# ==================== WORKFLOW FEATURES ====================
|
||||
- name: Test Workflow and WorkflowStep
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Workflow, WorkflowStep
|
||||
from praisonaiagents.workflows import WorkflowOutputConfig
|
||||
|
||||
w = Workflow(name='test')
|
||||
s1 = WorkflowStep(name='step1')
|
||||
s2 = WorkflowStep(name='step2', context=['step1'])
|
||||
print('Workflow:', w.name)
|
||||
print('Steps:', s1.name, s2.name)
|
||||
|
||||
cfg = WorkflowOutputConfig(verbose=True, stream=False)
|
||||
w2 = Workflow(name='configured', output=cfg)
|
||||
print('Configured workflow verbose:', w2.verbose)
|
||||
"
|
||||
|
||||
# ==================== MEMORY FEATURES ====================
|
||||
- name: Test Memory classes
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents.memory import FileMemory
|
||||
from praisonaiagents.config import MemoryConfig
|
||||
|
||||
cfg = MemoryConfig(backend='file', user_id='test_user')
|
||||
print('MemoryConfig backend:', cfg.backend)
|
||||
|
||||
mem = FileMemory(user_id='test_user', base_path='.praison/test_memory')
|
||||
print('FileMemory user:', mem.user_id)
|
||||
"
|
||||
|
||||
# ==================== TOOL DECORATOR ====================
|
||||
- name: Test tool decorator
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import tool
|
||||
|
||||
@tool
|
||||
def my_tool(query: str) -> str:
|
||||
'''A test tool'''
|
||||
return f'Echo: {query}'
|
||||
|
||||
result = my_tool('hello')
|
||||
print('Tool result:', result)
|
||||
"
|
||||
|
||||
# ==================== CONFIG CLASSES ====================
|
||||
- name: Test config classes
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents.config import OutputConfig, ExecutionConfig, PlanningConfig
|
||||
|
||||
out_cfg = OutputConfig(verbose=True, stream=True)
|
||||
print('OutputConfig verbose:', out_cfg.verbose)
|
||||
|
||||
exec_cfg = ExecutionConfig(max_iter=50, max_retry_limit=5)
|
||||
print('ExecutionConfig max_iter:', exec_cfg.max_iter)
|
||||
|
||||
plan_cfg = PlanningConfig(reasoning=True, auto_approve=False)
|
||||
print('PlanningConfig reasoning:', plan_cfg.reasoning)
|
||||
"
|
||||
# ==================== HANDOFF ====================
|
||||
- name: Test Handoff
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Handoff, handoff, Agent
|
||||
|
||||
a1 = Agent(name='Agent1', instructions='First')
|
||||
a2 = Agent(name='Agent2', instructions='Second')
|
||||
|
||||
# Using Handoff class directly
|
||||
h = Handoff(agent=a2)
|
||||
print('Handoff tool name:', h.tool_name)
|
||||
|
||||
# Using handoff function
|
||||
h2 = handoff(a1, tool_name_override='custom_handoff')
|
||||
print('Custom handoff name:', h2.tool_name)
|
||||
"
|
||||
|
||||
|
||||
# ==================== ASYNC FEATURES ====================
|
||||
- name: Test async imports
|
||||
run: |
|
||||
python -c "
|
||||
import asyncio
|
||||
from praisonaiagents import Agent
|
||||
|
||||
async def test_async():
|
||||
agent = Agent(name='AsyncAgent', instructions='Test')
|
||||
assert hasattr(agent, 'arun'), 'Missing arun'
|
||||
assert hasattr(agent, 'astart'), 'Missing astart'
|
||||
print('Async methods available')
|
||||
|
||||
asyncio.run(test_async())
|
||||
"
|
||||
|
||||
# ==================== CLI COMMANDS ====================
|
||||
- name: Test CLI help
|
||||
run: praisonai --help
|
||||
|
||||
- name: Test CLI session list
|
||||
run: praisonai session list
|
||||
continue-on-error: true
|
||||
|
||||
- name: Test CLI queue list
|
||||
run: praisonai queue list
|
||||
continue-on-error: true
|
||||
|
||||
# ==================== REAL API TESTS ====================
|
||||
- name: Real API test - Agent run
|
||||
if: env.OPENAI_API_KEY != ''
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Agent
|
||||
agent = Agent(name='TestAgent', instructions='Reply with exactly: WINDOWS_OK', llm='gpt-4o-mini')
|
||||
result = agent.run('Say WINDOWS_OK')
|
||||
print('Result:', result)
|
||||
print('Real API test passed')
|
||||
"
|
||||
|
||||
- name: Real API test - Multi-agent
|
||||
if: env.OPENAI_API_KEY != ''
|
||||
run: |
|
||||
python -c "
|
||||
from praisonaiagents import Agent, Agents, Task
|
||||
|
||||
researcher = Agent(name='Researcher', instructions='You research topics', llm='gpt-4o-mini')
|
||||
writer = Agent(name='Writer', instructions='You write content', llm='gpt-4o-mini')
|
||||
|
||||
task = Task(description='Say hello world', agent=researcher)
|
||||
|
||||
agents = Agents(agents=[researcher, writer], tasks=[task])
|
||||
result = agents.start()
|
||||
print('Multi-agent result type:', type(result))
|
||||
print('Multi-agent test passed')
|
||||
"
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Update API Reference
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'src/praisonai-agents/praisonaiagents/**/*.py'
|
||||
- 'src/praisonai/praisonai/**/*.py'
|
||||
- 'src/praisonai-ts/src/**/*.ts'
|
||||
- '.github/workflows/update-api-md.yml'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-api-md:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Generate api.md
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
python3 src/praisonai/scripts/generate_api_md.py
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if git diff --quiet api.md; then
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No changes to api.md"
|
||||
else
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
echo "api.md has been updated"
|
||||
git diff api.md | head -50
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.check_changes.outputs.changes == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "docs: auto-update api.md [skip ci]"
|
||||
file_pattern: 'api.md'
|
||||
commit_user_name: "github-actions[bot]"
|
||||
commit_user_email: "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
+2
-1
@@ -83,4 +83,5 @@ issue_details.json
|
||||
# MCP Registry tokens
|
||||
.mcpregistry_*
|
||||
|
||||
*.log
|
||||
*.log
|
||||
.internal
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
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, AG2 (Formerly 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.
|
||||
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. It provides a low-code solution to streamline 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">
|
||||
|
||||
@@ -3,27 +3,11 @@
|
||||
This file is auto-generated. Do not edit manually.
|
||||
Regenerate with: `praisonai docs api-md --write`
|
||||
|
||||
# Shared Types
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ContextPolicy, GuardrailResult, HandoffConfig, HandoffCycleError, HandoffDepthError, HandoffError, HandoffInputData, HandoffResult, HandoffTimeoutError, ReflectionOutput, StepResult, TaskOutput, ToolResult, ToolValidationError, WorkflowContext
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class GuardrailResult">GuardrailResult.<a href="./src/praisonai-agents/praisonaiagents/guardrails/guardrail_result.py">from_tuple</a>(result: Tuple[bool, Any]) -> 'GuardrailResult'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">from_dict</a>(data: Dict[str, Any]) -> 'HandoffConfig'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">json</a>() -> Optional[str]</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">to_dict</a>() -> dict</code>
|
||||
* <code title="class ToolResult">ToolResult.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Agents
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Agent, Agents, AutoAgents, AutoRagAgent, ContextAgent, DeepResearchAgent, ImageAgent, PraisonAIAgents, PromptExpanderAgent, QueryRewriterAgent, create_context_agent
|
||||
from praisonaiagents import Agent, Agents
|
||||
```
|
||||
|
||||
Methods:
|
||||
@@ -60,6 +44,7 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_skills_prompt</a>() -> str</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to_async</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">iter_stream</a>(prompt: str, **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">launch</a>(path: str = '/', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = 'http')</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">llm_model</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>()</code>
|
||||
@@ -76,7 +61,7 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_autonomous</a>(prompt: str, max_iterations: Optional[int] = None, timeout_seconds: Optional[float] = None)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">session_id</a>() -> Optional[str]</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">skill_manager</a>()</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">start</a>(prompt: str, **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">start</a>(prompt: str = None, **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">store_memory</a>(content: str, memory_type: str = 'short_term', **kwargs)</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">switch_model</a>(new_model: str) -> None</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">thinking_budget</a>()</code>
|
||||
@@ -116,455 +101,31 @@ Methods:
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class Agents">Agents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
|
||||
* <code title="class AutoAgents">AutoAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/autoagents.py">astart</a>()</code>
|
||||
* <code title="class AutoAgents">AutoAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/autoagents.py">start</a>()</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">achat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">chat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">name</a>() -> str</code>
|
||||
* <code title="class AutoRagAgent">AutoRagAgent.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">rag</a>() -> Optional['RAG']</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_codebase_with_gitingest</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_integration_points</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">analyze_test_patterns</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">build_implementation_blueprint</a>(feature_request: str, context_analysis: Dict[str, Any] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">compile_context_documentation</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_quality_gates</a>(requirements: List[str]) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_validation_framework</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">execute_prp</a>(prp_file_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">extract_implementation_patterns</a>(project_path: str, ast_analysis: Dict[str, Any] = None) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">generate_comprehensive_prp</a>(feature_request: str, context_analysis: Dict[str, Any] = None) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">generate_feature_prp</a>(feature_request: str) -> str</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">get_agent_interaction_summary</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">log_debug</a>(message: str, **kwargs)</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">perform_ast_analysis</a>(project_path: str) -> Dict[str, Any]</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">save_comprehensive_session_report</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">save_markdown_output</a>(content: str, filename: str, section_title: str = 'Output')</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">setup_logging</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">setup_output_directories</a>()</code>
|
||||
* <code title="class ContextAgent">ContextAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">start</a>(input_text: str) -> str</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">aresearch</a>(query: str, instructions: Optional[str] = None, model: Optional[str] = None, summary_mode: Optional[Literal['auto', 'detailed', 'concise']] = None, web_search: Optional[bool] = None, code_interpreter: Optional[bool] = None, mcp_servers: Optional[List[Dict[str, Any]]] = None, file_ids: Optional[List[str]] = None, file_search: Optional[bool] = None, file_search_stores: Optional[List[str]] = None) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">async_openai_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">clarify</a>(query: str, model: Optional[str] = None) -> str</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">follow_up</a>(query: str, previous_interaction_id: str, model: Optional[str] = None) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">gemini_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">openai_client</a>()</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">research</a>(query: str, instructions: Optional[str] = None, model: Optional[str] = None, summary_mode: Optional[Literal['auto', 'detailed', 'concise']] = None, web_search: Optional[bool] = None, code_interpreter: Optional[bool] = None, mcp_servers: Optional[List[Dict[str, Any]]] = None, file_ids: Optional[List[str]] = None, file_search: Optional[bool] = None, file_search_stores: Optional[List[str]] = None, stream: bool = True) -> DeepResearchResponse</code>
|
||||
* <code title="class DeepResearchAgent">DeepResearchAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">rewrite_query</a>(query: str, model: Optional[str] = None) -> str</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">achat</a>(prompt: str, temperature: float = 0.2, tools: Optional[List[Any]] = None, output_json: Optional[str] = None, output_pydantic: Optional[Any] = None, reasoning_steps: bool = False, **kwargs) -> Union[str, Dict[str, Any]]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">agenerate_image</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">chat</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">generate_image</a>(prompt: str, **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class ImageAgent">ImageAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/image_agent.py">litellm</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">append_to_state</a>(key: str, value: Any, max_length: Optional[int] = None) -> List[Any]</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_all_tasks</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_task</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">astart</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clean_json_output</a>(output: str) -> str</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clear_state</a>() -> None</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">context_manager</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">current_plan</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">default_completion_checker</a>(task, agent_output)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">delete_state</a>(key: str) -> bool</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">display_token_usage</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">execute_task</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_agent_details</a>(agent_name)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_state</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_tasks_status</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_detailed_token_report</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_plan_markdown</a>() -> str</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_details</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_result</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_status</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_todo_markdown</a>() -> str</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_token_usage_summary</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">has_state</a>(key: str) -> bool</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">increment_state</a>(key: str, amount: float = 1, default: float = 0) -> float</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">launch</a>(path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = 'http')</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">restore_session_state</a>(session_id: str) -> bool</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_all_tasks</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">run_task</a>(task_id)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, **kwargs)</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class PraisonAIAgents">PraisonAIAgents.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">agent</a>()</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand</a>(prompt: str, strategy: ExpandStrategy = ..., context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_basic</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_creative</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_detailed</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class PromptExpanderAgent">PromptExpanderAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/prompt_expander_agent.py">expand_structured</a>(prompt: str, context: Optional[str] = None) -> ExpandResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">add_abbreviation</a>(abbrev: str, expansion: str) -> None</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">add_abbreviations</a>(abbreviations: Dict[str, str]) -> None</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">agent</a>()</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite</a>(query: str, strategy: RewriteStrategy = ..., chat_history: Optional[List[Dict[str, str]]] = None, context: Optional[str] = None, num_queries: int = None) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_basic</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_contextual</a>(query: str, chat_history: List[Dict[str, str]]) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_hyde</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_multi_query</a>(query: str, num_queries: int = None) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_step_back</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="class QueryRewriterAgent">QueryRewriterAgent.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">rewrite_sub_queries</a>(query: str) -> RewriteResult</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/context_agent.py">create_context_agent</a>(llm: Optional[Union[str, Any]] = None, **kwargs) -> ContextAgent</code>
|
||||
|
||||
# Tools
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import BaseTool, FunctionTool, ToolRegistry, Tools, get_registry, get_tool, register_tool, tool, validate_tool
|
||||
from praisonaiagents import Tools, tool
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">__call__</a>(**kwargs) -> Any</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">get_schema</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">run</a>(**kwargs) -> Any</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">safe_run</a>(**kwargs) -> ToolResult</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate</a>() -> bool</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_class</a>() -> bool</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">__call__</a>(*args, **kwargs) -> Any</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">injected_params</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">run</a>(**kwargs) -> Any</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">clear</a>() -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">discover_plugins</a>() -> int</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_all</a>() -> Dict[str, Union[BaseTool, Callable]]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_base_tools</a>() -> List[BaseTool]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_tools</a>() -> List[str]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None, overwrite: bool = False) -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">unregister</a>(name: str) -> bool</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_registry</a>() -> ToolRegistry</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_tool</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register_tool</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None) -> None</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">tool</a>(func: Optional[Callable] = None) -> Union[FunctionTool, Callable[[Callable], FunctionTool]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_tool</a>(tool: Any) -> bool</code>
|
||||
|
||||
# Workflows
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Loop, Parallel, Pipeline, Repeat, Route, Workflow, WorkflowStep, loop, parallel, repeat, route
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">arun</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">astart</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">from_template</a>(uri: str, config: Optional[Dict[str, Any]] = None, offline: bool = False, **kwargs) -> 'Workflow'</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">memory_config</a>() -> Optional[Dict[str, Any]]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_complete</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_error</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_step_start</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_workflow_complete</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_workflow_start</a>() -> Optional[Callable]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">planning_llm</a>() -> Optional[str]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">reasoning</a>() -> bool</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">run</a>(input: str = '', llm: Optional[str] = None, verbose: bool = False, stream: bool = None) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">start</a>(input: str = '', **kwargs) -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">stream</a>() -> bool</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Workflow">Workflow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>() -> bool</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">async_execution</a>() -> bool</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">branch_condition</a>() -> Optional[Dict[str, List[str]]]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">context_from</a>() -> Optional[List[str]]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">evaluator</a>()</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">executor</a>()</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">max_retries</a>() -> int</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">next_steps</a>() -> Optional[List[str]]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">on_error</a>() -> str</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_file</a>() -> Optional[str]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_json</a>() -> Optional[Any]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_pydantic</a>() -> Optional[Any]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">output_variable</a>() -> Optional[str]</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">quality_check</a>() -> bool</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">rerun</a>() -> bool</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">retain_full_context</a>() -> bool</code>
|
||||
* <code title="class WorkflowStep">WorkflowStep.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">loop</a>(step: Any, over: Optional[str] = None, from_csv: Optional[str] = None, from_file: Optional[str] = None, var_name: str = 'item') -> Loop</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">parallel</a>(steps: List) -> Parallel</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">repeat</a>(step: Any, until: Optional[Callable[[WorkflowContext], bool]] = None, max_iterations: int = 10) -> Repeat</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">route</a>(routes: Dict[str, List], default: Optional[List] = None) -> Route</code>
|
||||
|
||||
# DB
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import db
|
||||
```
|
||||
|
||||
# Memory
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Memory
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">build_context_for_task</a>(task_descr: str, user_id: Optional[str] = None, additional: str = '', max_items: int = 3, include_in_output: Optional[bool] = None) -> str</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">calculate_quality_metrics</a>(output: str, expected_output: str, llm: Optional[str] = None, custom_prompt: Optional[str] = None) -> Dict[str, float]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">compute_quality_score</a>(completeness: float, relevance: float, clarity: float, accuracy: float, weights: Dict[str, float] = None) -> float</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">finalize_task_output</a>(content: str, agent_name: str, quality_score: float, threshold: float = 0.7, metrics: Dict[str, Any] = None, task_id: str = None)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">get_all_memories</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_all</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_entity_only</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_long_term</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_short_term</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">reset_user_memory</a>()</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search</a>(query: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, limit: int = 5, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_entity</a>(query: str, limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_long_term</a>(query: str, limit: int = 5, relevance_cutoff: float = 0.0, min_quality: float = 0.0, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_short_term</a>(query: str, limit: int = 5, min_quality: float = 0.0, relevance_cutoff: float = 0.0, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_user_memory</a>(user_id: str, query: str, limit: int = 5, rerank: bool = False, **kwargs) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">search_with_quality</a>(query: str, min_quality: float = 0.0, memory_type: Literal['short', 'long'] = 'long', limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_entity</a>(name: str, type_: str, desc: str, relations: str)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_long_term</a>(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)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_quality</a>(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</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_short_term</a>(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)</code>
|
||||
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">store_user_memory</a>(user_id: str, text: str, extra: Dict[str, Any] = None)</code>
|
||||
|
||||
# Knowledge
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Chunking, Knowledge
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">SUPPORTED_CHUNKERS</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">__call__</a>(text: Union[str, List[str]], **kwargs) -> Union[List[Any], List[List[Any]]]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">chunk</a>(text: Union[str, List[str]], **kwargs) -> Union[List[Any], List[List[Any]]]</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">chunker</a>()</code>
|
||||
* <code title="class Chunking">Chunking.<a href="./src/praisonai-agents/praisonaiagents/knowledge/chunking.py">embedding_model</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">add</a>(file_path, user_id = None, agent_id = None, run_id = None, metadata = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">chunker</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">config</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">delete</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">delete_all</a>(user_id = None, agent_id = None, run_id = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get_all</a>(user_id = None, agent_id = None, run_id = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">get_corpus_stats</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">history</a>(memory_id)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">index</a>(path: str, incremental: bool = True, force: bool = False, include_glob: list = None, exclude_glob: list = None, user_id: str = None, agent_id: str = None, run_id: str = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">markdown</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">memory</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">normalize_content</a>(content)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">reset</a>()</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">search</a>(query, user_id = None, agent_id = None, run_id = None, rerank = None, **kwargs)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">store</a>(content, user_id = None, agent_id = None, run_id = None, metadata = None)</code>
|
||||
* <code title="class Knowledge">Knowledge.<a href="./src/praisonai-agents/praisonaiagents/knowledge/knowledge.py">update</a>(memory_id, data)</code>
|
||||
|
||||
# RAG
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import RAG, RAGCitation, RAGConfig, RAGResult
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">aquery</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> RAGResult</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">aretrieve</a>(query: str, **kwargs) -> ContextPack</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">astream</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> AsyncIterator[str]</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">get_citations</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> List[Citation]</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">llm</a>()</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">query</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> RAGResult</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">retrieve</a>(query: str, **kwargs) -> ContextPack</code>
|
||||
* <code title="class RAG">RAG.<a href="./src/praisonai-agents/praisonaiagents/rag/pipeline.py">stream</a>(question: str, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None, **kwargs) -> Iterator[str]</code>
|
||||
* <code title="class RAGCitation">RAGCitation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'Citation'</code>
|
||||
* <code title="class RAGCitation">RAGCitation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RAGConfig">RAGConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'RAGConfig'</code>
|
||||
* <code title="class RAGConfig">RAGConfig.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">format_answer_with_citations</a>() -> str</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'RAGResult'</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">has_citations</a>() -> bool</code>
|
||||
* <code title="class RAGResult">RAGResult.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Handoff
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Handoff, RECOMMENDED_PROMPT_PREFIX, handoff, handoff_filters, prompt_with_handoff_instructions
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">default_tool_description</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">default_tool_name</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">execute_async</a>(source_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None) -> HandoffResult</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">execute_programmatic</a>(source_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None) -> HandoffResult</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_tool_function</a>(source_agent: 'Agent') -> Callable</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_description</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_name</a>() -> str</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">handoff</a>(agent: 'Agent', tool_name_override: Optional[str] = None, tool_description_override: Optional[str] = None, on_handoff: Optional[Callable] = None, input_type: Optional[type] = None, input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None, config: Optional[HandoffConfig] = None, context_policy: Optional[str] = None, timeout_seconds: Optional[float] = None, max_concurrent: Optional[int] = None, detect_cycles: Optional[bool] = None, max_depth: Optional[int] = None) -> Handoff</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">keep_last_n_messages</a>(n: int) -> Callable[[HandoffInputData], HandoffInputData]</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">remove_all_tools</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">remove_system_messages</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">prompt_with_handoff_instructions</a>(base_prompt: str, agent: 'Agent') -> str</code>
|
||||
|
||||
# Guardrails
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import LLMGuardrail
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">__call__</a>(task_output: TaskOutput) -> Tuple[bool, Union[str, TaskOutput]]</code>
|
||||
|
||||
# Skills
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import SkillLoader, SkillManager, SkillMetadata, SkillProperties
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">activate</a>(skill: LoadedSkill) -> bool</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load</a>(skill_path: str, activate: bool = False) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_all_resources</a>(skill: LoadedSkill) -> None</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_assets</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_metadata</a>(skill_path: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_references</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillLoader">SkillLoader.<a href="./src/praisonai-agents/praisonaiagents/skills/loader.py">load_scripts</a>(skill: LoadedSkill) -> dict</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">activate</a>(skill: LoadedSkill) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">activate_by_name</a>(name: str) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">add_skill</a>(skill_path: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">clear</a>() -> None</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">discover</a>(skill_dirs: Optional[List[str]] = None, include_defaults: bool = True) -> int</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_available_skills</a>() -> List[SkillMetadata]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_instructions</a>(name: str) -> Optional[str]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_skill</a>(name: str) -> Optional[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">load_resources</a>(name: str) -> bool</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skill_names</a>() -> List[str]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skills</a>() -> List[LoadedSkill]</code>
|
||||
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">to_prompt</a>() -> str</code>
|
||||
* <code title="class SkillMetadata">SkillMetadata.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">from_properties</a>(props: SkillProperties) -> 'SkillMetadata'</code>
|
||||
* <code title="class SkillProperties">SkillProperties.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">to_dict</a>() -> dict</code>
|
||||
|
||||
# Session
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import Session
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">Agent</a>(name: str, role: str = 'Assistant', instructions: Optional[str] = None, tools: Optional[List[Any]] = None, memory: bool = True, knowledge: Optional[List[str]] = None, **kwargs) -> 'Agent'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">add_knowledge</a>(source: str) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">add_memory</a>(text: str, memory_type: str = 'long', **metadata) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">chat</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">clear_memory</a>(memory_type: str = 'all') -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">create_agent</a>(*args, **kwargs) -> 'Agent'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">get_context</a>(query: str, max_items: int = 3) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">increment_state</a>(key: str, increment: int = 1, default: int = 0) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">knowledge</a>() -> 'Knowledge'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">memory</a>() -> 'Memory'</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">restore_state</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">save_state</a>(state_data: Dict[str, Any]) -> None</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">search_knowledge</a>(query: str, limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">search_memory</a>(query: str, memory_type: str = 'long', limit: int = 5) -> List[Dict[str, Any]]</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">send_message</a>(message: str, **kwargs) -> str</code>
|
||||
* <code title="class Session">Session.<a href="./src/praisonai-agents/praisonaiagents/session/api.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
|
||||
# Telemetry
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import get_telemetry
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/telemetry/__init__.py">get_telemetry</a>() -> 'MinimalTelemetry'</code>
|
||||
|
||||
# Observability
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import FlowDisplay, obs, track_workflow
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">display</a>()</code>
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">start</a>()</code>
|
||||
* <code title="class FlowDisplay">FlowDisplay.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">stop</a>()</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/flow_display.py">track_workflow</a>()</code>
|
||||
|
||||
# Context
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import ContextManager, ManagerConfig
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">capture_llm_boundary</a>(messages: List[Dict[str, Any]], tools: List[Dict[str, Any]]) -> SnapshotHookData</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">estimate_tokens</a>(text: str, validate: bool = False) -> Tuple[int, Optional[EstimationMetrics]]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_history</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_last_snapshot_hook</a>() -> Optional[SnapshotHookData]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_resolved_config</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_stats</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">get_tool_budget</a>(tool_name: str) -> int</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">process</a>(messages: List[Dict[str, Any]], system_prompt: str = '', tools: Optional[List[Dict[str, Any]]] = None, trigger: Literal['turn', 'tool_call', 'manual', 'overflow'] = 'turn') -> Dict[str, Any]</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">register_snapshot_callback</a>(callback: Callable[[SnapshotHookData], None]) -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">reset</a>() -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">set_tool_budget</a>(tool_name: str, max_tokens: int, protected: bool = False) -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">truncate_tool_output</a>(tool_name: str, output: str) -> str</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">from_env</a>() -> 'ManagerConfig'</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">merge</a>(**overrides) -> 'ManagerConfig'</code>
|
||||
* <code title="class ManagerConfig">ManagerConfig.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
|
||||
# Display
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import async_display_callbacks, clean_triple_backticks, display_error, display_generating, display_instruction, display_interaction, display_self_reflection, display_tool_call, error_logs, register_display_callback, sync_display_callbacks
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">clean_triple_backticks</a>(text: str) -> str</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_error</a>(message: str, console = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_generating</a>(content: str = '', start_time: Optional[float] = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_instruction</a>(message: str, console = None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_interaction</a>(message, response, markdown = True, generation_time = None, console = None, agent_name = None, agent_role = None, agent_tools = None, task_name = None, task_description = None, task_id = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_self_reflection</a>(message: str, console = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">display_tool_call</a>(message: str, console = None, tool_name: str = None, tool_input: dict = None, tool_output: str = None)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/main.py">register_display_callback</a>(display_type: str, callback_fn, is_async: bool = False)</code>
|
||||
|
||||
# Other
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import AutoRagConfig, Citation, CodeExecutionStep, DeepResearchResponse, ExpandResult, ExpandStrategy, FileSearchCall, MCPCall, Provider, RagRetrievalPolicy, ReasoningStep, RewriteResult, RewriteStrategy, Task, WebSearchCall
|
||||
from praisonaiagents import Task
|
||||
```
|
||||
|
||||
Methods:
|
||||
|
||||
* <code title="class AutoRagConfig">AutoRagConfig.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">from_dict</a>(data: Dict[str, Any]) -> 'AutoRagConfig'</code>
|
||||
* <code title="class AutoRagConfig">AutoRagConfig.<a href="./src/praisonai-agents/praisonaiagents/agents/auto_rag_agent.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class Citation">Citation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">from_dict</a>(data: Dict[str, Any]) -> 'Citation'</code>
|
||||
* <code title="class Citation">Citation.<a href="./src/praisonai-agents/praisonaiagents/rag/models.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class DeepResearchResponse">DeepResearchResponse.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">get_all_sources</a>() -> List[Dict[str, str]]</code>
|
||||
* <code title="class DeepResearchResponse">DeepResearchResponse.<a href="./src/praisonai-agents/praisonaiagents/agent/deep_research_agent.py">get_citation_text</a>(citation: Citation) -> str</code>
|
||||
* <code title="class RewriteResult">RewriteResult.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">all_queries</a>() -> List[str]</code>
|
||||
* <code title="class RewriteResult">RewriteResult.<a href="./src/praisonai-agents/praisonaiagents/agent/query_rewriter_agent.py">primary_query</a>() -> str</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">execute_callback</a>(task_output: TaskOutput) -> None</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">execute_callback_sync</a>(task_output: TaskOutput) -> None</code>
|
||||
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">initialize_memory</a>()</code>
|
||||
@@ -587,6 +148,10 @@ Methods:
|
||||
* <code title="cli">praisonai agents info <a href="./src/praisonai/praisonai/cli/commands/agents.py">--help</a></code>
|
||||
* <code title="cli">praisonai agents list <a href="./src/praisonai/praisonai/cli/commands/agents.py">--help</a></code>
|
||||
* <code title="cli">praisonai audit agent-centric <a href="./src/praisonai/praisonai/cli/commands/audit.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch batch-run <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch list <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch report <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
* <code title="cli">praisonai batch stats <a href="./src/praisonai/praisonai/cli/commands/batch.py">--help</a></code>
|
||||
* <code title="cli">praisonai benchmark agent <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
* <code title="cli">praisonai benchmark benchmark-callback <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
* <code title="cli">praisonai benchmark cli <a href="./src/praisonai/praisonai/cli/commands/benchmark.py">--help</a></code>
|
||||
@@ -614,8 +179,11 @@ Methods:
|
||||
* <code title="cli">praisonai context clear <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context compact <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context export <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context grep <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context list <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context show <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context stats <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai context tail <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
|
||||
* <code title="cli">praisonai debug acp <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
|
||||
* <code title="cli">praisonai debug debug-callback <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
|
||||
* <code title="cli">praisonai debug interactive <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
|
||||
@@ -635,6 +203,7 @@ Methods:
|
||||
* <code title="cli">praisonai docs run-all <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
|
||||
* <code title="cli">praisonai docs serve <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
|
||||
* <code title="cli">praisonai docs stats <a href="./src/praisonai/praisonai/cli/commands/docs.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor cleanup <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor config <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor db <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor doctor-callback <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
@@ -644,6 +213,7 @@ Methods:
|
||||
* <code title="cli">praisonai doctor performance <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor selftest <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor tools <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor troubleshoot <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai endpoints list <a href="./src/praisonai/praisonai/cli/commands/endpoints.py">--help</a></code>
|
||||
* <code title="cli">praisonai endpoints test <a href="./src/praisonai/praisonai/cli/commands/endpoints.py">--help</a></code>
|
||||
* <code title="cli">praisonai environment check <a href="./src/praisonai/praisonai/cli/commands/environment.py">--help</a></code>
|
||||
@@ -670,14 +240,20 @@ Methods:
|
||||
* <code title="cli">praisonai lsp status <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
|
||||
* <code title="cli">praisonai lsp stop <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp add <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp describe <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp list <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp mcp-callback <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp remove <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp run <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp status <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp sync <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp test <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai mcp tools <a href="./src/praisonai/praisonai/cli/commands/mcp.py">--help</a></code>
|
||||
* <code title="cli">praisonai memory add <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
|
||||
* <code title="cli">praisonai memory clear <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
|
||||
* <code title="cli">praisonai memory search <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
|
||||
* <code title="cli">praisonai memory show <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
|
||||
* <code title="cli">praisonai memory status <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
|
||||
* <code title="cli">praisonai package install <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
* <code title="cli">praisonai package list <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
* <code title="cli">praisonai package uninstall <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
|
||||
@@ -728,12 +304,19 @@ Methods:
|
||||
* <code title="cli">praisonai session resume <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session show <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills create <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills info <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills install <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills list <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills search <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai skills validate <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
|
||||
* <code title="cli">praisonai standardise check <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
|
||||
* <code title="cli">praisonai standardise fix <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
|
||||
* <code title="cli">praisonai standardise init <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
|
||||
* <code title="cli">praisonai standardise report <a href="./src/praisonai/praisonai/cli/commands/standardise.py">--help</a></code>
|
||||
* <code title="cli">praisonai templates create <a href="./src/praisonai/praisonai/cli/commands/templates.py">--help</a></code>
|
||||
* <code title="cli">praisonai templates list <a href="./src/praisonai/praisonai/cli/commands/templates.py">--help</a></code>
|
||||
* <code title="cli">praisonai test info <a href="./src/praisonai/praisonai/cli/commands/test.py">--help</a></code>
|
||||
* <code title="cli">praisonai test interactive <a href="./src/praisonai/praisonai/cli/commands/test.py">--help</a></code>
|
||||
* <code title="cli">praisonai test run <a href="./src/praisonai/praisonai/cli/commands/test.py">--help</a></code>
|
||||
* <code title="cli">praisonai todo add <a href="./src/praisonai/praisonai/cli/commands/todo.py">--help</a></code>
|
||||
* <code title="cli">praisonai todo done <a href="./src/praisonai/praisonai/cli/commands/todo.py">--help</a></code>
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=3.7.3" \
|
||||
"praisonai>=3.9.3" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=3.7.3" \
|
||||
"praisonai>=3.9.3" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=3.7.3" \
|
||||
"praisonai>=3.9.3" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.3
|
||||
tools:
|
||||
- "InternetSearchTool"
|
||||
@@ -76,7 +76,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.3
|
||||
tools:
|
||||
- "InternetSearchTool"
|
||||
@@ -130,7 +130,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.3
|
||||
tools:
|
||||
- "InternetSearchTool"
|
||||
@@ -185,7 +185,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.4
|
||||
tools:
|
||||
- "InternetSearchTool"
|
||||
@@ -239,7 +239,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.3
|
||||
tools:
|
||||
- "InternetSearchTool"
|
||||
@@ -299,7 +299,7 @@ agents: # Canonical: use 'agents' instead of 'roles'
|
||||
verbose: true
|
||||
allow_delegation: false
|
||||
llm:
|
||||
model: "gpt-5-nano"
|
||||
model: "gpt-4o-mini"
|
||||
temperature: 0.3
|
||||
tasks:
|
||||
report_synthesis:
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Multi-Server MCP Example.
|
||||
|
||||
This example demonstrates how to use multiple MCP servers with PraisonAI Agents,
|
||||
including mixing MCP tools with regular Python function tools.
|
||||
|
||||
Requirements:
|
||||
- Node.js and npx installed (or uvx for Python-based servers)
|
||||
- pip install praisonaiagents[mcp]
|
||||
- OPENAI_API_KEY environment variable set
|
||||
|
||||
Usage:
|
||||
python multi_server.py
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, MCP
|
||||
import os
|
||||
|
||||
|
||||
def calculate(expression: str) -> str:
|
||||
"""
|
||||
A simple calculator tool that evaluates mathematical expressions.
|
||||
|
||||
Args:
|
||||
expression: A mathematical expression to evaluate (e.g., "2 + 2")
|
||||
|
||||
Returns:
|
||||
The result of the calculation
|
||||
"""
|
||||
try:
|
||||
# Safe evaluation of mathematical expressions
|
||||
allowed_chars = set("0123456789+-*/.() ")
|
||||
if not all(c in allowed_chars for c in expression):
|
||||
return "Error: Invalid characters in expression"
|
||||
result = eval(expression)
|
||||
return f"Result: {result}"
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
return
|
||||
|
||||
print("=" * 60)
|
||||
print("Multi-Server MCP Example")
|
||||
print("=" * 60)
|
||||
|
||||
# Create MCP instance with Time server
|
||||
print("\nInitializing MCP servers...")
|
||||
try:
|
||||
time_mcp = MCP("uvx mcp-server-time", timeout=30)
|
||||
print("✓ Time MCP server initialized")
|
||||
except Exception as e:
|
||||
print(f"✗ Time MCP server failed: {e}")
|
||||
return
|
||||
|
||||
# Create an agent with multiple tool sources
|
||||
agent = Agent(
|
||||
name="MultiToolAgent",
|
||||
instructions="""You are a helpful assistant with access to multiple tools:
|
||||
1. Time tools - for getting current time in different timezones
|
||||
2. Calculator - for mathematical calculations
|
||||
|
||||
Use the appropriate tool based on the user's request.""",
|
||||
llm="openai/gpt-4o-mini",
|
||||
tools=[time_mcp, calculate] # Mix MCP and regular tools
|
||||
)
|
||||
|
||||
# Show available tools
|
||||
print("\nAvailable tools:")
|
||||
formatted_tools = agent._format_tools_for_completion(agent.tools)
|
||||
for tool in formatted_tools:
|
||||
print(f" - {tool['function']['name']}: {tool['function']['description'][:50]}...")
|
||||
|
||||
# Example queries
|
||||
queries = [
|
||||
"What time is it in Tokyo?",
|
||||
"Calculate 15 * 7 + 23",
|
||||
"What's the current time in New York and London?"
|
||||
]
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Running queries...")
|
||||
print("=" * 60)
|
||||
|
||||
for i, query in enumerate(queries, 1):
|
||||
print(f"\n--- Query {i} ---")
|
||||
print(f"Question: {query}\n")
|
||||
|
||||
try:
|
||||
response = agent.chat(query)
|
||||
print(f"Response:\n{response}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
print("-" * 40)
|
||||
|
||||
# Clean up
|
||||
time_mcp.shutdown()
|
||||
print("\nDone!")
|
||||
|
||||
|
||||
def demo_multiple_mcp_servers():
|
||||
"""
|
||||
Demonstrate using multiple MCP servers simultaneously.
|
||||
|
||||
This function shows how to create agents with multiple MCP servers,
|
||||
each providing different capabilities.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("Multiple MCP Servers Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Initialize multiple MCP servers
|
||||
servers = {}
|
||||
|
||||
try:
|
||||
servers['time'] = MCP("uvx mcp-server-time", timeout=30)
|
||||
print("✓ Time server initialized")
|
||||
except Exception as e:
|
||||
print(f"✗ Time server failed: {e}")
|
||||
|
||||
# Create agent with all available servers
|
||||
available_tools = list(servers.values())
|
||||
|
||||
if not available_tools:
|
||||
print("No MCP servers available")
|
||||
return
|
||||
|
||||
agent = Agent(
|
||||
name="MultiServerAgent",
|
||||
instructions="You have access to multiple MCP servers. Use the appropriate tools.",
|
||||
llm="openai/gpt-4o-mini",
|
||||
tools=available_tools
|
||||
)
|
||||
|
||||
print(f"\nAgent created with {len(available_tools)} MCP server(s)")
|
||||
|
||||
# Clean up
|
||||
for server in servers.values():
|
||||
server.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Sequential Thinking MCP Example.
|
||||
|
||||
This example demonstrates how to use the Sequential Thinking MCP server
|
||||
with PraisonAI Agents to break down complex problems step by step.
|
||||
|
||||
Requirements:
|
||||
- Node.js and npx installed
|
||||
- pip install praisonaiagents[mcp]
|
||||
- OPENAI_API_KEY environment variable set
|
||||
|
||||
Usage:
|
||||
python sequential_thinking.py
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent, MCP
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
# Check for API key
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Please set OPENAI_API_KEY environment variable")
|
||||
return
|
||||
|
||||
# Create MCP instance with Sequential Thinking server
|
||||
# The server provides tools for step-by-step problem analysis
|
||||
sequential_mcp = MCP(
|
||||
"npx -y @modelcontextprotocol/server-sequential-thinking",
|
||||
timeout=60
|
||||
)
|
||||
|
||||
# Create an agent with the Sequential Thinking tools
|
||||
agent = Agent(
|
||||
name="ProblemSolver",
|
||||
instructions="""You are an expert problem solver who breaks down complex problems
|
||||
into manageable steps. When given a problem:
|
||||
1. Use the sequential thinking tool to analyze it step by step
|
||||
2. Provide clear, actionable steps
|
||||
3. Explain your reasoning at each step""",
|
||||
llm="openai/gpt-4o-mini",
|
||||
tools=sequential_mcp
|
||||
)
|
||||
|
||||
# Example problems to solve
|
||||
problems = [
|
||||
"Break down the process of making a cup of tea",
|
||||
"How would you plan a weekend trip to a new city?",
|
||||
"What steps would you take to learn a new programming language?"
|
||||
]
|
||||
|
||||
print("=" * 60)
|
||||
print("Sequential Thinking MCP Example")
|
||||
print("=" * 60)
|
||||
|
||||
for i, problem in enumerate(problems, 1):
|
||||
print(f"\n--- Problem {i} ---")
|
||||
print(f"Question: {problem}\n")
|
||||
|
||||
response = agent.chat(problem)
|
||||
print(f"Response:\n{response}")
|
||||
print("-" * 40)
|
||||
|
||||
# Clean up
|
||||
sequential_mcp.shutdown()
|
||||
print("\nDone!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -46,7 +46,7 @@ agent = RouterAgent(
|
||||
name="Smart Assistant",
|
||||
role="Adaptive AI Assistant",
|
||||
goal="Complete tasks using the most appropriate model",
|
||||
models=["gpt-5-nano", "gpt-5-nano", "claude-3-5-sonnet-20241022"],
|
||||
models=["gpt-4o-mini", "gpt-4o-mini", "claude-3-5-sonnet-20241022"],
|
||||
routing_strategy="auto" # Automatic model selection
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ analyzer = RouterAgent(
|
||||
goal="Analyze data efficiently",
|
||||
models={
|
||||
"gemini/gemini-1.5-flash": {},
|
||||
"gpt-5-nano": {},
|
||||
"gpt-4o-mini": {},
|
||||
"claude-3-haiku-20240307": {}
|
||||
},
|
||||
model_router=router,
|
||||
@@ -207,7 +207,7 @@ for i, agent in enumerate(auto.agents):
|
||||
name=agent.name,
|
||||
role=agent.role,
|
||||
goal=agent.goal,
|
||||
models=["gpt-5-nano", "gpt-5-nano", "claude-3-5-sonnet"],
|
||||
models=["gpt-4o-mini", "gpt-4o-mini", "claude-3-5-sonnet"],
|
||||
routing_strategy="auto"
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Information Researcher",
|
||||
goal="Store information with semantic understanding",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create task
|
||||
|
||||
@@ -17,7 +17,7 @@ def my_function():
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
with track_api_call("agent_request"):
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
{
|
||||
"timestamp": "2025-12-31T18:45:38.187539Z",
|
||||
"timestamp": "2026-01-16T05:23:04.919943Z",
|
||||
"metadata": {
|
||||
"python_version": "3.12.11",
|
||||
"platform": "macOS-15.7.4-arm64-arm-64bit",
|
||||
"praisonai_version": "2.9.2",
|
||||
"timestamp": "2025-12-31T18:45:38.226558Z"
|
||||
"platform": "macOS-26.3-arm64-arm-64bit",
|
||||
"praisonai_version": "3.9.2",
|
||||
"timestamp": "2026-01-16T05:23:04.932474Z"
|
||||
},
|
||||
"startup": {
|
||||
"cold_ms": 80.9305000002496,
|
||||
"warm_ms": 55.266541996388696
|
||||
"cold_ms": 27.866583000104583,
|
||||
"warm_ms": 27.75670800019725
|
||||
},
|
||||
"import_analysis": [
|
||||
{
|
||||
"module": "praisonaiagents",
|
||||
"self_us": 2419,
|
||||
"cumulative_us": 1645390,
|
||||
"self_ms": 2.419,
|
||||
"cumulative_ms": 1645.39
|
||||
"self_us": 403,
|
||||
"cumulative_us": 188329,
|
||||
"self_ms": 0.403,
|
||||
"cumulative_ms": 188.329
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.workflows",
|
||||
"self_us": 670,
|
||||
"cumulative_us": 1375919,
|
||||
"self_ms": 0.67,
|
||||
"cumulative_ms": 1375.919
|
||||
"self_us": 106,
|
||||
"cumulative_us": 112180,
|
||||
"self_ms": 0.106,
|
||||
"cumulative_ms": 112.18
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.workflows.yaml_parser",
|
||||
"self_us": 775,
|
||||
"cumulative_us": 1369435,
|
||||
"self_ms": 0.775,
|
||||
"cumulative_ms": 1369.435
|
||||
"self_us": 217,
|
||||
"cumulative_us": 107210,
|
||||
"self_ms": 0.217,
|
||||
"cumulative_ms": 107.21
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent.agent",
|
||||
"self_us": 19,
|
||||
"cumulative_us": 1349174,
|
||||
"self_ms": 0.019,
|
||||
"cumulative_ms": 1349.174
|
||||
"self_us": 15,
|
||||
"cumulative_us": 98811,
|
||||
"self_ms": 0.015,
|
||||
"cumulative_ms": 98.811
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent",
|
||||
"self_us": 537,
|
||||
"cumulative_us": 1349155,
|
||||
"self_ms": 0.537,
|
||||
"cumulative_ms": 1349.155
|
||||
"self_us": 146,
|
||||
"cumulative_us": 98796,
|
||||
"self_ms": 0.146,
|
||||
"cumulative_ms": 98.796
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.agent.agent",
|
||||
"self_us": 1453,
|
||||
"cumulative_us": 1285299,
|
||||
"self_ms": 1.453,
|
||||
"cumulative_ms": 1285.299
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.llm.openai_client",
|
||||
"self_us": 7887,
|
||||
"cumulative_us": 1123578,
|
||||
"self_ms": 7.887,
|
||||
"cumulative_ms": 1123.578
|
||||
},
|
||||
{
|
||||
"module": "openai",
|
||||
"self_us": 1083,
|
||||
"cumulative_us": 1115692,
|
||||
"self_ms": 1.083,
|
||||
"cumulative_ms": 1115.692
|
||||
},
|
||||
{
|
||||
"module": "openai.types",
|
||||
"self_us": 2606,
|
||||
"cumulative_us": 957990,
|
||||
"self_ms": 2.606,
|
||||
"cumulative_ms": 957.99
|
||||
},
|
||||
{
|
||||
"module": "openai.types.batch",
|
||||
"self_us": 1787,
|
||||
"cumulative_us": 485667,
|
||||
"self_ms": 1.787,
|
||||
"cumulative_ms": 485.667
|
||||
},
|
||||
{
|
||||
"module": "openai._models",
|
||||
"self_us": 13423,
|
||||
"cumulative_us": 450798,
|
||||
"self_ms": 13.423,
|
||||
"cumulative_ms": 450.798
|
||||
},
|
||||
{
|
||||
"module": "openai.types.eval_create_params",
|
||||
"self_us": 644,
|
||||
"cumulative_us": 393275,
|
||||
"self_ms": 0.644,
|
||||
"cumulative_ms": 393.275
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.grader_inputs_param",
|
||||
"self_us": 17,
|
||||
"cumulative_us": 359515,
|
||||
"self_ms": 0.017,
|
||||
"cumulative_ms": 359.515
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders",
|
||||
"self_us": 392,
|
||||
"cumulative_us": 359498,
|
||||
"self_ms": 0.392,
|
||||
"cumulative_ms": 359.498
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.multi_grader",
|
||||
"self_us": 3847,
|
||||
"cumulative_us": 351251,
|
||||
"self_ms": 3.847,
|
||||
"cumulative_ms": 351.251
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.label_model_grader",
|
||||
"self_us": 5450,
|
||||
"cumulative_us": 337291,
|
||||
"self_ms": 5.45,
|
||||
"cumulative_ms": 337.291
|
||||
},
|
||||
{
|
||||
"module": "openai.types.graders.grader_inputs",
|
||||
"self_us": 545,
|
||||
"cumulative_us": 331841,
|
||||
"self_ms": 0.545,
|
||||
"cumulative_ms": 331.841
|
||||
},
|
||||
{
|
||||
"module": "openai.types.responses.response_input_text",
|
||||
"self_us": 21,
|
||||
"cumulative_us": 331297,
|
||||
"self_ms": 0.021,
|
||||
"cumulative_ms": 331.297
|
||||
},
|
||||
{
|
||||
"module": "openai.types.responses",
|
||||
"self_us": 1301,
|
||||
"cumulative_us": 331277,
|
||||
"self_ms": 1.301,
|
||||
"cumulative_ms": 331.277
|
||||
"self_us": 1027,
|
||||
"cumulative_us": 86223,
|
||||
"self_ms": 1.027,
|
||||
"cumulative_ms": 86.223
|
||||
},
|
||||
{
|
||||
"module": "rich.logging",
|
||||
"self_us": 1066,
|
||||
"cumulative_us": 238256,
|
||||
"self_ms": 1.066,
|
||||
"cumulative_ms": 238.256
|
||||
"self_us": 282,
|
||||
"cumulative_us": 53009,
|
||||
"self_ms": 0.282,
|
||||
"cumulative_ms": 53.009
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.main",
|
||||
"self_us": 18784,
|
||||
"cumulative_us": 45759,
|
||||
"self_ms": 18.784,
|
||||
"cumulative_ms": 45.759
|
||||
},
|
||||
{
|
||||
"module": "praisonaiagents.llm.openai_client",
|
||||
"self_us": 2470,
|
||||
"cumulative_us": 28251,
|
||||
"self_ms": 2.47,
|
||||
"cumulative_ms": 28.251
|
||||
},
|
||||
{
|
||||
"module": "rich.traceback",
|
||||
"self_us": 1478,
|
||||
"cumulative_us": 18788,
|
||||
"self_ms": 1.478,
|
||||
"cumulative_ms": 18.788
|
||||
},
|
||||
{
|
||||
"module": "rich.console",
|
||||
"self_us": 1737,
|
||||
"cumulative_us": 18587,
|
||||
"self_ms": 1.737,
|
||||
"cumulative_ms": 18.587
|
||||
},
|
||||
{
|
||||
"module": "pygments.lexers",
|
||||
"self_us": 242,
|
||||
"cumulative_us": 15427,
|
||||
"self_ms": 0.242,
|
||||
"cumulative_ms": 15.427
|
||||
},
|
||||
{
|
||||
"module": "rich.markdown",
|
||||
"self_us": 409,
|
||||
"cumulative_us": 13987,
|
||||
"self_ms": 0.409,
|
||||
"cumulative_ms": 13.987
|
||||
},
|
||||
{
|
||||
"module": "pygments.plugin",
|
||||
"self_us": 85,
|
||||
"cumulative_us": 13503,
|
||||
"self_ms": 0.085,
|
||||
"cumulative_ms": 13.503
|
||||
},
|
||||
{
|
||||
"module": "importlib.metadata",
|
||||
"self_us": 637,
|
||||
"cumulative_us": 13419,
|
||||
"self_ms": 0.637,
|
||||
"cumulative_ms": 13.419
|
||||
},
|
||||
{
|
||||
"module": "rich._log_render",
|
||||
"self_us": 145,
|
||||
"cumulative_us": 13380,
|
||||
"self_ms": 0.145,
|
||||
"cumulative_ms": 13.38
|
||||
},
|
||||
{
|
||||
"module": "markdown_it",
|
||||
"self_us": 172,
|
||||
"cumulative_us": 13339,
|
||||
"self_ms": 0.172,
|
||||
"cumulative_ms": 13.339
|
||||
},
|
||||
{
|
||||
"module": "rich.text",
|
||||
"self_us": 582,
|
||||
"cumulative_us": 13236,
|
||||
"self_ms": 0.582,
|
||||
"cumulative_ms": 13.236
|
||||
},
|
||||
{
|
||||
"module": "markdown_it.main",
|
||||
"self_us": 298,
|
||||
"cumulative_us": 13168,
|
||||
"self_ms": 0.298,
|
||||
"cumulative_ms": 13.168
|
||||
},
|
||||
{
|
||||
"module": "pydantic",
|
||||
"self_us": 223,
|
||||
"cumulative_us": 11119,
|
||||
"self_ms": 0.223,
|
||||
"cumulative_ms": 11.119
|
||||
}
|
||||
],
|
||||
"scenarios": [
|
||||
@@ -162,28 +162,22 @@
|
||||
"iterations": 2
|
||||
},
|
||||
"import_time_stats": {
|
||||
"mean": 0.008833496394800022,
|
||||
"min": 0.005916997906751931,
|
||||
"max": 0.011749994882848114,
|
||||
"stdev": 0.0041245517164382375,
|
||||
"median": 0.008833496394800022
|
||||
"mean": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"stdev": 0,
|
||||
"median": 0
|
||||
},
|
||||
"total_time_stats": {
|
||||
"mean": 1727.4090415012324,
|
||||
"min": 1367.2264170018025,
|
||||
"max": 2087.5916660006624,
|
||||
"stdev": 509.3751524982296,
|
||||
"median": 1727.4090415012324
|
||||
"mean": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"stdev": 0,
|
||||
"median": 0
|
||||
},
|
||||
"first_token_stats": null,
|
||||
"raw_import_times_ms": [
|
||||
0.011749994882848114,
|
||||
0.005916997906751931
|
||||
],
|
||||
"raw_total_times_ms": [
|
||||
1367.2264170018025,
|
||||
2087.5916660006624
|
||||
]
|
||||
"raw_import_times_ms": [],
|
||||
"raw_total_times_ms": []
|
||||
},
|
||||
{
|
||||
"name": "simple_stream",
|
||||
@@ -194,34 +188,22 @@
|
||||
"iterations": 2
|
||||
},
|
||||
"import_time_stats": {
|
||||
"mean": 0.008792001608526334,
|
||||
"min": 0.006375004886649549,
|
||||
"max": 0.01120899833040312,
|
||||
"stdev": 0.0034181495442894613,
|
||||
"median": 0.008792001608526334
|
||||
"mean": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"stdev": 0,
|
||||
"median": 0
|
||||
},
|
||||
"total_time_stats": {
|
||||
"mean": 2321.0982290001994,
|
||||
"min": 1697.1089580038097,
|
||||
"max": 2945.087499996589,
|
||||
"stdev": 882.4540898183949,
|
||||
"median": 2321.0982290001994
|
||||
"mean": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"stdev": 0,
|
||||
"median": 0
|
||||
},
|
||||
"first_token_stats": {
|
||||
"mean": 2312.8511880022415,
|
||||
"min": 1681.2440420035273,
|
||||
"max": 2944.458334000956,
|
||||
"stdev": 893.2273919631452,
|
||||
"median": 2312.8511880022415
|
||||
},
|
||||
"raw_import_times_ms": [
|
||||
0.01120899833040312,
|
||||
0.006375004886649549
|
||||
],
|
||||
"raw_total_times_ms": [
|
||||
2945.087499996589,
|
||||
1697.1089580038097
|
||||
]
|
||||
"first_token_stats": null,
|
||||
"raw_import_times_ms": [],
|
||||
"raw_total_times_ms": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -13,7 +13,7 @@ agent = Agent(
|
||||
role="Assistant",
|
||||
goal="Help users",
|
||||
backstory="You are helpful.",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
metrics=True # 🎯 That's it!
|
||||
)
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ Set your API keys:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-api-key"
|
||||
export OPENAI_MODEL_NAME="gpt-5-nano"
|
||||
export OPENAI_MODEL_NAME="gpt-4o-mini"
|
||||
```
|
||||
|
||||
## 📝 Customization
|
||||
|
||||
@@ -38,7 +38,7 @@ def main():
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant that explains scientific concepts simply",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Track the API call performance
|
||||
|
||||
@@ -13,8 +13,8 @@ def main():
|
||||
role="Research Specialist",
|
||||
goal="Find information about a topic",
|
||||
backstory="You are an expert researcher who finds comprehensive information.",
|
||||
verbose=True,
|
||||
llm="gpt-5-nano", # Use a cost-effective model for examples
|
||||
output="verbose",
|
||||
llm="gpt-4o-mini", # Use a cost-effective model for examples
|
||||
metrics=True # 🎯 SIMPLIFIED: Just add this parameter!
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ def main():
|
||||
agents = Agents(
|
||||
agents=[research_agent],
|
||||
tasks=[research_task],
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
print("🚀 Running agent task...")
|
||||
|
||||
@@ -54,7 +54,7 @@ def create_agents():
|
||||
goal="Gather comprehensive information on renewable energy",
|
||||
backstory="Expert researcher with deep knowledge of energy sector",
|
||||
instructions="You are a thorough researcher. Provide detailed, fact-based information.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
analyst = Agent(
|
||||
@@ -63,7 +63,7 @@ def create_agents():
|
||||
goal="Analyze trends and patterns in renewable energy data",
|
||||
backstory="Experienced data analyst specializing in energy markets",
|
||||
instructions="You are an analytical thinker. Focus on trends, patterns, and insights.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
@@ -72,7 +72,7 @@ def create_agents():
|
||||
goal="Create clear, compelling reports",
|
||||
backstory="Professional technical writer with expertise in energy sector",
|
||||
instructions="You are a skilled writer. Create clear, well-structured reports.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
return researcher, analyst, writer
|
||||
@@ -104,8 +104,7 @@ def main():
|
||||
workflow = Agents(
|
||||
agents=[researcher, analyst, writer],
|
||||
tasks=[research_task, analysis_task, report_task],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
result = workflow.start()
|
||||
|
||||
@@ -11,7 +11,7 @@ Shows how to monitor agent performance when using external tools.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.tools import duckduckgo, wikipedia
|
||||
from praisonaiagents.tools import duckduckgo, wikipedia_tools
|
||||
from praisonaiagents.telemetry import (
|
||||
monitor_function, track_api_call, get_api_stats,
|
||||
get_slowest_functions, performance_monitor
|
||||
@@ -48,8 +48,8 @@ def main():
|
||||
instructions="""You are a research assistant that helps find information.
|
||||
Use the available tools to search for information and provide comprehensive answers.
|
||||
Be thorough in your research and cite your sources.""",
|
||||
llm="gpt-5-nano",
|
||||
tools=[duckduckgo, wikipedia] # Add search tools
|
||||
llm="gpt-4o-mini",
|
||||
tools=[duckduckgo, wikipedia_tools] # Add search tools
|
||||
)
|
||||
|
||||
# Test queries to demonstrate tool performance monitoring
|
||||
@@ -97,7 +97,9 @@ def main():
|
||||
# Slowest functions analysis
|
||||
print("\n🐌 Slowest Functions:")
|
||||
slowest = get_slowest_functions()
|
||||
for func_name, avg_time in slowest:
|
||||
for item in slowest[:5]:
|
||||
func_name = item.get('function', 'unknown')
|
||||
avg_time = item.get('average_time', 0)
|
||||
print(f" {func_name}: {avg_time:.3f}s average")
|
||||
|
||||
# Real-time performance data
|
||||
|
||||
@@ -54,7 +54,7 @@ async def run_async_agent(query: str):
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
instructions="You are an efficient AI assistant that processes requests quickly",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Track the agent execution
|
||||
|
||||
@@ -60,7 +60,7 @@ def run_robust_agent(query: str, with_error_simulation: bool = True):
|
||||
instructions="""You are a resilient AI assistant.
|
||||
Even when facing challenges, you provide helpful responses.
|
||||
If you encounter errors, acknowledge them and offer alternatives.""",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -65,7 +65,7 @@ def run_stateful_agent(query: str, session_id: str, conversation_turn: int):
|
||||
instructions="""You are a memory-enabled AI assistant. You can remember
|
||||
previous conversations and build on past interactions. Use your memory
|
||||
to provide personalized and contextual responses.""",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Pre-processing: retrieve relevant memories
|
||||
@@ -165,7 +165,9 @@ def main():
|
||||
# Slowest operations
|
||||
print("\n🐌 Slowest Memory Operations:")
|
||||
slowest = get_slowest_functions()
|
||||
for func_name, avg_time in slowest[:5]: # Top 5 slowest
|
||||
for item in slowest[:5]: # Top 5 slowest
|
||||
func_name = item.get('function', 'unknown')
|
||||
avg_time = item.get('average_time', 0)
|
||||
print(f" {func_name}: {avg_time:.3f}s average")
|
||||
|
||||
# Memory operation breakdown
|
||||
|
||||
@@ -77,7 +77,7 @@ def run_hierarchical_workflow():
|
||||
backstory="Experienced project manager with expertise in team coordination",
|
||||
instructions="""You are a project manager. Break down complex projects into tasks,
|
||||
assign them to appropriate team members, and coordinate the overall effort.""",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create worker agents
|
||||
@@ -87,7 +87,7 @@ def run_hierarchical_workflow():
|
||||
goal="Conduct thorough research on assigned topics",
|
||||
backstory="Expert researcher with deep analytical skills",
|
||||
instructions="You are a research specialist. Provide detailed, factual information.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
analysis_worker = Agent(
|
||||
@@ -96,7 +96,7 @@ def run_hierarchical_workflow():
|
||||
goal="Analyze data and identify patterns and insights",
|
||||
backstory="Experienced data analyst with strong analytical thinking",
|
||||
instructions="You are a data analyst. Focus on patterns, trends, and insights.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
writing_worker = Agent(
|
||||
@@ -105,7 +105,7 @@ def run_hierarchical_workflow():
|
||||
goal="Create clear, well-structured documentation and reports",
|
||||
backstory="Professional writer specializing in technical content",
|
||||
instructions="You are a technical writer. Create clear, comprehensive reports.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Project tasks
|
||||
|
||||
@@ -180,7 +180,7 @@ def run_agent_with_custom_tools():
|
||||
instructions="""You are a technical assistant with access to various tools.
|
||||
Use the appropriate tools to help with data processing, API calls, database queries,
|
||||
and file operations. Always explain what tools you're using and why.""",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[process_data, call_external_api, query_database, file_operation]
|
||||
)
|
||||
|
||||
@@ -263,7 +263,7 @@ def main():
|
||||
# Slowest operations
|
||||
print("\n🐌 Slowest Tool Operations:")
|
||||
slowest = get_slowest_functions()
|
||||
tool_slowest = [(name, time) for name, time in slowest if 'tool' in name.lower()]
|
||||
tool_slowest = [(item.get('function', 'unknown'), item.get('average_time', 0)) for item in slowest if 'tool' in item.get('function', '').lower()]
|
||||
|
||||
for tool_name, avg_time in tool_slowest[:3]:
|
||||
print(f" {tool_name}: {avg_time:.3f}s")
|
||||
|
||||
@@ -60,7 +60,7 @@ def get_real_time_agent_response(query: str, response_id: int):
|
||||
# Create a lightweight agent for streaming
|
||||
agent = Agent(
|
||||
instructions="You are a real-time assistant. Provide quick, helpful responses.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
with track_api_call(f"streaming_response_{response_id}"):
|
||||
|
||||
@@ -42,19 +42,19 @@ def collect_comprehensive_data():
|
||||
agents = {
|
||||
'quick_agent': Agent(
|
||||
instructions="You are a quick-response agent. Provide concise answers.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
),
|
||||
'analytical_agent': Agent(
|
||||
instructions="You are an analytical agent. Provide detailed analysis.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
),
|
||||
'research_agent': Agent(
|
||||
instructions="You are a research agent. Provide comprehensive information.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
),
|
||||
'creative_agent': Agent(
|
||||
instructions="You are a creative agent. Provide imaginative responses.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,19 +98,19 @@ def simulate_complex_workflow():
|
||||
planner = Agent(
|
||||
name="planner",
|
||||
instructions="You are a project planner. Break down tasks and plan execution.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
executor = Agent(
|
||||
name="executor",
|
||||
instructions="You are a task executor. Complete assigned tasks efficiently.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
reviewer = Agent(
|
||||
name="reviewer",
|
||||
instructions="You are a quality reviewer. Review and improve work.",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create workflow tasks
|
||||
@@ -302,13 +302,17 @@ def display_dashboard(dashboard_data, benchmark_results):
|
||||
perf_data = dashboard_data['performance_data']
|
||||
|
||||
print(" Fastest Functions:")
|
||||
fastest_functions = sorted(perf_data['slowest_functions'], key=lambda x: x[1])[:3]
|
||||
for name, time in fastest_functions:
|
||||
print(f" {name}: {time:.3f}s")
|
||||
fastest_functions = sorted(perf_data['slowest_functions'], key=lambda x: x.get('average_time', float('inf')))[:3]
|
||||
for item in fastest_functions:
|
||||
name = item.get('function', 'unknown')
|
||||
time_val = item.get('average_time', 0)
|
||||
print(f" {name}: {time_val:.3f}s")
|
||||
|
||||
print(" Slowest Functions:")
|
||||
for name, time in perf_data['slowest_functions'][:3]:
|
||||
print(f" {name}: {time:.3f}s")
|
||||
for item in perf_data['slowest_functions'][:3]:
|
||||
name = item.get('function', 'unknown')
|
||||
time_val = item.get('average_time', 0)
|
||||
print(f" {name}: {time_val:.3f}s")
|
||||
|
||||
# Benchmark Results
|
||||
print(f"\n⚡ BENCHMARK RESULTS")
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
PraisonAI Agents is a hierarchical AI agent framework for completing complex tasks with self-reflection capabilities. It supports multi-agent collaboration, tool integration, and various execution patterns (sequential, hierarchical, parallel).
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Installation and Setup
|
||||
```bash
|
||||
# Install core package
|
||||
pip install -e .
|
||||
|
||||
# Install with specific features
|
||||
pip install -e .[all] # All features
|
||||
pip install -e .[memory] # Memory capabilities
|
||||
pip install -e .[knowledge] # Document processing
|
||||
pip install -e .[mcp] # MCP server support
|
||||
pip install -e .[llm] # Extended LLM support
|
||||
pip install -e .[api] # API server capabilities
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run individual test examples (no formal test runner configured)
|
||||
python tests/basic-agents.py
|
||||
python tests/async_example.py
|
||||
python tests/knowledge-agents.py
|
||||
|
||||
# Test specific features
|
||||
python tests/mcp-agents.py # MCP integration
|
||||
python tests/memory_example.py # Memory functionality
|
||||
python tests/tools_example.py # Tool system
|
||||
```
|
||||
|
||||
### Running Examples
|
||||
```bash
|
||||
# Basic agent usage
|
||||
python tests/single-agent.py
|
||||
|
||||
# Multi-agent workflows
|
||||
python tests/multi-agents-api.py
|
||||
|
||||
# Async operations
|
||||
python tests/async_example_full.py
|
||||
|
||||
# MCP server examples
|
||||
python tests/mcp-sse-direct-server.py # Start MCP server
|
||||
python tests/mcp-sse-direct-client.py # Connect to server
|
||||
```
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Agent System (`praisonaiagents/agent/`)
|
||||
- **Agent**: Core agent class with LLM integration, tool calling, and self-reflection
|
||||
- **ImageAgent**: Specialized multimodal agent for image processing
|
||||
- Self-reflection with configurable min/max iterations (default: 1-3)
|
||||
- Delegation support for hierarchical agent structures
|
||||
|
||||
### Multi-Agent Orchestration (`praisonaiagents/agents/`)
|
||||
- **PraisonAIAgents**: Main orchestrator for managing multiple agents and tasks
|
||||
- **AutoAgents**: Automatic agent creation and management
|
||||
- Process types: `sequential`, `hierarchical`, `parallel`
|
||||
- Context passing between agents and task dependency management
|
||||
|
||||
### Task System (`praisonaiagents/task/`)
|
||||
- **Task**: Core task definition with context, callbacks, and output specifications
|
||||
- Supports file output, JSON/Pydantic structured output, async execution
|
||||
- Conditional logic with `condition` parameter for task flow control
|
||||
- Context passing via `context` parameter for task dependencies
|
||||
- **Guardrails**: Built-in validation and safety mechanisms for task outputs
|
||||
- Function-based guardrails for custom validation logic
|
||||
- LLM-based guardrails using natural language descriptions
|
||||
- Automatic retry with configurable `max_retries` parameter
|
||||
- Compatible with CrewAI guardrail patterns
|
||||
|
||||
### LLM Integration (`praisonaiagents/llm/`)
|
||||
- Unified wrapper for multiple LLM providers via LiteLLM
|
||||
- Supports OpenAI, Anthropic, Gemini, DeepSeek, local models (Ollama)
|
||||
- Context length management and tool calling capabilities
|
||||
- Set via `llm` parameter on agents or global `OPENAI_API_KEY`/`ANTHROPIC_API_KEY`
|
||||
|
||||
### Tool System (`praisonaiagents/tools/`)
|
||||
Two implementation patterns:
|
||||
1. **Function-based**: Simple tools using `@tool` decorator
|
||||
2. **Class-based**: Complex tools inheriting from `BaseTool`
|
||||
|
||||
Built-in tools include: DuckDuckGo search, file operations, calculator, Wikipedia, arXiv, data analysis tools, shell execution.
|
||||
|
||||
### Memory & Knowledge Systems
|
||||
- **Memory** (`praisonaiagents/memory/`): Multi-layered memory with RAG support
|
||||
- Types: short-term, long-term, entity, user memory
|
||||
- Providers: ChromaDB, Mem0, custom implementations
|
||||
- **Knowledge** (`praisonaiagents/knowledge/`): Document processing with chunking
|
||||
- Chunking strategies via `chonkie` library
|
||||
- Embedding and retrieval capabilities
|
||||
|
||||
### MCP (Model Context Protocol) Integration
|
||||
- **MCP Server**: Server-side tool protocol for distributed execution
|
||||
- **SSE Support**: Server-sent events for real-time communication
|
||||
- Tool discovery and dynamic registration
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Agent Creation
|
||||
```python
|
||||
agent = Agent(
|
||||
name="Agent Name",
|
||||
role="Agent Role",
|
||||
goal="Agent Goal",
|
||||
backstory="Agent Background",
|
||||
llm="gpt-5-nano", # or other LLM
|
||||
self_reflect=True, # Enable self-reflection
|
||||
min_reflect=1, # Minimum reflection iterations
|
||||
max_reflect=3, # Maximum reflection iterations
|
||||
tools=[tool1, tool2], # Optional tools
|
||||
guardrail=validate_function, # Agent-level guardrail (function or string)
|
||||
max_guardrail_retries=3 # Retry limit for guardrail failures
|
||||
)
|
||||
```
|
||||
|
||||
### Task Definition
|
||||
```python
|
||||
task = Task(
|
||||
name="task_name",
|
||||
description="Task description",
|
||||
expected_output="Expected output format",
|
||||
agent=agent,
|
||||
context=[previous_task], # Task dependencies
|
||||
output_pydantic=ResponseModel, # Structured output
|
||||
condition="condition_function" # Conditional execution
|
||||
)
|
||||
```
|
||||
|
||||
### Guardrails Usage
|
||||
|
||||
#### Task-Level Guardrails
|
||||
```python
|
||||
from typing import Tuple, Any
|
||||
from praisonaiagents import GuardrailResult
|
||||
|
||||
# Function-based guardrail (Option 1: Using GuardrailResult)
|
||||
def validate_output(task_output: TaskOutput) -> GuardrailResult:
|
||||
"""Custom validation function returning GuardrailResult."""
|
||||
if "error" in task_output.raw.lower():
|
||||
return GuardrailResult(
|
||||
success=False,
|
||||
result=None,
|
||||
error="Output contains errors"
|
||||
)
|
||||
if len(task_output.raw) < 10:
|
||||
return GuardrailResult(
|
||||
success=False,
|
||||
result=None,
|
||||
error="Output is too short"
|
||||
)
|
||||
return GuardrailResult(
|
||||
success=True,
|
||||
result=task_output,
|
||||
error=""
|
||||
)
|
||||
|
||||
# Function-based guardrail (Option 2: Using Tuple[bool, Any])
|
||||
def validate_output_tuple(task_output: TaskOutput) -> Tuple[bool, Any]:
|
||||
"""Custom validation function returning tuple."""
|
||||
if "error" in task_output.raw.lower():
|
||||
return False, "Output contains errors"
|
||||
if len(task_output.raw) < 10:
|
||||
return False, "Output is too short"
|
||||
return True, task_output
|
||||
|
||||
task = Task(
|
||||
description="Write a professional email",
|
||||
expected_output="A well-formatted email",
|
||||
agent=agent,
|
||||
guardrail=validate_output, # Function-based guardrail
|
||||
max_retries=3 # Retry up to 3 times if guardrail fails
|
||||
)
|
||||
|
||||
# LLM-based guardrail
|
||||
task = Task(
|
||||
description="Generate marketing copy",
|
||||
expected_output="Professional marketing content",
|
||||
agent=agent,
|
||||
guardrail="Ensure the content is professional, engaging, and free of errors", # String description
|
||||
max_retries=2
|
||||
)
|
||||
```
|
||||
|
||||
#### Agent-Level Guardrails
|
||||
```python
|
||||
# Agent guardrails apply to ALL outputs from that agent
|
||||
|
||||
# Option 1: Using GuardrailResult
|
||||
def validate_professional_tone(task_output: TaskOutput) -> GuardrailResult:
|
||||
"""Ensure professional tone in all agent responses."""
|
||||
content = task_output.raw.lower()
|
||||
casual_words = ['yo', 'dude', 'awesome', 'cool']
|
||||
for word in casual_words:
|
||||
if word in content:
|
||||
return GuardrailResult(
|
||||
success=False,
|
||||
result=None,
|
||||
error=f"Unprofessional language detected: {word}"
|
||||
)
|
||||
return GuardrailResult(
|
||||
success=True,
|
||||
result=task_output,
|
||||
error=""
|
||||
)
|
||||
|
||||
# Option 2: Using Tuple[bool, Any]
|
||||
def validate_professional_tone_tuple(task_output: TaskOutput) -> Tuple[bool, Any]:
|
||||
"""Ensure professional tone in all agent responses."""
|
||||
content = task_output.raw.lower()
|
||||
casual_words = ['yo', 'dude', 'awesome', 'cool']
|
||||
for word in casual_words:
|
||||
if word in content:
|
||||
return False, f"Unprofessional language detected: {word}"
|
||||
return True, task_output
|
||||
|
||||
# Agent with function-based guardrail
|
||||
agent = Agent(
|
||||
name="BusinessWriter",
|
||||
instructions="You are a professional business writer",
|
||||
guardrail=validate_professional_tone, # Function guardrail
|
||||
max_guardrail_retries=3
|
||||
)
|
||||
|
||||
# Agent with LLM-based guardrail
|
||||
agent = Agent(
|
||||
name="ContentWriter",
|
||||
instructions="You are a content writer",
|
||||
guardrail="Ensure all responses are professional, accurate, and appropriate for business use", # String guardrail
|
||||
max_guardrail_retries=2
|
||||
)
|
||||
```
|
||||
|
||||
### Multi-Agent Workflow
|
||||
```python
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[agent1, agent2],
|
||||
tasks=[task1, task2],
|
||||
process="sequential", # or "hierarchical", "parallel"
|
||||
verbose=True,
|
||||
manager_agent=manager_agent # For hierarchical process
|
||||
)
|
||||
result = workflow.start()
|
||||
```
|
||||
|
||||
### Async Support
|
||||
All major components support async execution:
|
||||
```python
|
||||
result = await workflow.astart()
|
||||
result = await agent.aexecute(task)
|
||||
```
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Core**: `pydantic`, `rich`, `openai`, `mcp`
|
||||
- **Memory**: `chromadb`, `mem0ai`
|
||||
- **Knowledge**: `markitdown`, `chonkie`
|
||||
- **LLM**: `litellm` for unified provider access
|
||||
- **API**: `fastapi`, `uvicorn` for server capabilities
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Global error logging via `error_logs` list
|
||||
- Callback system for real-time error reporting
|
||||
- Context length exception handling with automatic retry
|
||||
- Graceful degradation for optional dependencies
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
The project uses example-driven testing with 100+ test files in `tests/` directory. Each test file demonstrates specific usage patterns and serves as both test and documentation. Run individual examples to test functionality rather than using a formal test runner.
|
||||
|
||||
Use conda activate praisonai-agents to activate the environment.
|
||||
@@ -1,98 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Agent-level guardrails example.
|
||||
|
||||
This example demonstrates how to use guardrails at the Agent level,
|
||||
This example demonstrates how to use guardrails at the Agent level
|
||||
which will apply to all tasks executed by that agent.
|
||||
"""
|
||||
|
||||
from typing import Tuple, Any
|
||||
from praisonaiagents import Agent, TaskOutput
|
||||
|
||||
def validate_content_length(task_output: TaskOutput) -> Tuple[bool, Any]:
|
||||
"""
|
||||
Validate that task output content meets minimum length requirement.
|
||||
|
||||
Args:
|
||||
task_output: The task output to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (success, result_or_error_message)
|
||||
"""
|
||||
if len(task_output.raw) < 50:
|
||||
return False, "Content too short - must be at least 50 characters"
|
||||
return True, task_output
|
||||
|
||||
def validate_professional_tone(task_output: TaskOutput) -> Tuple[bool, Any]:
|
||||
"""
|
||||
Validate that the content has a professional tone.
|
||||
|
||||
Args:
|
||||
task_output: The task output to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (success, result_or_error_message)
|
||||
"""
|
||||
content = task_output.raw.lower()
|
||||
unprofessional_words = ['yo', 'dude', 'awesome', 'cool', 'lol']
|
||||
|
||||
for word in unprofessional_words:
|
||||
if word in content:
|
||||
return False, f"Content contains unprofessional word: '{word}'"
|
||||
|
||||
return True, task_output
|
||||
|
||||
def main():
|
||||
"""Demonstrate Agent-level guardrails with function-based and LLM-based validation."""
|
||||
|
||||
print("=== Agent Guardrail Examples ===\n")
|
||||
|
||||
# Example 1: Function-based guardrail
|
||||
print("1. Function-based guardrail (content length validation):")
|
||||
agent1 = Agent(
|
||||
name="ContentWriter",
|
||||
instructions="You are a professional content writer who creates detailed responses",
|
||||
guardrail=validate_content_length,
|
||||
max_guardrail_retries=2
|
||||
name="ContentWriter"
|
||||
instructions="You are a professional content writer who creates detailed responses"
|
||||
guardrails=validate_content_length
|
||||
)
|
||||
|
||||
try:
|
||||
result1 = agent1.start("Write a brief welcome message")
|
||||
print(f"Result: {result1}\n")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}\n")
|
||||
|
||||
# Example 2: LLM-based guardrail (string description)
|
||||
print("2. LLM-based guardrail (professional tone validation):")
|
||||
agent2 = Agent(
|
||||
name="BusinessWriter",
|
||||
instructions="You are a business communication expert",
|
||||
guardrail="Ensure the content is professional, formal, and suitable for business communication. No casual language or slang.",
|
||||
max_guardrail_retries=3
|
||||
instructions="You are a business communication expert"
|
||||
guardrails="Ensure the content is professional, formal, and suitable for business communication. No casual language or slang."
|
||||
)
|
||||
|
||||
try:
|
||||
result2 = agent2.start("Write a welcome message for new employees")
|
||||
print(f"Result: {result2}\n")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}\n")
|
||||
|
||||
# Example 3: Multiple agents with different guardrails
|
||||
print("3. Professional tone function-based guardrail:")
|
||||
agent3 = Agent(
|
||||
name="ProfessionalWriter",
|
||||
instructions="Write professional business content",
|
||||
guardrail=validate_professional_tone,
|
||||
max_guardrail_retries=2
|
||||
name="ProfessionalWriter"
|
||||
instructions="Write professional business content"
|
||||
guardrails=validate_professional_tone
|
||||
)
|
||||
|
||||
try:
|
||||
result3 = agent3.start("Write a casual greeting message")
|
||||
print(f"Result: {result3}\n")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}\n")
|
||||
|
||||
print("=== Agent Guardrails Demonstration Complete ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -21,7 +21,7 @@ article_agent = Agent(
|
||||
goal="Fetch the user's favorite morning article",
|
||||
tools=[fetch_favorite_article],
|
||||
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
news_agent = Agent(
|
||||
@@ -30,7 +30,7 @@ news_agent = Agent(
|
||||
goal="Search for trending news in Kenya",
|
||||
tools=[search_trending_kenya],
|
||||
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
twitter_agent = Agent(
|
||||
@@ -39,7 +39,7 @@ twitter_agent = Agent(
|
||||
goal="Fetch the latest Twitter feed",
|
||||
tools=[fetch_twitter_feed],
|
||||
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
aggregator = Agent(
|
||||
@@ -47,7 +47,7 @@ aggregator = Agent(
|
||||
role="Result Aggregator",
|
||||
goal="Aggregate and summarize all results",
|
||||
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Tasks for each agent
|
||||
@@ -92,7 +92,7 @@ async def main():
|
||||
agents=[article_agent, news_agent, twitter_agent, aggregator],
|
||||
tasks=[article_task, news_task, twitter_task, aggregate_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
results = await workflow.astart(dict_output=True)
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ async def main():
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gemini/gemini-1.5-flash-8b",
|
||||
self_reflect=True,
|
||||
verbose=True
|
||||
reflection=True,
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Use achat instead of start/chat for async operation
|
||||
|
||||
@@ -10,7 +10,7 @@ import os
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
llm_config = {
|
||||
"model": "gpt-5-nano", # Changed from Gemini to gpt-5-nano for better tool support
|
||||
"model": "gpt-4o-mini", # Changed from Gemini to gpt-5-nano for better tool support
|
||||
"temperature": 0.7,
|
||||
"response_format": {
|
||||
"type": "text"
|
||||
@@ -79,7 +79,7 @@ async def run_parallel_searches():
|
||||
backstory="Expert in web search and information retrieval. You have access to the tavily_search tool to search the web.",
|
||||
tools=[tavily_search],
|
||||
llm=llm_config,
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Create summary agent
|
||||
|
||||
@@ -52,7 +52,7 @@ agents = AutoAgents(
|
||||
tools=[evaluate, get_stock_price],
|
||||
process="sequential",
|
||||
llm="gemini/gemini-2.5-flash-lite-preview-06-17",
|
||||
self_reflect=False,
|
||||
reflection=False,
|
||||
verbose=False,
|
||||
max_agents=3 # Maximum number of agents to create
|
||||
)
|
||||
|
||||
@@ -5,8 +5,9 @@ def get_weather(city: str) -> str:
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-5-nano",
|
||||
tools=[get_weather]
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_weather],
|
||||
output="status"
|
||||
)
|
||||
|
||||
agent.start("What is the weather in Tokyo?")
|
||||
@@ -1,14 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
print("Creating agent...")
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant"
|
||||
)
|
||||
|
||||
print("Starting agent...")
|
||||
result = agent.start("Why is the sky blue?")
|
||||
|
||||
print("Agent completed successfully!")
|
||||
print(f"Result: {result}")
|
||||
agent = Agent(instructions="You are a helpful assistant")
|
||||
result = agent.start("Why is the sky blue?")
|
||||
@@ -1,6 +1,6 @@
|
||||
# PraisonAI Agents - Real Execution Benchmark
|
||||
|
||||
**Generated:** 2025-12-18 14:43:52
|
||||
**Generated:** 2026-01-13 06:07:28
|
||||
**Model:** gpt-4o-mini
|
||||
**Iterations:** 3
|
||||
**Prompt:** "What is 2+2? Reply with just the number."
|
||||
@@ -9,18 +9,18 @@
|
||||
|
||||
| Framework | Method | Avg Time | Relative |
|
||||
|-----------|--------|----------|----------|
|
||||
| **PraisonAI** | `agent.start()` | **0.45s** | **1.00x (fastest)** |
|
||||
| PraisonAI (LiteLLM) | `agent.start()` | 0.55s | 1.22x |
|
||||
| CrewAI | `crew.kickoff()` | 0.58s | 1.28x |
|
||||
| Agno | `agent.run()` | 0.92s | 2.05x |
|
||||
| **PraisonAI (LiteLLM)** | `agent.start()` | **0.55s** | **1.00x (fastest)** |
|
||||
| PraisonAI | `agent.start()` | 0.64s | 1.15x |
|
||||
| CrewAI | `crew.kickoff()` | 0.95s | 1.71x |
|
||||
| Agno | `agent.run()` | 1.05s | 1.90x |
|
||||
|
||||
## Package Versions
|
||||
|
||||
| Package | Version |
|
||||
|---------|--------|
|
||||
| PraisonAI | 0.1.5 |
|
||||
| Agno | 2.3.14 |
|
||||
| CrewAI | 1.6.1 |
|
||||
| PraisonAI | 0.11.7 |
|
||||
| Agno | 2.3.25 |
|
||||
| CrewAI | 1.8.0 |
|
||||
|
||||
## How to Reproduce
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PraisonAI Agents - Real-World Execution Benchmark
|
||||
|
||||
**Generated:** 2025-12-18 14:33:49
|
||||
**Generated:** 2026-01-13 14:42:59
|
||||
**Model:** gpt-4o-mini
|
||||
**Iterations:** 3 per test
|
||||
|
||||
@@ -8,60 +8,60 @@
|
||||
|
||||
| Test | PraisonAI (avg) | Agno (avg) | Difference |
|
||||
|------|-----------------|------------|------------|
|
||||
| Single Agent | 2.04s | 3.18s | PraisonAI 1.56x faster |
|
||||
| 2 Agents | 2.49s | 20.49s | PraisonAI 8.21x faster |
|
||||
| 3 Agents | 6.52s | 13.97s | PraisonAI 2.14x faster |
|
||||
| Single Agent | 2.46s | 2.92s | PraisonAI 1.19x faster |
|
||||
| 2 Agents | 3.21s | 19.39s | PraisonAI 6.05x faster |
|
||||
| 3 Agents | 8.26s | 15.01s | PraisonAI 1.82x faster |
|
||||
|
||||
## Detailed Results
|
||||
|
||||
### praisonai_single
|
||||
- Average: 2.04s
|
||||
- Min: 1.95s
|
||||
- Max: 2.19s
|
||||
- Std Dev: 0.13s
|
||||
- Times: ['2.19s', '1.95s', '2.00s']
|
||||
- Average: 2.46s
|
||||
- Min: 1.93s
|
||||
- Max: 2.90s
|
||||
- Std Dev: 0.49s
|
||||
- Times: ['1.93s', '2.55s', '2.90s']
|
||||
|
||||
### agno_single
|
||||
- Average: 3.18s
|
||||
- Min: 3.00s
|
||||
- Max: 3.51s
|
||||
- Std Dev: 0.28s
|
||||
- Times: ['3.51s', '3.00s', '3.04s']
|
||||
- Average: 2.92s
|
||||
- Min: 2.85s
|
||||
- Max: 2.97s
|
||||
- Std Dev: 0.06s
|
||||
- Times: ['2.97s', '2.85s', '2.94s']
|
||||
|
||||
### praisonai_two_agents
|
||||
- Average: 2.49s
|
||||
- Min: 2.22s
|
||||
- Max: 2.65s
|
||||
- Std Dev: 0.24s
|
||||
- Times: ['2.65s', '2.22s', '2.62s']
|
||||
- Average: 3.21s
|
||||
- Min: 2.80s
|
||||
- Max: 3.67s
|
||||
- Std Dev: 0.44s
|
||||
- Times: ['3.67s', '2.80s', '3.15s']
|
||||
|
||||
### agno_two_agents
|
||||
- Average: 20.49s
|
||||
- Min: 9.96s
|
||||
- Max: 35.05s
|
||||
- Std Dev: 13.02s
|
||||
- Times: ['16.46s', '9.96s', '35.05s']
|
||||
- Average: 19.39s
|
||||
- Min: 10.62s
|
||||
- Max: 26.06s
|
||||
- Std Dev: 7.93s
|
||||
- Times: ['26.06s', '10.62s', '21.50s']
|
||||
|
||||
### praisonai_three_agents
|
||||
- Average: 6.52s
|
||||
- Min: 5.62s
|
||||
- Max: 7.39s
|
||||
- Std Dev: 0.88s
|
||||
- Times: ['5.62s', '6.54s', '7.39s']
|
||||
- Average: 8.26s
|
||||
- Min: 6.44s
|
||||
- Max: 11.88s
|
||||
- Std Dev: 3.13s
|
||||
- Times: ['6.47s', '6.44s', '11.88s']
|
||||
|
||||
### agno_three_agents
|
||||
- Average: 13.97s
|
||||
- Min: 11.69s
|
||||
- Max: 15.32s
|
||||
- Std Dev: 1.99s
|
||||
- Times: ['14.91s', '11.69s', '15.32s']
|
||||
- Average: 15.01s
|
||||
- Min: 14.46s
|
||||
- Max: 15.90s
|
||||
- Std Dev: 0.78s
|
||||
- Times: ['15.90s', '14.46s', '14.68s']
|
||||
|
||||
## Package Versions
|
||||
|
||||
| Package | Version |
|
||||
|---------|--------|
|
||||
| PraisonAI | 0.1.5 |
|
||||
| Agno | 2.3.14 |
|
||||
| PraisonAI | 0.11.22 |
|
||||
| Agno | 2.3.25 |
|
||||
|
||||
## How to Reproduce
|
||||
|
||||
|
||||
@@ -52,12 +52,11 @@ def benchmark_praisonai(model, iterations, prompt):
|
||||
agent = Agent(
|
||||
name="Calculator",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
llm=model,
|
||||
verbose=False
|
||||
llm=model
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
response = agent.start(prompt)
|
||||
response = agent.start(prompt, output="silent")
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
times.append(elapsed)
|
||||
@@ -80,12 +79,11 @@ def benchmark_praisonai_litellm(model, iterations, prompt):
|
||||
agent = Agent(
|
||||
name="Calculator",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
llm=f"openai/{model}",
|
||||
verbose=False
|
||||
llm=f"openai/{model}"
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
response = agent.start(prompt)
|
||||
response = agent.start(prompt, output="silent")
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
times.append(elapsed)
|
||||
|
||||
@@ -103,12 +103,11 @@ def benchmark_praisonai_single(iterations: int = 3) -> ExecutionResult:
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be very brief.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_weather],
|
||||
verbose=False
|
||||
tools=[get_weather]
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
response = agent.start("What's the weather in NYC? Reply in one sentence.")
|
||||
response = agent.start("What's the weather in NYC? Reply in one sentence.", output="silent")
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
result.times.append(elapsed)
|
||||
@@ -132,8 +131,7 @@ def benchmark_praisonai_two_agents(iterations: int = 3) -> ExecutionResult:
|
||||
goal="Gather weather information",
|
||||
instructions="Get weather data and pass to reporter. Be brief.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_weather],
|
||||
verbose=False
|
||||
tools=[get_weather]
|
||||
)
|
||||
|
||||
reporter = Agent(
|
||||
@@ -141,18 +139,16 @@ def benchmark_praisonai_two_agents(iterations: int = 3) -> ExecutionResult:
|
||||
role="News Reporter",
|
||||
goal="Create weather report",
|
||||
instructions="Create a one-sentence weather summary.",
|
||||
llm="gpt-4o-mini",
|
||||
verbose=False
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
agents = Agents(
|
||||
agents=[researcher, reporter],
|
||||
process="sequential",
|
||||
verbose=False
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
response = agents.start("Get NYC weather and create a brief report.")
|
||||
response = agents.start("Get NYC weather and create a brief report.", output="silent")
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
result.times.append(elapsed)
|
||||
@@ -176,8 +172,7 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
|
||||
goal="Get weather data",
|
||||
instructions="Fetch weather information. Be brief.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_weather],
|
||||
verbose=False
|
||||
tools=[get_weather]
|
||||
)
|
||||
|
||||
time_agent = Agent(
|
||||
@@ -186,8 +181,7 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
|
||||
goal="Get time information",
|
||||
instructions="Fetch timezone information. Be brief.",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_time],
|
||||
verbose=False
|
||||
tools=[get_time]
|
||||
)
|
||||
|
||||
summarizer = Agent(
|
||||
@@ -195,18 +189,16 @@ def benchmark_praisonai_three_agents(iterations: int = 3) -> ExecutionResult:
|
||||
role="Summary Writer",
|
||||
goal="Combine information",
|
||||
instructions="Create a one-sentence summary combining weather and time.",
|
||||
llm="gpt-4o-mini",
|
||||
verbose=False
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
agents = Agents(
|
||||
agents=[weather_agent, time_agent, summarizer],
|
||||
process="sequential",
|
||||
verbose=False
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
start = time.perf_counter()
|
||||
response = agents.start("Get NYC weather and EST time, then summarize.")
|
||||
response = agents.start("Get NYC weather and EST time, then summarize.", output="silent")
|
||||
elapsed = time.perf_counter() - start
|
||||
|
||||
result.times.append(elapsed)
|
||||
|
||||
Whitespace-only changes.
@@ -1,5 +1,5 @@
|
||||
from praisonaiagents import ContextAgent
|
||||
|
||||
agent = ContextAgent(llm="gpt-5-nano", auto_analyze=False)
|
||||
agent = ContextAgent(llm="gpt-4o-mini", auto_analyze=False)
|
||||
|
||||
agent.start("https://github.com/MervinPraison/PraisonAI/ Need to add Authentication")
|
||||
@@ -14,7 +14,7 @@ def test_agent_direct():
|
||||
agent = Agent(
|
||||
instructions="""You are a helpful assistant that can break down complex problems.
|
||||
Use the available tools when relevant to perform step-by-step analysis.""",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP("npx -y @modelcontextprotocol/server-sequential-thinking")
|
||||
)
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ llm_config = {
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. Be concise and clear.",
|
||||
llm=llm_config,
|
||||
verbose=True,
|
||||
self_reflect=True,
|
||||
output="verbose",
|
||||
reflection=True,
|
||||
max_reflect=2,
|
||||
min_reflect=1
|
||||
)
|
||||
|
||||
@@ -7,8 +7,7 @@ def validate_content(data):
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a writer",
|
||||
guardrail=validate_content,
|
||||
max_guardrail_retries=1
|
||||
guardrails=validate_content
|
||||
)
|
||||
|
||||
agent.start("Write a welcome message with 5 words")
|
||||
@@ -12,7 +12,7 @@ agent = Agent(
|
||||
|
||||
task = Task(
|
||||
description="Write a welcome message",
|
||||
guardrail=validate_content,
|
||||
guardrails=validate_content,
|
||||
agent=agent
|
||||
)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def main():
|
||||
description="Write a professional email to a client about project completion",
|
||||
expected_output="A well-formatted professional email",
|
||||
agent=agent,
|
||||
guardrail=email_validator, # Function-based guardrail
|
||||
guardrails=email_validator, # Function-based guardrail
|
||||
max_retries=2
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ Project Team""",
|
||||
description="Write a marketing email for a new product launch",
|
||||
expected_output="Engaging marketing content",
|
||||
agent=agent,
|
||||
guardrail="Ensure the content is professional, engaging, includes a clear call-to-action, and is free of errors",
|
||||
guardrails="Ensure the content is professional, engaging, includes a clear call-to-action, and is free of errors",
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ agent = Agent(
|
||||
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
|
||||
reflection=False
|
||||
)
|
||||
|
||||
# Create tasks to demonstrate both tools
|
||||
|
||||
@@ -32,9 +32,9 @@ 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
|
||||
output="verbose", # Enable detailed output
|
||||
markdown=True, # Format responses in markdown
|
||||
self_reflect=True, # Enable self-reflection
|
||||
reflection=True, # Enable self-reflection
|
||||
max_reflect=3, # Maximum reflection iterations
|
||||
min_reflect=1 # Minimum reflection iterations
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from praisonaiagents import Agent, MCP
|
||||
agent = Agent(
|
||||
instructions="""You are a helpful assistant that can check stock prices and perform other tasks.
|
||||
Use the available tools when relevant to answer user questions.""",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools = MCP("/Users/praison/miniconda3/envs/mcp/bin/python /Users/praison/stockprice/app.py")
|
||||
)
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def main():
|
||||
tools=[mongodb_tools],
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# 2. Knowledge Curator Agent - manages knowledge base
|
||||
@@ -85,7 +85,7 @@ def main():
|
||||
knowledge_config=mongodb_knowledge_config,
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# 3. Business Analyst Agent - analyzes data and provides insights
|
||||
@@ -99,7 +99,7 @@ def main():
|
||||
tools=[mongodb_tools],
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# 4. Customer Service Agent - handles customer interactions
|
||||
@@ -114,7 +114,7 @@ def main():
|
||||
tools=[mongodb_tools],
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create comprehensive business tasks
|
||||
|
||||
@@ -55,11 +55,9 @@ def main():
|
||||
retrieve information from a comprehensive MongoDB knowledge base. You excel
|
||||
at finding relevant information, synthesizing knowledge from multiple sources,
|
||||
and providing accurate, context-aware responses.""",
|
||||
knowledge_config=mongodb_knowledge_config,
|
||||
knowledge=[os.path.join(os.path.dirname(__file__), "llms.md")],
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create a research assistant agent
|
||||
@@ -72,8 +70,7 @@ def main():
|
||||
knowledge base. You ensure information is accurate, well-structured,
|
||||
and properly categorized.""",
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create tasks for knowledge management
|
||||
@@ -128,7 +125,7 @@ def main():
|
||||
agents=[research_agent, knowledge_agent],
|
||||
tasks=knowledge_tasks,
|
||||
memory=True,
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Execute the knowledge management pipeline
|
||||
|
||||
@@ -59,7 +59,7 @@ def main():
|
||||
knowledge=[os.path.join(os.path.dirname(__file__), "llms.md")],
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create a research assistant agent
|
||||
@@ -73,7 +73,7 @@ def main():
|
||||
and properly categorized.""",
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create tasks for knowledge management
|
||||
|
||||
@@ -3,7 +3,7 @@ from praisonaiagents.memory import Memory
|
||||
from praisonaiagents.agent import ContextAgent
|
||||
import pymongo
|
||||
|
||||
context_agent = ContextAgent(llm="gpt-5-nano", auto_analyze=False)
|
||||
context_agent = ContextAgent(llm="gpt-4o-mini", auto_analyze=False)
|
||||
|
||||
context_output = context_agent.start("https://github.com/MervinPraison/PraisonAI/ Need to add Authentication")
|
||||
|
||||
@@ -25,7 +25,7 @@ implementation_agent = Agent(
|
||||
goal="Implement authentication features based on project requirements",
|
||||
backstory="Expert software implementer specializing in authentication systems, security features, and seamless integration with existing codebases",
|
||||
memory=True,
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
)
|
||||
|
||||
implementation_task = Task(
|
||||
|
||||
@@ -50,7 +50,7 @@ def main():
|
||||
retrieve relevant information from your persistent memory store.""",
|
||||
memory=True,
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create research tasks
|
||||
|
||||
@@ -38,7 +38,7 @@ def main():
|
||||
for data management and retrieval.""",
|
||||
tools=[mongodb_tools],
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create a Data Analysis Agent
|
||||
@@ -51,7 +51,7 @@ def main():
|
||||
identify patterns and trends in the stored information.""",
|
||||
tools=[mongodb_tools],
|
||||
verbose=True,
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create tasks for MongoDB operations
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from praisonaiagents import Agent, Agents
|
||||
|
||||
research_agent = Agent(instructions="Research about AI")
|
||||
summarise_agent = Agent(instructions="Summarise research agent's findings")
|
||||
|
||||
agents = Agents(agents=[research_agent, summarise_agent])
|
||||
response = agents.start()
|
||||
@@ -2,6 +2,6 @@ from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
agent.start("Why sky is Blue?")
|
||||
@@ -2,7 +2,7 @@ from praisonaiagents import Agent
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
agent.start("Why sky is Blue?")
|
||||
agent.start("What was my previous question?")
|
||||
@@ -3,7 +3,7 @@ import asyncio
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
llm="gpt-5-nano"
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
agent.run("Why sea is Blue?")
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ def multiply(a: int, b: int) -> int:
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user.",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_stock_price, multiply]
|
||||
)
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ task2 = Task(description="Write a short article about meditation benefits", agen
|
||||
agents = Agents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2],
|
||||
planning=True,
|
||||
auto_approve_plan=True # Auto-approve the generated plan
|
||||
planning=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -22,7 +22,7 @@ def multiply(a: int, b: int) -> int:
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user.",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=[get_stock_price, multiply]
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,29 @@ This module uses lazy loading to minimize import time and memory usage.
|
||||
Heavy dependencies like litellm are only loaded when actually needed.
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# NAMESPACE PACKAGE GUARD
|
||||
# =============================================================================
|
||||
# Detect if we're loaded as a namespace package (which indicates stale artifacts
|
||||
# in site-packages). This happens when there's a praisonaiagents/ directory in
|
||||
# site-packages without an __init__.py file.
|
||||
#
|
||||
# Root cause: Partial uninstall or version mismatch leaves directories that
|
||||
# Python's PathFinder treats as namespace packages, shadowing the real package.
|
||||
# =============================================================================
|
||||
if __file__ is None:
|
||||
import warnings as _ns_warnings
|
||||
_ns_warnings.warn(
|
||||
"praisonaiagents is loaded as a namespace package, which indicates "
|
||||
"stale artifacts in site-packages. This will cause import errors. "
|
||||
"Fix: Remove the stale directory with:\n"
|
||||
" rm -rf $(python -c \"import site; print(site.getsitepackages()[0])\")/praisonaiagents/\n"
|
||||
"Then reinstall: pip install praisonaiagents",
|
||||
ImportWarning,
|
||||
stacklevel=1
|
||||
)
|
||||
del _ns_warnings
|
||||
|
||||
# Apply warning patch BEFORE any imports to intercept warnings at the source
|
||||
from . import _warning_patch # noqa: F401
|
||||
|
||||
@@ -25,6 +48,14 @@ from .tools.registry import get_registry, register_tool, get_tool, ToolRegistry
|
||||
from .db import db
|
||||
from .obs import obs
|
||||
|
||||
# Sub-packages for organized imports (pa.config, pa.tools, etc.)
|
||||
# These enable: import praisonaiagents as pa; pa.config.MemoryConfig
|
||||
from . import config
|
||||
from . import tools
|
||||
from . import memory
|
||||
from . import workflows
|
||||
# Note: knowledge and mcp are lazy-loaded via __getattr__ due to heavy deps
|
||||
|
||||
# Workflows - lightweight module
|
||||
from .workflows import (
|
||||
Workflow, WorkflowStep, WorkflowContext, StepResult,
|
||||
@@ -68,7 +99,18 @@ from .main import (
|
||||
)
|
||||
|
||||
# Module-level caches for lazy-loaded classes
|
||||
_lazy_cache = {}
|
||||
# Using threading.local() ensures thread-safe access to cached values
|
||||
import threading
|
||||
_lazy_cache_local = threading.local()
|
||||
|
||||
def _get_lazy_cache():
|
||||
"""Get thread-local lazy cache dict. Thread-safe for concurrent access."""
|
||||
if not hasattr(_lazy_cache_local, 'cache'):
|
||||
_lazy_cache_local.cache = {}
|
||||
return _lazy_cache_local.cache
|
||||
|
||||
# Backward-compatible alias (module-level access still works, now thread-safe)
|
||||
_lazy_cache = {} # Kept for compatibility, but prefer _get_lazy_cache()
|
||||
|
||||
# Flags for optional modules
|
||||
_mcp_available = None
|
||||
@@ -442,6 +484,51 @@ def __getattr__(name):
|
||||
_lazy_cache[name] = ContextManager
|
||||
return ContextManager
|
||||
|
||||
# =========================================================================
|
||||
# Sub-package fallback: delegate to sub-packages for additional symbols
|
||||
# This enables: from praisonaiagents import duckduckgo, FileMemory, etc.
|
||||
# =========================================================================
|
||||
|
||||
# Try tools sub-package (110+ tool functions)
|
||||
try:
|
||||
from . import tools as _tools_module
|
||||
if hasattr(_tools_module, name):
|
||||
result = getattr(_tools_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try memory sub-package (FileMemory, RulesManager, etc.)
|
||||
try:
|
||||
from . import memory as _memory_module
|
||||
if hasattr(_memory_module, name):
|
||||
result = getattr(_memory_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try config sub-package (already handled above, but catch any stragglers)
|
||||
try:
|
||||
from . import config as _config_module
|
||||
if hasattr(_config_module, name):
|
||||
result = getattr(_config_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Try workflows sub-package (Pipeline, Route, etc. - already imported, catch aliases)
|
||||
try:
|
||||
from . import workflows as _workflows_module
|
||||
if hasattr(_workflows_module, name):
|
||||
result = getattr(_workflows_module, name)
|
||||
_lazy_cache[name] = result
|
||||
return result
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
@@ -532,176 +619,61 @@ def warmup(include_litellm: bool = False, include_openai: bool = True) -> dict:
|
||||
return timings
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PUBLIC API: __all__ (controls IDE autocomplete and `from X import *`)
|
||||
# ============================================================================
|
||||
# DESIGN: Keep __all__ minimal for clean IDE experience.
|
||||
# All 186+ symbols are still accessible via __getattr__ for backwards compat.
|
||||
# Organized imports available via sub-packages: config, tools, memory, workflows
|
||||
# ============================================================================
|
||||
|
||||
__all__ = [
|
||||
# Core classes (lazy loaded)
|
||||
# Core classes - the essentials
|
||||
'Agent',
|
||||
'ImageAgent',
|
||||
'ContextAgent',
|
||||
'create_context_agent',
|
||||
'PraisonAIAgents',
|
||||
'Agents',
|
||||
'Tools',
|
||||
'Task',
|
||||
# Plugin system
|
||||
'BaseTool',
|
||||
'ToolResult',
|
||||
'ToolValidationError',
|
||||
'validate_tool',
|
||||
|
||||
# Tool essentials
|
||||
'tool',
|
||||
'FunctionTool',
|
||||
'ToolRegistry',
|
||||
'get_registry',
|
||||
'register_tool',
|
||||
'get_tool',
|
||||
'TaskOutput',
|
||||
'ReflectionOutput',
|
||||
'AutoAgents',
|
||||
'AutoRagAgent',
|
||||
'AutoRagConfig',
|
||||
'RagRetrievalPolicy',
|
||||
'Session',
|
||||
'Memory',
|
||||
'db',
|
||||
'obs',
|
||||
'display_interaction',
|
||||
'display_self_reflection',
|
||||
'display_instruction',
|
||||
'display_tool_call',
|
||||
'display_error',
|
||||
'display_generating',
|
||||
'clean_triple_backticks',
|
||||
'error_logs',
|
||||
'register_display_callback',
|
||||
'sync_display_callbacks',
|
||||
'async_display_callbacks',
|
||||
'Knowledge',
|
||||
'Chunking',
|
||||
'GuardrailResult',
|
||||
'LLMGuardrail',
|
||||
'Handoff',
|
||||
'handoff',
|
||||
'handoff_filters',
|
||||
'RECOMMENDED_PROMPT_PREFIX',
|
||||
'prompt_with_handoff_instructions',
|
||||
'HandoffConfig',
|
||||
'HandoffResult',
|
||||
'HandoffInputData',
|
||||
'ContextPolicy',
|
||||
'HandoffError',
|
||||
'HandoffCycleError',
|
||||
'HandoffDepthError',
|
||||
'HandoffTimeoutError',
|
||||
'get_telemetry',
|
||||
'enable_telemetry',
|
||||
'disable_telemetry',
|
||||
'enable_performance_mode',
|
||||
'disable_performance_mode',
|
||||
'cleanup_telemetry_resources',
|
||||
'MinimalTelemetry',
|
||||
'TelemetryCollector',
|
||||
'DeepResearchAgent',
|
||||
'DeepResearchResponse',
|
||||
'Citation',
|
||||
'ReasoningStep',
|
||||
'WebSearchCall',
|
||||
'CodeExecutionStep',
|
||||
'MCPCall',
|
||||
'FileSearchCall',
|
||||
'Provider',
|
||||
'QueryRewriterAgent',
|
||||
'RewriteStrategy',
|
||||
'RewriteResult',
|
||||
'PromptExpanderAgent',
|
||||
'ExpandStrategy',
|
||||
'ExpandResult',
|
||||
# Workflows
|
||||
'Workflow',
|
||||
'WorkflowStep',
|
||||
'WorkflowContext',
|
||||
'StepResult',
|
||||
'Route',
|
||||
'Parallel',
|
||||
'Loop',
|
||||
'Repeat',
|
||||
'route',
|
||||
'parallel',
|
||||
'loop',
|
||||
'repeat',
|
||||
'Pipeline',
|
||||
# MCP
|
||||
'MCP',
|
||||
# Flow display
|
||||
'FlowDisplay',
|
||||
'track_workflow',
|
||||
# FastContext
|
||||
'FastContext',
|
||||
'FastContextResult',
|
||||
'FileMatch',
|
||||
'LineRange',
|
||||
# Agent Skills
|
||||
'SkillManager',
|
||||
'SkillProperties',
|
||||
'SkillMetadata',
|
||||
'SkillLoader',
|
||||
# Planning
|
||||
'Plan',
|
||||
'PlanStep',
|
||||
'TodoList',
|
||||
'TodoItem',
|
||||
'PlanStorage',
|
||||
'PlanningAgent',
|
||||
'ApprovalCallback',
|
||||
'READ_ONLY_TOOLS',
|
||||
'RESTRICTED_TOOLS',
|
||||
# UI
|
||||
'AGUI',
|
||||
'A2A',
|
||||
# RAG
|
||||
'RAG',
|
||||
'RAGConfig',
|
||||
'RAGResult',
|
||||
'RAGCitation',
|
||||
# Feature Configs (agent-centric API)
|
||||
'MemoryConfig',
|
||||
'KnowledgeConfig',
|
||||
'PlanningConfig',
|
||||
'ReflectionConfig',
|
||||
'GuardrailConfig',
|
||||
'WebConfig',
|
||||
'OutputConfig',
|
||||
'ExecutionConfig',
|
||||
'TemplateConfig',
|
||||
'CachingConfig',
|
||||
'HooksConfig',
|
||||
'SkillsConfig',
|
||||
'AutonomyConfig',
|
||||
'MemoryBackend',
|
||||
'ChunkingStrategy',
|
||||
'GuardrailAction',
|
||||
'WebSearchProvider',
|
||||
'OutputPreset',
|
||||
'ExecutionPreset',
|
||||
'AutonomyLevel',
|
||||
# Multi-Agent Feature Configs
|
||||
'MultiAgentHooksConfig',
|
||||
'MultiAgentOutputConfig',
|
||||
'MultiAgentExecutionConfig',
|
||||
'MultiAgentPlanningConfig',
|
||||
'MultiAgentMemoryConfig',
|
||||
# Context Management
|
||||
'ManagerConfig',
|
||||
'ContextManager',
|
||||
# Parameter Resolution (agent-centric API)
|
||||
'resolve',
|
||||
'ArrayMode',
|
||||
'resolve_guardrails',
|
||||
'resolve_guardrail_policies',
|
||||
# Presets (agent-centric API)
|
||||
'GUARDRAIL_PRESETS',
|
||||
'KNOWLEDGE_PRESETS',
|
||||
# Parse Utilities
|
||||
'is_policy_string',
|
||||
'parse_policy_string',
|
||||
# Performance utilities
|
||||
'warmup',
|
||||
'Tools',
|
||||
|
||||
# Sub-packages for organized imports
|
||||
# Usage: import praisonaiagents as pa; pa.config.MemoryConfig
|
||||
'config',
|
||||
'tools',
|
||||
'memory',
|
||||
'workflows',
|
||||
]
|
||||
|
||||
|
||||
def __dir__():
|
||||
"""
|
||||
Return clean list for dir() - matches __all__ plus standard attributes.
|
||||
|
||||
This keeps IDE autocomplete clean while preserving full backwards
|
||||
compatibility via __getattr__ for all 186+ legacy exports.
|
||||
"""
|
||||
return list(__all__) + [
|
||||
# Standard module attributes
|
||||
'__name__', '__doc__', '__file__', '__path__', '__package__',
|
||||
'__loader__', '__spec__', '__cached__', '__builtins__',
|
||||
]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# BACKWARDS COMPATIBILITY: Legacy __all__ items (for reference)
|
||||
# ============================================================================
|
||||
# All items below are still importable via __getattr__ but NOT in autocomplete:
|
||||
# - ImageAgent, ContextAgent, create_context_agent, PraisonAIAgents
|
||||
# - BaseTool, ToolResult, ToolValidationError, validate_tool, FunctionTool
|
||||
# - ToolRegistry, get_registry, register_tool, get_tool
|
||||
# - TaskOutput, ReflectionOutput, AutoAgents, AutoRagAgent, AutoRagConfig
|
||||
# - Session, Memory, db, obs, Knowledge, Chunking
|
||||
# - GuardrailResult, LLMGuardrail, Handoff, handoff, handoff_filters
|
||||
# - MemoryConfig, KnowledgeConfig, PlanningConfig, OutputConfig, etc.
|
||||
# - Workflow, WorkflowStep, Route, Parallel, Loop, Repeat, Pipeline, etc.
|
||||
# - MCP, FlowDisplay, track_workflow, FastContext, etc.
|
||||
# - Plan, PlanStep, TodoList, PlanningAgent, ApprovalCallback, etc.
|
||||
# - RAG, RAGConfig, RAGResult, AGUI, A2A, etc.
|
||||
# - All telemetry, display, and utility functions
|
||||
# ============================================================================
|
||||
@@ -30,6 +30,13 @@ from .prompt_expander_agent import (
|
||||
ExpandStrategy,
|
||||
ExpandResult
|
||||
)
|
||||
from .protocols import (
|
||||
AgentProtocol,
|
||||
RunnableAgentProtocol,
|
||||
ToolAwareAgentProtocol,
|
||||
MemoryAwareAgentProtocol,
|
||||
FullAgentProtocol,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'Agent',
|
||||
@@ -64,5 +71,11 @@ __all__ = [
|
||||
'RewriteResult',
|
||||
'PromptExpanderAgent',
|
||||
'ExpandStrategy',
|
||||
'ExpandResult'
|
||||
'ExpandResult',
|
||||
# Protocols
|
||||
'AgentProtocol',
|
||||
'RunnableAgentProtocol',
|
||||
'ToolAwareAgentProtocol',
|
||||
'MemoryAwareAgentProtocol',
|
||||
'FullAgentProtocol',
|
||||
]
|
||||
@@ -44,7 +44,7 @@ _shared_apps = {} # Dict of port -> FastAPI app
|
||||
if TYPE_CHECKING:
|
||||
from ..task.task import Task
|
||||
from ..main import TaskOutput
|
||||
from ..handoff import Handoff
|
||||
from .handoff import Handoff, HandoffConfig, HandoffResult
|
||||
from ..rag.models import RAGResult, ContextPack
|
||||
|
||||
class Agent:
|
||||
@@ -291,96 +291,87 @@ class Agent:
|
||||
"""Initialize an Agent instance.
|
||||
|
||||
Args:
|
||||
name (Optional[str], optional): Name of the agent used for identification and logging.
|
||||
If None, defaults to "Agent". Defaults to None.
|
||||
role (Optional[str], optional): Role or job title that defines the agent's expertise
|
||||
and behavior patterns. Examples: "Data Analyst", "Content Writer". Defaults to None.
|
||||
goal (Optional[str], optional): Primary objective or goal the agent aims to achieve.
|
||||
Defines the agent's purpose and success criteria. Defaults to None.
|
||||
backstory (Optional[str], optional): Background story or context that shapes the agent's
|
||||
personality and decision-making approach. Defaults to None.
|
||||
instructions (Optional[str], optional): Direct instructions that override role, goal,
|
||||
and backstory when provided. Used for simple, task-specific agents. Defaults to None.
|
||||
llm (Optional[Union[str, Any]], optional): Language model configuration. Can be a model
|
||||
name string (e.g., "gpt-5-nano", "anthropic/claude-3-sonnet") or a configured LLM object.
|
||||
Defaults to environment variable OPENAI_MODEL_NAME or "gpt-5-nano".
|
||||
tools (Optional[List[Any]], optional): List of tools, functions, or capabilities
|
||||
available to the agent for task execution. Can include callables, tool objects,
|
||||
or MCP instances. Defaults to None.
|
||||
function_calling_llm (Optional[Any], optional): Dedicated language model for function
|
||||
calling operations. If None, uses the main llm parameter. Defaults to None.
|
||||
max_iter (int, optional): Maximum number of iterations the agent can perform during
|
||||
task execution to prevent infinite loops. Defaults to 20.
|
||||
max_rpm (Optional[int], optional): Maximum requests per minute to rate limit API calls
|
||||
and prevent quota exhaustion. If None, no rate limiting is applied. Defaults to None.
|
||||
max_execution_time (Optional[int], optional): Maximum execution time in seconds for
|
||||
agent operations before timeout. If None, no time limit is enforced. Defaults to None.
|
||||
memory (Optional[Any], optional): Memory system for storing and retrieving information
|
||||
across conversations. Requires memory dependencies to be installed. Defaults to None.
|
||||
verbose (bool, optional): Enable detailed logging and status updates during agent
|
||||
execution for debugging and monitoring. Defaults to True.
|
||||
allow_delegation (bool, optional): Allow the agent to delegate tasks to other agents
|
||||
or sub-processes when appropriate. Defaults to False.
|
||||
step_callback (Optional[Any], optional): Callback function called after each step
|
||||
of agent execution for custom monitoring or intervention. Defaults to None.
|
||||
cache (bool, optional): Enable caching of responses and computations to improve
|
||||
performance and reduce API costs. Defaults to True.
|
||||
system_template (Optional[str], optional): Custom template for system prompts that
|
||||
overrides the default system prompt generation. Defaults to None.
|
||||
prompt_template (Optional[str], optional): Template for formatting user prompts
|
||||
before sending to the language model. Defaults to None.
|
||||
response_template (Optional[str], optional): Template for formatting agent responses
|
||||
before returning to the user. Defaults to None.
|
||||
allow_code_execution (Optional[bool], optional): Enable the agent to execute code
|
||||
snippets during task completion. Use with caution for security. Defaults to False.
|
||||
max_retry_limit (int, optional): Maximum number of retry attempts for failed operations
|
||||
before giving up. Helps handle transient errors. Defaults to 2.
|
||||
code_execution_mode (Literal["safe", "unsafe"], optional): Safety mode for code execution.
|
||||
"safe" restricts dangerous operations, "unsafe" allows full code execution. Defaults to "safe".
|
||||
embedder_config (Optional[Dict[str, Any]], optional): Configuration dictionary for
|
||||
text embedding models used in knowledge retrieval and similarity search. Defaults to None.
|
||||
knowledge (Optional[List[str]], optional): List of knowledge sources (file paths, URLs,
|
||||
or text content) to be processed and made available to the agent. Defaults to None.
|
||||
knowledge_config (Optional[Dict[str, Any]], optional): Configuration for knowledge
|
||||
processing and retrieval system including chunking and indexing parameters. Defaults to None.
|
||||
use_system_prompt (Optional[bool], optional): Whether to include system prompts in
|
||||
conversations to establish agent behavior and context. Defaults to True.
|
||||
markdown (bool, optional): Enable markdown formatting in agent responses for better
|
||||
readability and structure. Defaults to True.
|
||||
stream (bool, optional): Enable streaming responses from the language model for real-time
|
||||
output when using Agent.start() method. Defaults to False for backward compatibility.
|
||||
metrics (bool, optional): Enable automatic token usage tracking and display summary
|
||||
when tasks complete. Simplifies token monitoring for cost optimization. Defaults to False.
|
||||
self_reflect (bool, optional): Enable self-reflection capabilities where the agent
|
||||
evaluates and improves its own responses. Defaults to False.
|
||||
max_reflect (int, optional): Maximum number of self-reflection iterations to prevent
|
||||
excessive reflection loops. Defaults to 3.
|
||||
min_reflect (int, optional): Minimum number of self-reflection iterations required
|
||||
before accepting a response as satisfactory. Defaults to 1.
|
||||
reflect_llm (Optional[str], optional): Dedicated language model for self-reflection
|
||||
operations. If None, uses the main llm parameter. Defaults to None.
|
||||
reflect_prompt (Optional[str], optional): Custom prompt template for self-reflection
|
||||
that guides the agent's self-evaluation process. Defaults to None.
|
||||
user_id (Optional[str], optional): Unique identifier for the user or session to
|
||||
enable personalized responses and memory isolation. Defaults to "praison".
|
||||
reasoning_steps (bool, optional): Enable step-by-step reasoning output to show the
|
||||
agent's thought process during problem solving. Defaults to False.
|
||||
guardrail (Optional[Union[Callable[['TaskOutput'], Tuple[bool, Any]], str]], optional):
|
||||
Safety mechanism to validate agent outputs. Can be a validation function or
|
||||
description string for LLM-based validation. Defaults to None.
|
||||
max_guardrail_retries (int, optional): Maximum number of retry attempts when guardrail
|
||||
validation fails before giving up. Defaults to 3.
|
||||
handoffs (Optional[List[Union['Agent', 'Handoff']]], optional): List of agents or
|
||||
handoff configurations that this agent can delegate tasks to. Enables agent-to-agent
|
||||
collaboration and task specialization. Defaults to None.
|
||||
base_url (Optional[str], optional): Base URL for custom LLM endpoints (e.g., Ollama).
|
||||
If provided, automatically creates a custom LLM instance. Defaults to None.
|
||||
api_key (Optional[str], optional): API key for LLM provider. If not provided,
|
||||
falls back to environment variables. Defaults to None.
|
||||
name: Agent name for identification and logging. Defaults to "Agent".
|
||||
role: Role/job title defining expertise (e.g., "Data Analyst").
|
||||
goal: Primary objective the agent aims to achieve.
|
||||
backstory: Background context shaping personality and decisions.
|
||||
instructions: Direct instructions (overrides role/goal/backstory). Recommended for simple agents.
|
||||
llm: Model name string ("gpt-4o", "anthropic/claude-3-sonnet") or LLM object.
|
||||
Defaults to OPENAI_MODEL_NAME env var or "gpt-4o-mini".
|
||||
model: Alias for llm parameter.
|
||||
function_calling_llm: Dedicated LLM for function calling. Deprecated: use llm=.
|
||||
llm_config: LLM configuration dict. Deprecated: use llm=.
|
||||
base_url: Custom LLM endpoint URL (e.g., for Ollama). Kept separate for auth.
|
||||
api_key: API key for LLM provider. Kept separate for auth.
|
||||
tools: List of tools, functions, callables, or MCP instances.
|
||||
allow_delegation: Allow task delegation to other agents. Defaults to False.
|
||||
allow_code_execution: Enable code execution during tasks. Defaults to False.
|
||||
code_execution_mode: "safe" (restricted) or "unsafe" (full access). Defaults to "safe".
|
||||
handoffs: List of Agent or Handoff objects for agent-to-agent collaboration.
|
||||
auto_save: Session name for automatic session saving.
|
||||
rate_limiter: Rate limiter instance for API call throttling.
|
||||
memory: Memory system configuration. Accepts:
|
||||
- bool: True enables defaults, False disables
|
||||
- MemoryConfig: Custom configuration
|
||||
- MemoryManager: Pre-configured instance
|
||||
knowledge: Knowledge sources. Accepts:
|
||||
- bool: True enables defaults
|
||||
- List[str]: File paths, URLs, or text content
|
||||
- KnowledgeConfig: Custom configuration
|
||||
planning: Planning mode. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- PlanningConfig: Custom configuration
|
||||
reflection: Self-reflection. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- ReflectionConfig: Custom configuration
|
||||
guardrails: Output validation. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- Callable: Validation function
|
||||
- GuardrailConfig: Custom configuration
|
||||
web: Web search/fetch. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- WebConfig: Custom configuration
|
||||
context: Context management. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- ManagerConfig: Custom configuration
|
||||
autonomy: Autonomy settings. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- Dict: Configuration dict
|
||||
- AutonomyConfig: Custom configuration
|
||||
verification_hooks: List of VerificationHook instances for output verification.
|
||||
output: Output configuration. Accepts:
|
||||
- str: Preset name ("silent", "actions", "verbose", "json", "stream")
|
||||
- OutputConfig: Custom configuration
|
||||
Controls: verbose, markdown, stream, metrics, reasoning_steps
|
||||
execution: Execution configuration. Accepts:
|
||||
- str: Preset name ("fast", "balanced", "thorough")
|
||||
- ExecutionConfig: Custom configuration
|
||||
Controls: max_iter, max_rpm, max_execution_time, max_retry_limit
|
||||
templates: Template configuration (TemplateConfig).
|
||||
Controls: system_template, prompt_template, response_template
|
||||
caching: Caching configuration. Accepts:
|
||||
- bool: True enables with defaults
|
||||
- CachingConfig: Custom configuration
|
||||
hooks: Event hooks. Accepts:
|
||||
- List: List of hook callables
|
||||
- HooksConfig: Custom configuration
|
||||
skills: Agent skills. Accepts:
|
||||
- List[str]: Skill directory paths
|
||||
- SkillsConfig: Custom configuration
|
||||
|
||||
Raises:
|
||||
ValueError: If all of name, role, goal, backstory, and instructions are None.
|
||||
ImportError: If memory or LLM features are requested but dependencies are not installed.
|
||||
|
||||
Note:
|
||||
Many legacy parameters have been consolidated into config objects:
|
||||
- verbose, markdown, stream, metrics, reasoning_steps → output=
|
||||
- max_iter, max_rpm, max_execution_time, max_retry_limit → execution=
|
||||
- self_reflect, max_reflect, min_reflect, reflect_llm → reflection=
|
||||
- guardrail, max_guardrail_retries → guardrails=
|
||||
- system_template, prompt_template, response_template → templates=
|
||||
- cache, prompt_caching → caching=
|
||||
- web_search, web_fetch → web=
|
||||
"""
|
||||
# Add check at start if memory is requested
|
||||
if memory is not None:
|
||||
@@ -447,14 +438,31 @@ class Agent:
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve OUTPUT param using unified resolver
|
||||
# Supports: None, str preset, list [preset, overrides], OutputConfig
|
||||
# DEFAULT: "silent" mode (zero overhead, fastest performance)
|
||||
# Can be overridden by PRAISONAI_OUTPUT env var
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
from ..config.presets import DEFAULT_OUTPUT_MODE
|
||||
|
||||
# Check for env var override
|
||||
env_output = os.environ.get('PRAISONAI_OUTPUT', '').lower()
|
||||
if env_output and env_output in OUTPUT_PRESETS and output is None:
|
||||
output = env_output
|
||||
|
||||
# Use default output mode if not specified
|
||||
# Track if user explicitly configured output (for respecting in start())
|
||||
if output is None:
|
||||
output = DEFAULT_OUTPUT_MODE
|
||||
_has_explicit_output = False
|
||||
else:
|
||||
_has_explicit_output = True
|
||||
|
||||
_output_config = resolve(
|
||||
value=output,
|
||||
param_name="output",
|
||||
config_class=OutputConfig,
|
||||
presets=OUTPUT_PRESETS,
|
||||
array_mode=ArrayMode.PRESET_OVERRIDE,
|
||||
default=OutputConfig(),
|
||||
default=OutputConfig(), # OutputConfig defaults to silent mode (zero overhead)
|
||||
)
|
||||
if _output_config:
|
||||
verbose = _output_config.verbose
|
||||
@@ -463,8 +471,45 @@ class Agent:
|
||||
metrics = _output_config.metrics
|
||||
reasoning_steps = _output_config.reasoning_steps
|
||||
output_style = getattr(_output_config, 'style', None)
|
||||
actions_trace = getattr(_output_config, 'actions_trace', False) # Default False (silent)
|
||||
json_output = getattr(_output_config, 'json_output', False)
|
||||
status_trace = getattr(_output_config, 'status_trace', False) # New: clean inline status
|
||||
simple_output = getattr(_output_config, 'simple_output', False) # status preset: no timestamps
|
||||
else:
|
||||
verbose, markdown, stream, metrics, reasoning_steps = True, True, False, False, False
|
||||
# Fallback defaults match silent mode (zero overhead)
|
||||
verbose, markdown, stream, metrics, reasoning_steps = False, False, False, False, False
|
||||
actions_trace = False # No callbacks by default
|
||||
json_output = False
|
||||
status_trace = False
|
||||
simple_output = False
|
||||
|
||||
# Enable trace output mode if configured (takes priority)
|
||||
# This provides timestamped inline status with duration
|
||||
if status_trace:
|
||||
try:
|
||||
from ..output.trace import enable_trace_output, is_trace_output_enabled
|
||||
if not is_trace_output_enabled():
|
||||
enable_trace_output(use_color=True, show_timestamps=True)
|
||||
except ImportError:
|
||||
pass # Trace module not available
|
||||
# Enable status output mode if configured (simple progress, no timestamps)
|
||||
# This registers callbacks to capture tool calls and final output
|
||||
elif actions_trace:
|
||||
try:
|
||||
from ..output.status import enable_status_output, is_status_output_enabled
|
||||
if not is_status_output_enabled():
|
||||
output_format = "jsonl" if json_output else "text"
|
||||
# simple_output=True means status preset (no timestamps)
|
||||
# metrics=True means debug preset (show token/cost info)
|
||||
enable_status_output(
|
||||
redact=True,
|
||||
use_color=True,
|
||||
format=output_format,
|
||||
show_timestamps=not simple_output,
|
||||
show_metrics=metrics
|
||||
)
|
||||
except ImportError:
|
||||
pass # Status module not available
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Resolve EXECUTION param using unified resolver
|
||||
@@ -755,7 +800,15 @@ class Agent:
|
||||
|
||||
# If instructions are provided, use them to set role, goal, and backstory
|
||||
if instructions:
|
||||
self.name = name or "Agent"
|
||||
# Only use explicitly provided name, don't auto-generate from instructions
|
||||
# Auto-generation was producing confusing names like "You Are Agent" from
|
||||
# instructions like "You are a helpful assistant"
|
||||
if name:
|
||||
self.name = name
|
||||
else:
|
||||
# Don't auto-generate - None signals "no explicit name provided"
|
||||
# Display logic will skip Agent Info panel when name is None
|
||||
self.name = None
|
||||
self.role = role or "Assistant"
|
||||
self.goal = goal or instructions
|
||||
self.backstory = backstory or instructions
|
||||
@@ -824,7 +877,7 @@ class Agent:
|
||||
self.llm_instance = LLM(**llm_config)
|
||||
else:
|
||||
# Create LLM with model string and base_url
|
||||
model_name = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
model_name = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
self.llm_instance = LLM(
|
||||
model=model_name,
|
||||
base_url=base_url,
|
||||
@@ -887,7 +940,7 @@ class Agent:
|
||||
) from e
|
||||
# Otherwise, fall back to OpenAI environment/name
|
||||
else:
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
# Handle tools parameter - ensure it's always a list
|
||||
if callable(tools):
|
||||
# If a single function/callable is passed, wrap it in a list
|
||||
@@ -911,6 +964,7 @@ class Agent:
|
||||
self._memory_instance = None
|
||||
self._init_memory(memory, user_id)
|
||||
self.verbose = verbose
|
||||
self._has_explicit_output_config = _has_explicit_output # Track if user set output mode
|
||||
self.allow_delegation = allow_delegation
|
||||
self.step_callback = step_callback
|
||||
self.cache = cache
|
||||
@@ -933,7 +987,7 @@ class Agent:
|
||||
self.min_reflect = min_reflect
|
||||
self.reflect_prompt = reflect_prompt
|
||||
# Use the same model selection logic for reflect_llm
|
||||
self.reflect_llm = reflect_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
self.reflect_llm = reflect_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
self._console = None # Lazy load console when needed
|
||||
|
||||
# Initialize system prompt
|
||||
@@ -1065,6 +1119,16 @@ Your Goal: {self.goal}
|
||||
self._context_param = context # Store raw param for lazy init
|
||||
self._context_manager = None # Lazy initialized on first use
|
||||
self._context_manager_initialized = False
|
||||
|
||||
# Action trace mode - handled via display callbacks, not separate emitter
|
||||
self._actions_trace = actions_trace
|
||||
|
||||
# Telemetry
|
||||
try:
|
||||
from ..telemetry import get_telemetry
|
||||
self._telemetry = get_telemetry()
|
||||
except (ImportError, AttributeError):
|
||||
self._telemetry = None
|
||||
|
||||
@property
|
||||
def auto_memory(self):
|
||||
@@ -1184,7 +1248,11 @@ Your Goal: {self.goal}
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
"""Lazily initialize Rich Console only when needed."""
|
||||
"""Lazily initialize Rich Console only when needed AND verbose is True."""
|
||||
# Only return console if verbose mode is enabled
|
||||
# This prevents panels from being shown in status/silent modes
|
||||
if not self.verbose:
|
||||
return None
|
||||
if self._console is None:
|
||||
from rich.console import Console
|
||||
self._console = Console()
|
||||
@@ -1720,7 +1788,7 @@ Your Goal: {self.goal}
|
||||
elif hasattr(self, 'llm') and self.llm:
|
||||
model_name = self.llm
|
||||
else:
|
||||
model_name = "gpt-5-nano"
|
||||
model_name = "gpt-4o-mini"
|
||||
|
||||
return supports_web_search(model_name)
|
||||
|
||||
@@ -1742,7 +1810,7 @@ Your Goal: {self.goal}
|
||||
elif hasattr(self, 'llm') and self.llm:
|
||||
model_name = self.llm
|
||||
else:
|
||||
model_name = "gpt-5-nano"
|
||||
model_name = "gpt-4o-mini"
|
||||
|
||||
return supports_web_fetch(model_name)
|
||||
|
||||
@@ -1986,7 +2054,7 @@ Your Goal: {self.goal}
|
||||
|
||||
Returns:
|
||||
The LLM model/instance being used by this agent.
|
||||
- For standard models: returns the model string (e.g., "gpt-5-nano")
|
||||
- For standard models: returns the model string (e.g., "gpt-4o-mini")
|
||||
- For custom LLM instances: returns the LLM instance object
|
||||
- For provider models: returns the LLM instance object
|
||||
"""
|
||||
@@ -1996,7 +2064,7 @@ Your Goal: {self.goal}
|
||||
return self.llm
|
||||
else:
|
||||
# Default fallback
|
||||
return "gpt-5-nano"
|
||||
return "gpt-4o-mini"
|
||||
|
||||
def _ensure_knowledge_processed(self):
|
||||
"""Ensure knowledge is initialized and processed when first accessed."""
|
||||
@@ -2817,6 +2885,9 @@ Your Goal: {self.goal}"""
|
||||
"""
|
||||
logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
|
||||
|
||||
# NOTE: tool_call callback is triggered by display_tool_call in openai_client.py
|
||||
# Do NOT call it here to avoid duplicate output
|
||||
|
||||
# Set up injection context for tools with Injected parameters
|
||||
from ..tools.injected import AgentState, with_injection_context
|
||||
state = AgentState(
|
||||
@@ -3131,21 +3202,20 @@ Your Goal: {self.goal}"""
|
||||
|
||||
This centralizes the logic for callback execution and display to avoid duplication.
|
||||
"""
|
||||
# Always execute callbacks regardless of verbose setting (only when not using custom LLM)
|
||||
if not self._using_custom_llm:
|
||||
execute_sync_callback(
|
||||
'interaction',
|
||||
message=prompt,
|
||||
response=response,
|
||||
markdown=self.markdown,
|
||||
generation_time=generation_time,
|
||||
agent_name=self.name,
|
||||
agent_role=self.role,
|
||||
agent_tools=[t.__name__ for t in self.tools] if self.tools else None,
|
||||
task_name=task_name,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
)
|
||||
# Always execute callbacks for status/trace output (regardless of LLM backend)
|
||||
execute_sync_callback(
|
||||
'interaction',
|
||||
message=prompt,
|
||||
response=response,
|
||||
markdown=self.markdown,
|
||||
generation_time=generation_time,
|
||||
agent_name=self.name,
|
||||
agent_role=self.role,
|
||||
agent_tools=[t.__name__ for t in self.tools] if self.tools else None,
|
||||
task_name=task_name,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
)
|
||||
# Always display final interaction when verbose is True to ensure consistent formatting
|
||||
# This ensures both OpenAI and custom LLM providers (like Gemini) show formatted output
|
||||
if self.verbose and not self._final_display_shown:
|
||||
@@ -3560,7 +3630,7 @@ Your Goal: {self.goal}"""
|
||||
output_pydantic=output_pydantic,
|
||||
verbose=self.verbose,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
reflection=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
console=self.console,
|
||||
@@ -3696,7 +3766,7 @@ Your Goal: {self.goal}"""
|
||||
if self.verbose:
|
||||
logging.debug(f"Agent {self.name} final response: {response_text}")
|
||||
# Return only reasoning content if reasoning_steps is True
|
||||
if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content'):
|
||||
if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content') and response.choices[0].message.reasoning_content:
|
||||
# Apply guardrail to reasoning content
|
||||
try:
|
||||
validated_reasoning = self._apply_guardrail_with_retry(response.choices[0].message.reasoning_content, original_prompt, temperature, tools, task_name, task_description, task_id)
|
||||
@@ -3848,6 +3918,10 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
|
||||
async def achat(self, prompt: str, temperature=1.0, tools=None, output_json=None, output_pydantic=None, reasoning_steps=False, task_name=None, task_description=None, task_id=None):
|
||||
"""Async version of chat method with self-reflection support."""
|
||||
# Track execution via telemetry
|
||||
if hasattr(self, '_telemetry') and self._telemetry:
|
||||
self._telemetry.track_agent_execution(self.name, success=True)
|
||||
|
||||
# Reset the final display flag for each new conversation
|
||||
self._final_display_shown = False
|
||||
|
||||
@@ -3914,7 +3988,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
output_pydantic=output_pydantic,
|
||||
verbose=self.verbose,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
reflection=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
console=self.console,
|
||||
@@ -4221,7 +4295,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
|
||||
final_response = process_stream_chunks(chunks)
|
||||
# Return only reasoning content if reasoning_steps is True
|
||||
if reasoning_steps and hasattr(final_response.choices[0].message, 'reasoning_content'):
|
||||
if reasoning_steps and hasattr(final_response.choices[0].message, 'reasoning_content') and final_response.choices[0].message.reasoning_content:
|
||||
return final_response.choices[0].message.reasoning_content
|
||||
return final_response.choices[0].message.content if final_response else full_response_text
|
||||
|
||||
@@ -4235,16 +4309,94 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
return None
|
||||
|
||||
async def arun(self, prompt: str, **kwargs):
|
||||
"""Async alias for astart() method"""
|
||||
return await self.astart(prompt, **kwargs)
|
||||
"""Async version of run() - silent, non-streaming, returns structured result.
|
||||
|
||||
Production-friendly async execution. Does not stream or display output.
|
||||
|
||||
Args:
|
||||
prompt: The input prompt to process
|
||||
**kwargs: Additional arguments passed to achat()
|
||||
|
||||
Returns:
|
||||
The agent's response as a string
|
||||
"""
|
||||
# Remove stream from kwargs since achat() doesn't accept it
|
||||
kwargs.pop('stream', None)
|
||||
return await self.achat(prompt, **kwargs)
|
||||
|
||||
async def astart(self, prompt: str, **kwargs):
|
||||
"""Async version of start method"""
|
||||
"""Async version of start() - interactive, streaming-aware.
|
||||
|
||||
Beginner-friendly async execution. Streams by default when in TTY.
|
||||
|
||||
Args:
|
||||
prompt: The input prompt to process
|
||||
**kwargs: Additional arguments passed to achat()
|
||||
|
||||
Returns:
|
||||
The agent's response as a string
|
||||
"""
|
||||
import sys
|
||||
|
||||
# Determine streaming behavior (same logic as start())
|
||||
stream_requested = kwargs.get('stream')
|
||||
if stream_requested is None:
|
||||
if getattr(self, 'stream', None) is not None:
|
||||
stream_requested = self.stream
|
||||
else:
|
||||
stream_requested = sys.stdout.isatty()
|
||||
|
||||
kwargs['stream'] = stream_requested
|
||||
return await self.achat(prompt, **kwargs)
|
||||
|
||||
def run(self, prompt: str, **kwargs):
|
||||
"""Alias for start() method"""
|
||||
return self.start(prompt, **kwargs)
|
||||
"""Execute agent silently and return structured result.
|
||||
|
||||
Production-friendly execution. Always uses silent mode with no streaming
|
||||
or verbose display, regardless of TTY status. Use this for programmatic,
|
||||
scripted, or automated usage where you want just the result.
|
||||
|
||||
Args:
|
||||
prompt: The input prompt to process
|
||||
**kwargs: Additional arguments:
|
||||
- stream (bool): Force streaming if True. Default: False
|
||||
- output (str): Output preset override (rarely needed)
|
||||
|
||||
Returns:
|
||||
The agent's response as a string
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = Agent(instructions="You are helpful")
|
||||
result = agent.run("What is 2+2?") # Silent, returns "4"
|
||||
print(result)
|
||||
```
|
||||
|
||||
Note:
|
||||
Unlike .start() which enables verbose output in TTY for interactive
|
||||
use, .run() is always silent. This makes it suitable for:
|
||||
- Production pipelines
|
||||
- Automated scripts
|
||||
- Background processing
|
||||
- API endpoints
|
||||
"""
|
||||
# Production defaults: no streaming, no display
|
||||
if 'stream' not in kwargs:
|
||||
kwargs['stream'] = False
|
||||
|
||||
# Load history context
|
||||
self._load_history_context()
|
||||
|
||||
# Check if planning mode is enabled
|
||||
if self.planning:
|
||||
result = self._start_with_planning(prompt, **kwargs)
|
||||
else:
|
||||
result = self.chat(prompt, **kwargs)
|
||||
|
||||
# Auto-save session if enabled
|
||||
self._auto_save_session()
|
||||
|
||||
return result
|
||||
|
||||
def _get_planning_agent(self):
|
||||
"""Lazy load PlanningAgent for planning mode."""
|
||||
@@ -4378,26 +4530,297 @@ Write the complete compiled report:"""
|
||||
|
||||
# Chat history is preserved in self.chat_history (no action needed)
|
||||
|
||||
def start(self, prompt: str, **kwargs):
|
||||
"""Start the agent with a prompt. This is a convenience method that wraps chat()."""
|
||||
# Load history from past sessions (now handled via context= param)
|
||||
def start(self, prompt: str = None, **kwargs):
|
||||
"""Start the agent interactively with verbose output.
|
||||
|
||||
Beginner-friendly execution. Defaults to verbose output with streaming
|
||||
when running in a TTY. Use this for interactive/terminal usage where
|
||||
you want to see output in real-time with rich formatting.
|
||||
|
||||
Args:
|
||||
prompt: The input prompt to process. If not provided, uses the
|
||||
agent's instructions as the task (useful when instructions
|
||||
already describe what the agent should do).
|
||||
**kwargs: Additional arguments:
|
||||
- stream (bool | None): Override streaming. None = auto-detect TTY
|
||||
- output (str): Output preset override (e.g., "silent", "verbose")
|
||||
|
||||
Returns:
|
||||
- If streaming: Generator yielding response chunks
|
||||
- If not streaming: The complete response as a string
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Minimal usage - instructions IS the task
|
||||
agent = Agent(instructions="Research AI trends and summarize")
|
||||
result = agent.start() # Uses instructions as task
|
||||
|
||||
# With explicit prompt (overrides/adds to instructions)
|
||||
agent = Agent(instructions="You are a helpful assistant")
|
||||
result = agent.start("What is 2+2?") # Uses prompt as task
|
||||
```
|
||||
|
||||
Note:
|
||||
Unlike .run() which is always silent (production use), .start()
|
||||
enables verbose output by default when in a TTY for beginner-friendly
|
||||
interactive use. Use .run() for programmatic/scripted usage.
|
||||
"""
|
||||
import sys
|
||||
|
||||
# If no prompt provided, use instructions as the task
|
||||
if prompt is None:
|
||||
prompt = self.instructions or "Hello"
|
||||
|
||||
# Load history from past sessions
|
||||
self._load_history_context()
|
||||
|
||||
# Check if planning mode is enabled
|
||||
if self.planning:
|
||||
result = self._start_with_planning(prompt, **kwargs)
|
||||
elif kwargs.get('stream', getattr(self, 'stream', False)):
|
||||
# Return a generator for streaming response
|
||||
result = self._start_stream(prompt, **kwargs)
|
||||
else:
|
||||
# Return regular chat response for backward compatibility
|
||||
kwargs['stream'] = False
|
||||
result = self.chat(prompt, **kwargs)
|
||||
# Determine if we're in an interactive TTY
|
||||
is_tty = sys.stdout.isatty()
|
||||
|
||||
# Determine streaming behavior
|
||||
# Priority: explicit kwarg > agent's stream attribute > TTY detection
|
||||
stream_requested = kwargs.get('stream')
|
||||
if stream_requested is None:
|
||||
# Check agent's stream attribute first
|
||||
if getattr(self, 'stream', None) is not None:
|
||||
stream_requested = self.stream
|
||||
else:
|
||||
# Auto-detect: stream if stdout is a TTY (interactive terminal)
|
||||
stream_requested = is_tty
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Enable verbose output in TTY for beginner-friendly interactive use
|
||||
# Priority: agent's explicit output config > start() override > TTY auto
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
original_verbose = self.verbose
|
||||
original_markdown = self.markdown
|
||||
output_override = kwargs.pop('output', None) # Pop to prevent passing to chat()
|
||||
|
||||
# Check if agent was configured with explicit output mode (not default)
|
||||
# If so, respect it and don't auto-enable verbose for TTY
|
||||
has_explicit_output = getattr(self, '_has_explicit_output_config', False)
|
||||
|
||||
try:
|
||||
# Apply output override from start() call if provided
|
||||
if output_override:
|
||||
# Apply explicit output preset for this call
|
||||
from ..config.presets import OUTPUT_PRESETS
|
||||
if output_override in OUTPUT_PRESETS:
|
||||
preset = OUTPUT_PRESETS[output_override]
|
||||
self.verbose = preset.get('verbose', False)
|
||||
self.markdown = preset.get('markdown', False)
|
||||
# Only auto-enable verbose for TTY if NO explicit output was configured
|
||||
elif is_tty and not has_explicit_output:
|
||||
self.verbose = True
|
||||
self.markdown = True
|
||||
|
||||
# Check if planning mode is enabled
|
||||
if self.planning:
|
||||
result = self._start_with_planning(prompt, **kwargs)
|
||||
elif stream_requested:
|
||||
# Return a generator for streaming response
|
||||
kwargs['stream'] = True
|
||||
result = self._start_stream(prompt, **kwargs)
|
||||
else:
|
||||
# Return regular chat response with animated working status
|
||||
kwargs['stream'] = False
|
||||
|
||||
# Show animated status during LLM call if verbose
|
||||
if self.verbose and is_tty:
|
||||
from rich.live import Live
|
||||
from rich.console import Group
|
||||
from rich.status import Status
|
||||
from ..main import PRAISON_COLORS, sync_display_callbacks
|
||||
import threading
|
||||
import time as time_module
|
||||
|
||||
console = Console()
|
||||
start_time = time_module.time()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Shared state for dynamic status messages (thread-safe)
|
||||
# Updated by callbacks during tool execution
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
current_status = ["Analyzing query..."]
|
||||
tools_called = []
|
||||
|
||||
# Register a temporary callback to track tool calls
|
||||
def status_tool_callback(**kwargs):
|
||||
tool_name = kwargs.get('tool_name', '')
|
||||
if tool_name:
|
||||
tools_called.append(tool_name)
|
||||
current_status[0] = f"Calling tool: {tool_name}..."
|
||||
|
||||
# Store original callback and register ours
|
||||
original_tool_callback = sync_display_callbacks.get('tool_call')
|
||||
sync_display_callbacks['tool_call'] = status_tool_callback
|
||||
|
||||
# Animation state
|
||||
result_holder = [None]
|
||||
error_holder = [None]
|
||||
|
||||
# Temporarily disable verbose in chat to prevent duplicate output
|
||||
original_verbose_chat = self.verbose
|
||||
|
||||
def run_chat():
|
||||
try:
|
||||
# Suppress verbose during animation - we'll display result ourselves
|
||||
self.verbose = False
|
||||
current_status[0] = "Sending to LLM..."
|
||||
result_holder[0] = self.chat(prompt, **kwargs)
|
||||
current_status[0] = "Finalizing response..."
|
||||
except Exception as e:
|
||||
error_holder[0] = e
|
||||
finally:
|
||||
self.verbose = original_verbose_chat
|
||||
# Restore original callback
|
||||
if original_tool_callback:
|
||||
sync_display_callbacks['tool_call'] = original_tool_callback
|
||||
elif 'tool_call' in sync_display_callbacks:
|
||||
del sync_display_callbacks['tool_call']
|
||||
|
||||
# Start chat in background thread
|
||||
chat_thread = threading.Thread(target=run_chat)
|
||||
chat_thread.start()
|
||||
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.markdown import Markdown
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Smart Agent Info: Only show if user provided meaningful info
|
||||
# Skip if using defaults ("Agent"/"Assistant") with single agent
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
has_custom_name = self.name and self.name not in ("Agent", "agent", None, "")
|
||||
has_custom_role = self.role and self.role not in ("Assistant", "AI Assistant", "assistant", None, "")
|
||||
has_tools = bool(self.tools)
|
||||
|
||||
show_agent_info = has_custom_name or has_custom_role or has_tools
|
||||
|
||||
agent_panel = None
|
||||
if show_agent_info:
|
||||
agent_info_parts = []
|
||||
if has_custom_name:
|
||||
agent_info_parts.append(f"[bold {PRAISON_COLORS['task']}]👤 Agent:[/] [{PRAISON_COLORS['agent_text']}]{self.name}[/]")
|
||||
if has_custom_role:
|
||||
agent_info_parts.append(f"[bold {PRAISON_COLORS['metrics']}]Role:[/] [{PRAISON_COLORS['agent_text']}]{self.role}[/]")
|
||||
if has_tools:
|
||||
tools_list = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools][:5]
|
||||
tools_str = ", ".join(f"[italic {PRAISON_COLORS['response']}]{tool}[/]" for tool in tools_list)
|
||||
agent_info_parts.append(f"[bold {PRAISON_COLORS['agent']}]Tools:[/] {tools_str}")
|
||||
|
||||
agent_panel = Panel(
|
||||
"\n".join(agent_info_parts),
|
||||
border_style=PRAISON_COLORS["agent"],
|
||||
title="[bold]Agent Info[/]",
|
||||
title_align="left",
|
||||
padding=(1, 2)
|
||||
)
|
||||
|
||||
# Create task panel
|
||||
task_panel = Panel.fit(
|
||||
Markdown(prompt) if self.markdown else Text(prompt),
|
||||
title="Task",
|
||||
border_style=PRAISON_COLORS["task"]
|
||||
)
|
||||
|
||||
# Show initial panels (agent info if applicable, then task)
|
||||
if agent_panel:
|
||||
console.print(agent_panel)
|
||||
console.print(task_panel)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Animate with Rich.Status showing DYNAMIC status from callbacks
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
with console.status(
|
||||
f"[bold yellow]Working...[/] {current_status[0]}",
|
||||
spinner="dots",
|
||||
spinner_style="yellow"
|
||||
) as status:
|
||||
last_status = current_status[0]
|
||||
while chat_thread.is_alive():
|
||||
time_module.sleep(0.15) # More responsive updates
|
||||
# Only update if status changed (reduces flicker)
|
||||
if current_status[0] != last_status:
|
||||
last_status = current_status[0]
|
||||
status.update(f"[bold yellow]Working...[/] {current_status[0]}")
|
||||
|
||||
# Calculate elapsed time
|
||||
elapsed = time_module.time() - start_time
|
||||
|
||||
# Re-raise any error from chat
|
||||
if error_holder[0]:
|
||||
raise error_holder[0]
|
||||
|
||||
result = result_holder[0]
|
||||
|
||||
# Display response panel
|
||||
response_panel = Panel.fit(
|
||||
Markdown(str(result)) if self.markdown else Text(str(result)),
|
||||
title=f"Response ({elapsed:.1f}s)",
|
||||
border_style=PRAISON_COLORS["response"]
|
||||
)
|
||||
console.print(response_panel)
|
||||
|
||||
# Show tool activity summary if tools were called (deduplicated)
|
||||
if tools_called:
|
||||
# Use dict.fromkeys to preserve order while removing duplicates
|
||||
unique_tools = list(dict.fromkeys(tools_called))
|
||||
tools_summary = ", ".join(unique_tools)
|
||||
console.print(f"[dim]🔧 Tools used: {tools_summary}[/dim]")
|
||||
else:
|
||||
result = self.chat(prompt, **kwargs)
|
||||
|
||||
# Auto-save session if enabled
|
||||
self._auto_save_session()
|
||||
|
||||
return result
|
||||
finally:
|
||||
# Restore original output settings
|
||||
self.verbose = original_verbose
|
||||
self.markdown = original_markdown
|
||||
|
||||
def iter_stream(self, prompt: str, **kwargs):
|
||||
"""Stream agent response as an iterator of chunks.
|
||||
|
||||
App-friendly streaming. Yields response chunks without terminal display.
|
||||
Use this for building custom UIs or processing streams programmatically.
|
||||
|
||||
Args:
|
||||
prompt: The input prompt to process
|
||||
**kwargs: Additional arguments:
|
||||
- display (bool): Show terminal output. Default: False
|
||||
- output (str): Output preset override
|
||||
|
||||
Yields:
|
||||
str: Response chunks as they are generated
|
||||
|
||||
Example:
|
||||
```python
|
||||
agent = Agent(instructions="You are helpful")
|
||||
|
||||
# Process stream programmatically
|
||||
full_response = ""
|
||||
for chunk in agent.iter_stream("Tell me a story"):
|
||||
full_response += chunk
|
||||
# Custom processing here
|
||||
|
||||
# Or collect all at once
|
||||
response = "".join(agent.iter_stream("Hello"))
|
||||
```
|
||||
"""
|
||||
# Load history context
|
||||
self._load_history_context()
|
||||
|
||||
# Force streaming, no display by default (app-friendly)
|
||||
kwargs['stream'] = True
|
||||
|
||||
# Use the internal streaming generator
|
||||
for chunk in self._start_stream(prompt, **kwargs):
|
||||
yield chunk
|
||||
|
||||
# Auto-save session if enabled
|
||||
self._auto_save_session()
|
||||
|
||||
return result
|
||||
|
||||
def _load_history_context(self):
|
||||
"""Load history from past sessions into context.
|
||||
|
||||
@@ -434,8 +434,7 @@ This report contains all agent interactions and outputs from a complete ContextA
|
||||
|
||||
Provide comprehensive analysis that follows the PRD template principles and enables
|
||||
AI assistants to implement features that perfectly match existing codebase patterns.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
prompt = f"""Analyze this gitingest codebase digest following PRD template methodology:
|
||||
@@ -515,8 +514,7 @@ codebase style and architecture following PRD template principles."""
|
||||
role="Expert Manual Codebase Analysis Specialist",
|
||||
goal="Perform comprehensive manual codebase analysis following PRD methodology",
|
||||
instructions="""Analyze the codebase samples following PRD template methodology for complete understanding.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Format comprehensive sample
|
||||
@@ -566,8 +564,7 @@ Analyze following PRD principles to extract patterns, conventions, and architect
|
||||
6. Design pattern implementations
|
||||
7. Code complexity metrics
|
||||
8. API and interface patterns""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Perform AST analysis on Python files
|
||||
@@ -624,8 +621,7 @@ Extract comprehensive patterns that follow PRD template principles for implement
|
||||
|
||||
For each pattern, provide the pattern name, where it's used, and how to replicate it
|
||||
following PRD template principles.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Get representative code samples
|
||||
@@ -674,8 +670,7 @@ patterns and best practices for first-try success."""
|
||||
goal="Analyze testing patterns for comprehensive validation framework design",
|
||||
instructions="""Analyze testing patterns to understand validation approaches and create
|
||||
comprehensive test frameworks following PRD methodology.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Get test files
|
||||
@@ -801,8 +796,7 @@ Extract testing patterns for validation framework creation following PRD princip
|
||||
Confidence level for one-pass implementation
|
||||
|
||||
Generate PRPs following this EXACT structure for first-try implementation success.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
prompt = f"""Generate a comprehensive Product Requirements Prompt (PRP) following the EXACT PRD template structure:
|
||||
@@ -850,8 +844,7 @@ on the first try following PRD template principles."""
|
||||
6. CODE QUALITY: Complexity analysis, maintainability
|
||||
7. DOCUMENTATION VALIDATION: Documentation completeness
|
||||
8. DEPENDENCY VALIDATION: Dependency analysis and security""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Analyze existing validation patterns
|
||||
@@ -897,8 +890,7 @@ following PRD template principles."""
|
||||
instructions="""Compile all available documentation following PRD methodology including:
|
||||
README files, API documentation, setup guides, architecture docs, and any other
|
||||
relevant documentation that provides context for implementation.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Get documentation files
|
||||
@@ -943,8 +935,7 @@ following PRD template principles."""
|
||||
instructions="""Analyze integration points following PRD methodology including:
|
||||
APIs, databases, external services, configuration points, and any other
|
||||
integration requirements that affect implementation.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Get configuration and dependency files
|
||||
@@ -1004,8 +995,7 @@ following PRD template principles."""
|
||||
8. DOCUMENTATION UPDATES: Documentation to create/update
|
||||
9. INTEGRATION STEPS: How to integrate with existing systems
|
||||
10. VALIDATION CHECKPOINTS: Validation steps at each phase""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
prompt = f"""Create detailed implementation blueprint following PRD methodology:
|
||||
@@ -1302,8 +1292,7 @@ Every agent interaction has been saved for full audit trail and reproducibility.
|
||||
GOAL: [extracted implementation goal]
|
||||
|
||||
Be precise and extract only what is explicitly mentioned or clearly implied.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
prompt = f"""Parse this user input to extract codebase and goal:
|
||||
@@ -1536,8 +1525,7 @@ Note: Detailed function/class metadata not available due to content access limit
|
||||
5. Documentation topics
|
||||
|
||||
Make the output easy for a file selection agent to understand which files contain what functionality.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
structure_text = basic_structure.get('structure', '')
|
||||
@@ -1770,8 +1758,7 @@ Focus on creating clear, structured metadata that will help with intelligent fil
|
||||
["README.md", "src/auth/login.py", "config/settings.py", ...]
|
||||
|
||||
Maximum 50 files for efficient analysis.""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
# Prepare enhanced structure information
|
||||
@@ -1923,8 +1910,7 @@ Maximum 50 files.""".format(goal=goal)
|
||||
8. EXAMPLES: Similar features that can guide {goal} implementation
|
||||
|
||||
Since these files were pre-selected for relevance, provide deep analysis of how each contributes to implementing: {goal}""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
prompt = f"""Analyze these carefully selected repository files for implementing: {goal}
|
||||
@@ -2024,8 +2010,7 @@ Since these files were pre-selected for relevance, explain how each contributes
|
||||
- Success criteria for {goal}
|
||||
|
||||
Focus everything on successfully implementing: {goal}""",
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-5-nano",
|
||||
verbose=getattr(self, 'verbose', True)
|
||||
llm=self.llm if hasattr(self, 'llm') else "gpt-4o-mini",
|
||||
)
|
||||
|
||||
chunk_info = ""
|
||||
@@ -2303,13 +2288,13 @@ def create_context_agent(llm: Optional[Union[str, Any]] = None, **kwargs) -> Con
|
||||
Factory function to create a ContextAgent following Context Engineering and PRD methodology.
|
||||
|
||||
Args:
|
||||
llm: Language model to use (e.g., "gpt-5-nano", "claude-3-haiku")
|
||||
llm: Language model to use (e.g., "gpt-4o-mini", "claude-3-haiku")
|
||||
**kwargs: Additional arguments to pass to ContextAgent constructor
|
||||
|
||||
Returns:
|
||||
ContextAgent: Configured ContextAgent for comprehensive context generation following PRD principles
|
||||
"""
|
||||
if llm is None:
|
||||
llm = "gpt-5-nano"
|
||||
llm = "gpt-4o-mini"
|
||||
|
||||
return ContextAgent(llm=llm, **kwargs)
|
||||
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Agent Protocol Definitions.
|
||||
|
||||
Provides Protocol interfaces that define the minimal contract for agent implementations.
|
||||
This enables:
|
||||
- Mocking agents in tests without real LLM calls
|
||||
- Creating custom agent implementations
|
||||
- Type checking with static analyzers
|
||||
|
||||
These protocols are lightweight and have zero performance impact.
|
||||
"""
|
||||
from typing import Protocol, runtime_checkable, Optional, Any, Dict, List
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AgentProtocol(Protocol):
|
||||
"""
|
||||
Minimal Protocol for agent implementations.
|
||||
|
||||
This defines the essential interface that any agent must provide.
|
||||
It enables proper mocking and testing without real LLM dependencies.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Create a mock agent for testing
|
||||
class MockAgent:
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "TestAgent"
|
||||
|
||||
def chat(self, prompt: str, **kwargs) -> str:
|
||||
return "Mock response"
|
||||
|
||||
async def achat(self, prompt: str, **kwargs) -> str:
|
||||
return "Async mock response"
|
||||
|
||||
# Use in tests
|
||||
agent: AgentProtocol = MockAgent()
|
||||
result = agent.chat("Hello")
|
||||
```
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Agent's name identifier."""
|
||||
...
|
||||
|
||||
def chat(self, prompt: str, **kwargs) -> str:
|
||||
"""
|
||||
Synchronous chat with the agent.
|
||||
|
||||
Args:
|
||||
prompt: The user prompt or message
|
||||
**kwargs: Additional optional parameters (temperature, tools, etc.)
|
||||
|
||||
Returns:
|
||||
The agent's response as a string
|
||||
"""
|
||||
...
|
||||
|
||||
async def achat(self, prompt: str, **kwargs) -> str:
|
||||
"""
|
||||
Asynchronous chat with the agent.
|
||||
|
||||
Args:
|
||||
prompt: The user prompt or message
|
||||
**kwargs: Additional optional parameters (temperature, tools, etc.)
|
||||
|
||||
Returns:
|
||||
The agent's response as a string
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class RunnableAgentProtocol(AgentProtocol, Protocol):
|
||||
"""
|
||||
Extended Protocol for agents that support run/start methods.
|
||||
|
||||
This extends AgentProtocol with the run/start interface used
|
||||
by the Agent class for task execution.
|
||||
"""
|
||||
|
||||
def run(self, prompt: str, **kwargs) -> str:
|
||||
"""Run the agent with a prompt (alias for chat in most cases)."""
|
||||
...
|
||||
|
||||
def start(self, prompt: str, **kwargs) -> str:
|
||||
"""Start the agent with a prompt."""
|
||||
...
|
||||
|
||||
async def arun(self, prompt: str, **kwargs) -> str:
|
||||
"""Async run the agent with a prompt."""
|
||||
...
|
||||
|
||||
async def astart(self, prompt: str, **kwargs) -> str:
|
||||
"""Async start the agent with a prompt."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ToolAwareAgentProtocol(AgentProtocol, Protocol):
|
||||
"""
|
||||
Protocol for agents that support tools.
|
||||
|
||||
This extends AgentProtocol with tool-related properties.
|
||||
"""
|
||||
|
||||
@property
|
||||
def tools(self) -> List[Any]:
|
||||
"""List of tools available to the agent."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class MemoryAwareAgentProtocol(AgentProtocol, Protocol):
|
||||
"""
|
||||
Protocol for agents that support memory.
|
||||
|
||||
This extends AgentProtocol with memory-related properties.
|
||||
"""
|
||||
|
||||
@property
|
||||
def chat_history(self) -> List[Dict[str, Any]]:
|
||||
"""The agent's conversation history."""
|
||||
...
|
||||
|
||||
def clear_history(self) -> None:
|
||||
"""Clear the agent's conversation history."""
|
||||
...
|
||||
|
||||
|
||||
# Composite protocol for fully-featured agents
|
||||
@runtime_checkable
|
||||
class FullAgentProtocol(RunnableAgentProtocol, ToolAwareAgentProtocol, MemoryAwareAgentProtocol, Protocol):
|
||||
"""
|
||||
Complete Protocol for full-featured agents.
|
||||
|
||||
This combines all agent protocol features for agents that support
|
||||
the full interface (chat, run, tools, memory).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AgentProtocol',
|
||||
'RunnableAgentProtocol',
|
||||
'ToolAwareAgentProtocol',
|
||||
'MemoryAwareAgentProtocol',
|
||||
'FullAgentProtocol',
|
||||
]
|
||||
@@ -44,7 +44,7 @@ class RouterAgent(Agent):
|
||||
# Initialize model router
|
||||
self.model_router = model_router or ModelRouter()
|
||||
self.routing_strategy = routing_strategy
|
||||
self.fallback_model = fallback_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
self.fallback_model = fallback_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
|
||||
# Process models configuration
|
||||
self.available_models = self._process_models_config(models)
|
||||
|
||||
@@ -65,6 +65,52 @@ def process_video(video_path: str, seconds_per_frame=2):
|
||||
video.release()
|
||||
return base64_frames
|
||||
|
||||
|
||||
def get_multimodal_message(text_prompt: str, images: list) -> list:
|
||||
"""
|
||||
Build multimodal message content for LLM with text and images.
|
||||
|
||||
DRY helper - replaces duplicate _get_multimodal_message in aexecute_task/execute_task.
|
||||
|
||||
Args:
|
||||
text_prompt: The text content of the message
|
||||
images: List of image paths (local or URL)
|
||||
|
||||
Returns:
|
||||
List of content items for multimodal LLM message
|
||||
"""
|
||||
content = [{"type": "text", "text": text_prompt}]
|
||||
|
||||
for img in images:
|
||||
# If local file path for a valid image
|
||||
if os.path.exists(img):
|
||||
ext = os.path.splitext(img)[1].lower()
|
||||
# If it's a .mp4, convert to frames
|
||||
if ext == ".mp4":
|
||||
frames = process_video(img, seconds_per_frame=1)
|
||||
content.append({"type": "text", "text": "These are frames from the video."})
|
||||
for f in frames:
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
||||
})
|
||||
else:
|
||||
encoded = encode_file_to_base64(img)
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Treat as a remote URL
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": img}
|
||||
})
|
||||
return content
|
||||
|
||||
|
||||
def process_task_context(context_item, verbose=0, user_id=None):
|
||||
"""
|
||||
Process a single context item for task execution.
|
||||
@@ -363,7 +409,7 @@ class Agents:
|
||||
self.variables = variables if variables else {}
|
||||
|
||||
# Check for manager_llm in environment variable if not provided
|
||||
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
|
||||
# Set logger level based on verbose
|
||||
if _verbose >= 5:
|
||||
@@ -466,10 +512,16 @@ class Agents:
|
||||
|
||||
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}")
|
||||
|
||||
# Telemetry
|
||||
try:
|
||||
from ..telemetry import get_telemetry
|
||||
self._telemetry = get_telemetry()
|
||||
except (ImportError, AttributeError):
|
||||
self._telemetry = None
|
||||
|
||||
def add_task(self, task):
|
||||
task_id = self.task_id_counter
|
||||
task.id = task_id
|
||||
@@ -603,40 +655,9 @@ Context:
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
content = [{"type": "text", "text": text_prompt}]
|
||||
|
||||
for img in images:
|
||||
# If local file path for a valid image
|
||||
if os.path.exists(img):
|
||||
ext = os.path.splitext(img)[1].lower()
|
||||
# If it's a .mp4, convert to frames
|
||||
if ext == ".mp4":
|
||||
frames = process_video(img, seconds_per_frame=1)
|
||||
content.append({"type": "text", "text": "These are frames from the video."})
|
||||
for f in frames:
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
||||
})
|
||||
else:
|
||||
encoded = encode_file_to_base64(img)
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Treat as a remote URL
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": img}
|
||||
})
|
||||
return content
|
||||
|
||||
# Use shared multimodal helper (DRY - defined at module level)
|
||||
agent_output = await executor_agent.achat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
get_multimodal_message(task_prompt, task.images),
|
||||
tools=tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic,
|
||||
@@ -821,13 +842,17 @@ Context:
|
||||
await loop.run_in_executor(None, self.run_task, task_id)
|
||||
|
||||
async def astart(self, content=None, return_dict=False, **kwargs):
|
||||
"""Async version of start method
|
||||
"""Async version of start method.
|
||||
|
||||
Args:
|
||||
content: Optional content to add to all tasks' context
|
||||
return_dict: If True, returns the full results dictionary instead of only the final response
|
||||
**kwargs: Additional arguments
|
||||
"""
|
||||
# Track execution via telemetry
|
||||
if hasattr(self, '_telemetry') and self._telemetry:
|
||||
self._telemetry.track_agent_execution(self.name, success=True, async_mode=True)
|
||||
|
||||
if content:
|
||||
# Add content to context of all tasks
|
||||
for task in self.tasks.values():
|
||||
@@ -961,40 +986,9 @@ Context:
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
content = [{"type": "text", "text": text_prompt}]
|
||||
|
||||
for img in images:
|
||||
# If local file path for a valid image
|
||||
if os.path.exists(img):
|
||||
ext = os.path.splitext(img)[1].lower()
|
||||
# If it's a .mp4, convert to frames
|
||||
if ext == ".mp4":
|
||||
frames = process_video(img, seconds_per_frame=1)
|
||||
content.append({"type": "text", "text": "These are frames from the video."})
|
||||
for f in frames:
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
||||
})
|
||||
else:
|
||||
encoded = encode_file_to_base64(img)
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Treat as a remote URL
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": img}
|
||||
})
|
||||
return content
|
||||
|
||||
# Use shared multimodal helper (DRY - defined at module level)
|
||||
agent_output = executor_agent.chat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
get_multimodal_message(task_prompt, task.images),
|
||||
tools=task.tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic,
|
||||
@@ -1193,30 +1187,147 @@ Context:
|
||||
return str(agent[0])
|
||||
return None
|
||||
|
||||
def start(self, content=None, return_dict=False, **kwargs):
|
||||
"""Start agent execution with optional content and config
|
||||
def start(self, content=None, return_dict=False, output=None, **kwargs):
|
||||
"""Start agent execution with verbose output (beginner-friendly).
|
||||
|
||||
Shows Rich panels with workflow progress when in TTY. Use .run() for
|
||||
silent execution in production/scripts.
|
||||
|
||||
Args:
|
||||
content: Optional content to add to all tasks' context
|
||||
return_dict: If True, returns the full results dictionary instead of only the final response
|
||||
return_dict: If True, returns the full results dictionary
|
||||
output: Output preset - "silent", "verbose", "normal", etc.
|
||||
Default in TTY: "verbose" (shows progress)
|
||||
Default non-TTY: "silent"
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Interactive - shows Rich panels
|
||||
agents = Agents(agents=[agent1, agent2])
|
||||
result = agents.start() # Verbose output by default
|
||||
|
||||
# Force silent mode
|
||||
result = agents.start(output="silent")
|
||||
```
|
||||
"""
|
||||
# Track execution via telemetry
|
||||
if hasattr(self, '_telemetry') and self._telemetry:
|
||||
self._telemetry.track_agent_execution(self.name, success=True)
|
||||
import sys
|
||||
from ..main import PRAISON_COLORS
|
||||
|
||||
# Determine if we're in an interactive TTY
|
||||
is_tty = sys.stdout.isatty()
|
||||
|
||||
# Resolve output mode (TTY-aware)
|
||||
if output is None:
|
||||
# Default: verbose in TTY (beginner-friendly), silent otherwise
|
||||
# Note: Don't check self.verbose here - start() is for interactive use
|
||||
show_verbose = is_tty
|
||||
elif output == "silent":
|
||||
show_verbose = False
|
||||
elif output in ("verbose", "debug", "normal"):
|
||||
show_verbose = True
|
||||
else:
|
||||
show_verbose = is_tty
|
||||
|
||||
# Add content to context if provided
|
||||
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)
|
||||
|
||||
# Planning Mode: Create plan and todo list before execution
|
||||
if self.planning:
|
||||
self._run_with_planning()
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Verbose Mode: Show Rich panels for multi-agent workflow
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
if show_verbose and is_tty:
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
console = Console()
|
||||
import time as time_module
|
||||
|
||||
# Show workflow overview panel
|
||||
agent_names = " → ".join([a.name for a in self.agents])
|
||||
workflow_info = f"[bold {PRAISON_COLORS['metrics']}]Process:[/] {self.process}\n"
|
||||
workflow_info += f"[bold {PRAISON_COLORS['metrics']}]Agents:[/] {agent_names}"
|
||||
|
||||
console.print(Panel(
|
||||
workflow_info,
|
||||
title="[bold]Multi-Agent Workflow[/]",
|
||||
border_style=PRAISON_COLORS["agent"],
|
||||
padding=(1, 2)
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Execute tasks with verbose output
|
||||
total_agents = len(self.agents)
|
||||
workflow_start_time = time_module.time()
|
||||
|
||||
for idx, (task_id, task) in enumerate(self.tasks.items(), 1):
|
||||
agent = task.agent
|
||||
agent_name = agent.name if agent else "Unknown"
|
||||
agent_model = getattr(agent, 'llm', 'gpt-4o-mini') if agent else "unknown"
|
||||
|
||||
# Show agent task panel with model info
|
||||
task_desc = task.description[:100] + "..." if len(task.description) > 100 else task.description
|
||||
panel_content = f"[bold {PRAISON_COLORS['task_text']}]📋 Task:[/] {task_desc}\n"
|
||||
panel_content += f"[dim]🤖 Model: {agent_model}[/dim]"
|
||||
console.print(Panel.fit(
|
||||
panel_content,
|
||||
title=f"[bold]Agent [{idx}/{total_agents}]: {agent_name}[/]",
|
||||
border_style=PRAISON_COLORS["task"]
|
||||
))
|
||||
|
||||
# Execute with timing and status
|
||||
start_time = time_module.time()
|
||||
|
||||
# Show working spinner
|
||||
with console.status(
|
||||
f"[bold yellow]Working...[/] {agent_name} generating response...",
|
||||
spinner="dots",
|
||||
spinner_style="yellow"
|
||||
):
|
||||
# Run the task
|
||||
if self.planning:
|
||||
self._run_with_planning()
|
||||
break # Planning mode handles all tasks
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
|
||||
elapsed = time_module.time() - start_time
|
||||
|
||||
# Show response panel - FULL response, no truncation
|
||||
result = self.get_task_result(task_id)
|
||||
if result:
|
||||
response_text = str(result.raw)
|
||||
# No truncation - show full response in verbose mode
|
||||
from rich.markdown import Markdown
|
||||
console.print(Panel(
|
||||
Markdown(response_text),
|
||||
title=f"[bold]Agent [{idx}/{total_agents}] Complete ({elapsed:.1f}s)[/]",
|
||||
border_style=PRAISON_COLORS["response"],
|
||||
padding=(1, 2)
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Workflow summary panel
|
||||
total_elapsed = time_module.time() - workflow_start_time
|
||||
console.print(Panel.fit(
|
||||
f"[bold green]Total Time:[/] {total_elapsed:.1f}s\n"
|
||||
f"[bold green]Agents Run:[/] {total_agents}/{total_agents}",
|
||||
title="[bold]✅ Workflow Complete[/]",
|
||||
border_style="green"
|
||||
))
|
||||
console.print()
|
||||
else:
|
||||
# Run tasks as before
|
||||
self.run_all_tasks()
|
||||
# Silent mode: Run tasks without display
|
||||
if self.planning:
|
||||
self._run_with_planning()
|
||||
else:
|
||||
self.run_all_tasks()
|
||||
|
||||
# Auto-display token metrics if any agent has metrics=True
|
||||
metrics_enabled = any(getattr(agent, 'metrics', False) for agent in self.agents)
|
||||
@@ -1224,10 +1335,8 @@ Context:
|
||||
try:
|
||||
self.display_token_usage()
|
||||
except (ImportError, AttributeError) as e:
|
||||
# Token tracking not available or not properly configured
|
||||
logging.debug(f"Could not auto-display token usage: {e}")
|
||||
except Exception as e:
|
||||
# Log unexpected errors for debugging
|
||||
logging.debug(f"Unexpected error in token metrics display: {e}")
|
||||
|
||||
# Get results
|
||||
@@ -1238,7 +1347,6 @@ Context:
|
||||
|
||||
# By default, return only the final agent's response
|
||||
if not return_dict:
|
||||
# Get the last task (assuming sequential processing)
|
||||
task_ids = list(self.tasks.keys())
|
||||
if task_ids:
|
||||
last_task_id = task_ids[-1]
|
||||
@@ -1246,12 +1354,21 @@ Context:
|
||||
if last_result:
|
||||
return last_result.raw
|
||||
|
||||
# Return full results dict if return_dict is True or if no final result was found
|
||||
return results
|
||||
|
||||
def run(self, content=None, return_dict=False, **kwargs):
|
||||
"""Alias for start() method to provide consistent API with Agent class"""
|
||||
return self.start(content=content, return_dict=return_dict, **kwargs)
|
||||
"""Run agents silently (production use).
|
||||
|
||||
Unlike .start() which shows verbose output, .run() executes silently
|
||||
for programmatic/production use.
|
||||
|
||||
Args:
|
||||
content: Optional content to add to all tasks' context
|
||||
return_dict: If True, returns the full results dictionary
|
||||
**kwargs: Additional arguments
|
||||
"""
|
||||
# Always run silently - no verbose output
|
||||
return self.start(content=content, return_dict=return_dict, output="silent", **kwargs)
|
||||
|
||||
def set_state(self, key: str, value: Any) -> None:
|
||||
"""Set a state value"""
|
||||
@@ -2191,7 +2308,7 @@ Context:
|
||||
# Inherit from original task if available
|
||||
memory=original_task.memory if original_task else None,
|
||||
callback=original_task.callback if original_task else None,
|
||||
guardrail=original_task.guardrail if original_task else None,
|
||||
guardrails=original_task.guardrail if original_task else None,
|
||||
max_retries=original_task.max_retries if original_task else 3,
|
||||
output_json=original_task.output_json if original_task else None,
|
||||
output_pydantic=original_task.output_pydantic if original_task else None,
|
||||
|
||||
@@ -103,7 +103,7 @@ class AutoAgents(Agents):
|
||||
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-5-nano')
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
self.function_calling_llm = function_calling_llm
|
||||
self.code_execution_mode = code_execution_mode
|
||||
self.embedder_config = embedder_config
|
||||
|
||||
@@ -13,10 +13,35 @@ from typing import Dict, Set, Optional, Callable, Any, Literal, List
|
||||
from functools import wraps
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass, field
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.prompt import Confirm
|
||||
|
||||
# Lazy import for rich components
|
||||
_rich_console = None
|
||||
_rich_panel = None
|
||||
_rich_confirm = None
|
||||
|
||||
def _get_rich_console():
|
||||
"""Lazy import rich Console."""
|
||||
global _rich_console
|
||||
if _rich_console is None:
|
||||
from rich.console import Console
|
||||
_rich_console = Console
|
||||
return _rich_console
|
||||
|
||||
def _get_rich_panel():
|
||||
"""Lazy import rich Panel."""
|
||||
global _rich_panel
|
||||
if _rich_panel is None:
|
||||
from rich.panel import Panel
|
||||
_rich_panel = Panel
|
||||
return _rich_panel
|
||||
|
||||
def _get_rich_confirm():
|
||||
"""Lazy import rich Confirm."""
|
||||
global _rich_confirm
|
||||
if _rich_confirm is None:
|
||||
from rich.prompt import Confirm
|
||||
_rich_confirm = Confirm
|
||||
return _rich_confirm
|
||||
|
||||
# Global registries for approval requirements
|
||||
APPROVAL_REQUIRED_TOOLS: Set[str] = set()
|
||||
@@ -308,6 +333,10 @@ def console_approval_callback(function_name: str, arguments: Dict[str, Any], ris
|
||||
|
||||
Displays tool information and prompts user for approval via console.
|
||||
"""
|
||||
Console = _get_rich_console()
|
||||
Panel = _get_rich_panel()
|
||||
Confirm = _get_rich_confirm()
|
||||
|
||||
console = Console()
|
||||
|
||||
# Create risk level styling
|
||||
|
||||
@@ -44,12 +44,79 @@ class MemoryBackend(str, Enum):
|
||||
MONGODB = "mongodb"
|
||||
|
||||
|
||||
class LearnScope(str, Enum):
|
||||
"""Scope for learning data visibility.
|
||||
|
||||
PRIVATE: Learning data is private to this user/agent (default, safest)
|
||||
SHARED: Learning data is shared with all agents
|
||||
"""
|
||||
PRIVATE = "private" # Private to this user/agent
|
||||
SHARED = "shared" # Shared with all agents
|
||||
|
||||
|
||||
@dataclass
|
||||
class LearnConfig:
|
||||
"""
|
||||
Configuration for continuous learning within memory system.
|
||||
|
||||
Learning captures patterns, preferences, and insights from agent interactions
|
||||
to improve future responses. All learning data is stored within the memory system.
|
||||
|
||||
Usage:
|
||||
# Simple enable
|
||||
Agent(memory=MemoryConfig(learn=True))
|
||||
|
||||
# With specific capabilities
|
||||
Agent(memory=MemoryConfig(
|
||||
learn=LearnConfig(
|
||||
persona=True, # User preferences
|
||||
insights=True, # Observations
|
||||
patterns=True, # Reusable knowledge
|
||||
)
|
||||
))
|
||||
"""
|
||||
# Learning capabilities
|
||||
persona: bool = True # User preferences and profile
|
||||
insights: bool = True # Observations and learnings
|
||||
thread: bool = True # Session/conversation context
|
||||
patterns: bool = False # Reusable knowledge patterns
|
||||
decisions: bool = False # Decision logging
|
||||
feedback: bool = False # Outcome signals
|
||||
improvements: bool = False # Self-improvement proposals
|
||||
|
||||
# Scope configuration
|
||||
scope: Union[str, LearnScope] = LearnScope.PRIVATE
|
||||
|
||||
# Storage configuration
|
||||
store_path: Optional[str] = None # Custom storage path
|
||||
|
||||
# Maintenance settings
|
||||
auto_consolidate: bool = True # Auto-consolidate learnings
|
||||
retention_days: Optional[int] = None # Days to retain (None = forever)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"persona": self.persona,
|
||||
"insights": self.insights,
|
||||
"thread": self.thread,
|
||||
"patterns": self.patterns,
|
||||
"decisions": self.decisions,
|
||||
"feedback": self.feedback,
|
||||
"improvements": self.improvements,
|
||||
"scope": self.scope.value if isinstance(self.scope, LearnScope) else self.scope,
|
||||
"store_path": self.store_path,
|
||||
"auto_consolidate": self.auto_consolidate,
|
||||
"retention_days": self.retention_days,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemoryConfig:
|
||||
"""
|
||||
Configuration for agent memory and session management.
|
||||
|
||||
Consolidates: memory, auto_memory, claude_memory, user_id, session_id, db
|
||||
Consolidates: memory, auto_memory, claude_memory, user_id, session_id, db, learn
|
||||
|
||||
Usage:
|
||||
# Simple enable (uses FileMemory)
|
||||
@@ -58,12 +125,22 @@ class MemoryConfig:
|
||||
# With backend
|
||||
Agent(memory=MemoryConfig(backend="redis"))
|
||||
|
||||
# Full config
|
||||
# Full config with learning
|
||||
Agent(memory=MemoryConfig(
|
||||
backend="sqlite",
|
||||
user_id="user123",
|
||||
session_id="session456",
|
||||
auto_memory=True,
|
||||
learn=True, # Enable continuous learning
|
||||
))
|
||||
|
||||
# With detailed learn config
|
||||
Agent(memory=MemoryConfig(
|
||||
learn=LearnConfig(
|
||||
persona=True,
|
||||
insights=True,
|
||||
patterns=True,
|
||||
)
|
||||
))
|
||||
"""
|
||||
# Backend selection
|
||||
@@ -85,8 +162,17 @@ class MemoryConfig:
|
||||
# Memory configuration dict (for provider-specific settings)
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Continuous learning configuration
|
||||
learn: Optional[Union[bool, LearnConfig]] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
learn_dict = None
|
||||
if self.learn is True:
|
||||
learn_dict = LearnConfig().to_dict()
|
||||
elif isinstance(self.learn, LearnConfig):
|
||||
learn_dict = self.learn.to_dict()
|
||||
|
||||
return {
|
||||
"backend": self.backend.value if isinstance(self.backend, MemoryBackend) else self.backend,
|
||||
"user_id": self.user_id,
|
||||
@@ -94,6 +180,7 @@ class MemoryConfig:
|
||||
"auto_memory": self.auto_memory,
|
||||
"claude_memory": self.claude_memory,
|
||||
"config": self.config,
|
||||
"learn": learn_dict,
|
||||
}
|
||||
|
||||
|
||||
@@ -391,10 +478,21 @@ class OutputConfig:
|
||||
|
||||
Consolidates: verbose, markdown, stream, metrics, reasoning_steps, output_style
|
||||
|
||||
DEFAULT: output="silent" (zero overhead, fastest performance)
|
||||
|
||||
Usage:
|
||||
# Simple preset
|
||||
# Default is silent mode (no output overhead, programmatic use)
|
||||
Agent(instructions="...") # Uses output="silent"
|
||||
|
||||
# Actions mode (tool calls + final output trace)
|
||||
Agent(output="actions")
|
||||
|
||||
# Verbose mode with Rich panels
|
||||
Agent(output="verbose")
|
||||
|
||||
# JSON mode for piping
|
||||
Agent(output="json")
|
||||
|
||||
# With config
|
||||
Agent(output=OutputConfig(
|
||||
verbose=True,
|
||||
@@ -404,11 +502,11 @@ class OutputConfig:
|
||||
reasoning_steps=True,
|
||||
))
|
||||
"""
|
||||
# Verbosity
|
||||
verbose: bool = True
|
||||
# Verbosity - False by default for silent mode (fastest)
|
||||
verbose: bool = False
|
||||
|
||||
# Formatting
|
||||
markdown: bool = True
|
||||
# Formatting - False by default for silent mode
|
||||
markdown: bool = False
|
||||
|
||||
# Streaming
|
||||
stream: bool = False
|
||||
@@ -422,6 +520,24 @@ class OutputConfig:
|
||||
# Output style (custom styling)
|
||||
style: Optional[Any] = None
|
||||
|
||||
# Actions trace mode - shows tool calls, agent lifecycle, final output
|
||||
# When True, registers callbacks and outputs to stderr
|
||||
# DEFAULT: False for zero overhead (silent mode)
|
||||
actions_trace: bool = False
|
||||
|
||||
# JSON output mode - emit JSONL events for piping
|
||||
json_output: bool = False
|
||||
|
||||
# Simple output mode - just print response without panels
|
||||
simple_output: bool = False
|
||||
|
||||
# Show LLM parameters (for debug mode)
|
||||
show_parameters: bool = False
|
||||
|
||||
# Status trace mode - clean inline status updates
|
||||
# Shows: [timestamp] Calling LLM..., Executing tool..., Response: ...
|
||||
status_trace: bool = False
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
@@ -430,6 +546,11 @@ class OutputConfig:
|
||||
"stream": self.stream,
|
||||
"metrics": self.metrics,
|
||||
"reasoning_steps": self.reasoning_steps,
|
||||
"actions_trace": self.actions_trace,
|
||||
"json_output": self.json_output,
|
||||
"simple_output": self.simple_output,
|
||||
"show_parameters": self.show_parameters,
|
||||
"status_trace": self.status_trace,
|
||||
}
|
||||
|
||||
|
||||
@@ -857,9 +978,259 @@ SkillsParam = Union[List[str], SkillsConfig]
|
||||
AutonomyParam = Union[bool, Dict[str, Any], AutonomyConfig]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PRECEDENCE LADDER RESOLVERS
|
||||
# =============================================================================
|
||||
# Precedence: Instance > Config > Array > Dict > String > Bool > Default
|
||||
#
|
||||
# These resolvers normalize various input types into the canonical Config form.
|
||||
# This allows users to pass simplified inputs while maintaining type safety.
|
||||
|
||||
|
||||
def resolve_memory(value: MemoryParam) -> Optional[MemoryConfig]:
|
||||
"""
|
||||
Resolve memory= parameter following precedence ladder.
|
||||
|
||||
Precedence: Instance > Config > Dict > String > Bool > Default
|
||||
|
||||
Args:
|
||||
value: Memory parameter in any supported form
|
||||
|
||||
Returns:
|
||||
MemoryConfig if enabled, None if disabled
|
||||
|
||||
Examples:
|
||||
>>> resolve_memory(None) # Default: disabled
|
||||
None
|
||||
>>> resolve_memory(False) # Explicit disable
|
||||
None
|
||||
>>> resolve_memory(True) # Enable with defaults
|
||||
MemoryConfig(backend='file')
|
||||
>>> resolve_memory("redis") # String shorthand
|
||||
MemoryConfig(backend='redis')
|
||||
>>> resolve_memory({"backend": "sqlite", "user_id": "alice"}) # Dict
|
||||
MemoryConfig(backend='sqlite', user_id='alice')
|
||||
>>> resolve_memory(MemoryConfig(backend="postgres")) # Config passthrough
|
||||
MemoryConfig(backend='postgres')
|
||||
"""
|
||||
# Default: disabled
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Bool: False = disabled, True = defaults
|
||||
if value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return MemoryConfig()
|
||||
|
||||
# String: backend shorthand
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
backend = MemoryBackend(value.lower())
|
||||
except ValueError:
|
||||
backend = value # Allow custom backend strings
|
||||
return MemoryConfig(backend=backend)
|
||||
|
||||
# Dict: expand to config
|
||||
if isinstance(value, dict):
|
||||
# Handle backend enum conversion
|
||||
backend = value.get("backend", MemoryBackend.FILE)
|
||||
if isinstance(backend, str):
|
||||
try:
|
||||
backend = MemoryBackend(backend.lower())
|
||||
except ValueError:
|
||||
pass # Keep as string for custom backends
|
||||
return MemoryConfig(
|
||||
backend=backend,
|
||||
user_id=value.get("user_id"),
|
||||
session_id=value.get("session_id"),
|
||||
auto_memory=value.get("auto_memory", False),
|
||||
claude_memory=value.get("claude_memory", False),
|
||||
db=value.get("db"),
|
||||
config=value.get("config"),
|
||||
)
|
||||
|
||||
# Config: passthrough
|
||||
if isinstance(value, MemoryConfig):
|
||||
return value
|
||||
|
||||
# Instance (MemoryManager): return as-is (caller handles)
|
||||
# This is the highest precedence - user provided a pre-configured instance
|
||||
return value
|
||||
|
||||
|
||||
def resolve_knowledge(value: KnowledgeParam) -> Optional[KnowledgeConfig]:
|
||||
"""
|
||||
Resolve knowledge= parameter following precedence ladder.
|
||||
|
||||
Precedence: Instance > Config > Array > Dict > String > Bool > Default
|
||||
|
||||
Args:
|
||||
value: Knowledge parameter in any supported form
|
||||
|
||||
Returns:
|
||||
KnowledgeConfig if enabled, None if disabled
|
||||
"""
|
||||
# Default: disabled
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Bool: False = disabled, True = defaults
|
||||
if value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return KnowledgeConfig()
|
||||
|
||||
# String: single source
|
||||
if isinstance(value, str):
|
||||
return KnowledgeConfig(sources=[value])
|
||||
|
||||
# Array: list of sources
|
||||
if isinstance(value, list):
|
||||
return KnowledgeConfig(sources=value)
|
||||
|
||||
# Dict: expand to config
|
||||
if isinstance(value, dict):
|
||||
return KnowledgeConfig(
|
||||
sources=value.get("sources", []),
|
||||
embedder=value.get("embedder", "openai"),
|
||||
embedder_config=value.get("embedder_config"),
|
||||
chunking_strategy=value.get("chunking_strategy", ChunkingStrategy.SEMANTIC),
|
||||
chunk_size=value.get("chunk_size", 1000),
|
||||
chunk_overlap=value.get("chunk_overlap", 200),
|
||||
retrieval_k=value.get("retrieval_k", 5),
|
||||
retrieval_threshold=value.get("retrieval_threshold", 0.0),
|
||||
rerank=value.get("rerank", False),
|
||||
rerank_model=value.get("rerank_model"),
|
||||
auto_retrieve=value.get("auto_retrieve", True),
|
||||
)
|
||||
|
||||
# Config: passthrough
|
||||
if isinstance(value, KnowledgeConfig):
|
||||
return value
|
||||
|
||||
# Instance: return as-is
|
||||
return value
|
||||
|
||||
|
||||
def resolve_planning(value: PlanningParam) -> Optional[PlanningConfig]:
|
||||
"""Resolve planning= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return PlanningConfig()
|
||||
if isinstance(value, dict):
|
||||
return PlanningConfig(**value)
|
||||
if isinstance(value, PlanningConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_reflection(value: ReflectionParam) -> Optional[ReflectionConfig]:
|
||||
"""Resolve reflection= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return ReflectionConfig()
|
||||
if isinstance(value, dict):
|
||||
return ReflectionConfig(**value)
|
||||
if isinstance(value, ReflectionConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_guardrails(value: GuardrailParam) -> Optional[GuardrailConfig]:
|
||||
"""Resolve guardrails= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return GuardrailConfig()
|
||||
if callable(value):
|
||||
return GuardrailConfig(validator=value)
|
||||
if isinstance(value, dict):
|
||||
return GuardrailConfig(**value)
|
||||
if isinstance(value, GuardrailConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_web(value: WebParam) -> Optional[WebConfig]:
|
||||
"""Resolve web= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return WebConfig()
|
||||
if isinstance(value, dict):
|
||||
return WebConfig(**value)
|
||||
if isinstance(value, WebConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_output(value: OutputParam) -> Optional[OutputConfig]:
|
||||
"""Resolve output= parameter following precedence ladder."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
preset = OutputPreset(value.lower())
|
||||
return OutputConfig.from_preset(preset)
|
||||
except ValueError:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
return OutputConfig(**value)
|
||||
if isinstance(value, OutputConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_execution(value: ExecutionParam) -> Optional[ExecutionConfig]:
|
||||
"""Resolve execution= parameter following precedence ladder."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
preset = ExecutionPreset(value.lower())
|
||||
return ExecutionConfig.from_preset(preset)
|
||||
except ValueError:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
return ExecutionConfig(**value)
|
||||
if isinstance(value, ExecutionConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_caching(value: CachingParam) -> Optional[CachingConfig]:
|
||||
"""Resolve caching= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return CachingConfig()
|
||||
if isinstance(value, dict):
|
||||
return CachingConfig(**value)
|
||||
if isinstance(value, CachingConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def resolve_autonomy(value: AutonomyParam) -> Optional[AutonomyConfig]:
|
||||
"""Resolve autonomy= parameter following precedence ladder."""
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if value is True:
|
||||
return AutonomyConfig()
|
||||
if isinstance(value, dict):
|
||||
return AutonomyConfig(**value)
|
||||
if isinstance(value, AutonomyConfig):
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
__all__ = [
|
||||
# Enums
|
||||
"MemoryBackend",
|
||||
"LearnScope",
|
||||
"ChunkingStrategy",
|
||||
"GuardrailAction",
|
||||
"WebSearchProvider",
|
||||
@@ -867,6 +1238,7 @@ __all__ = [
|
||||
"ExecutionPreset",
|
||||
"AutonomyLevel",
|
||||
# Config classes (Agent)
|
||||
"LearnConfig",
|
||||
"MemoryConfig",
|
||||
"KnowledgeConfig",
|
||||
"PlanningConfig",
|
||||
@@ -900,4 +1272,15 @@ __all__ = [
|
||||
"HooksParam",
|
||||
"SkillsParam",
|
||||
"AutonomyParam",
|
||||
# Precedence ladder resolvers
|
||||
"resolve_memory",
|
||||
"resolve_knowledge",
|
||||
"resolve_planning",
|
||||
"resolve_reflection",
|
||||
"resolve_guardrails",
|
||||
"resolve_web",
|
||||
"resolve_output",
|
||||
"resolve_execution",
|
||||
"resolve_caching",
|
||||
"resolve_autonomy",
|
||||
]
|
||||
@@ -39,42 +39,74 @@ MEMORY_URL_SCHEMES: Dict[str, str] = {
|
||||
# =============================================================================
|
||||
# Output Presets
|
||||
# =============================================================================
|
||||
#
|
||||
# Preset Hierarchy (from least to most output):
|
||||
# silent → Nothing (default for SDK, max performance)
|
||||
# text → Response only, simple format
|
||||
# verbose → Task + Tools inline + Response panel
|
||||
# debug → verbose + metrics footer (tokens, cost, model)
|
||||
# stream → Real-time token streaming
|
||||
# json → Machine-readable JSONL events
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
OUTPUT_PRESETS: Dict[str, Dict[str, Any]] = {
|
||||
"minimal": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
"normal": {
|
||||
"verbose": True,
|
||||
"markdown": True,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
"verbose": {
|
||||
"verbose": True,
|
||||
"markdown": True,
|
||||
"stream": False,
|
||||
"metrics": True,
|
||||
"reasoning_steps": True,
|
||||
},
|
||||
"debug": {
|
||||
"verbose": True,
|
||||
"markdown": True,
|
||||
"stream": False,
|
||||
"metrics": True,
|
||||
"reasoning_steps": True,
|
||||
},
|
||||
# Silent preset - DEFAULT for SDK
|
||||
# Zero overhead, no callbacks, no I/O on hot path
|
||||
# Fastest performance for programmatic use
|
||||
"silent": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": False,
|
||||
},
|
||||
# Status preset - Shows tool calls and response without timestamps
|
||||
# Simple progress indicator: ▸ tool → result ✓
|
||||
"status": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": True,
|
||||
"simple_output": True, # Simple format without timestamps
|
||||
},
|
||||
# Trace preset - Full execution trace with timestamps
|
||||
# Shows: [HH:MM:SS] ▸ tool → result [0.2s] ✓
|
||||
# Ideal for debugging and monitoring
|
||||
"trace": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": True,
|
||||
"status_trace": True, # Enable timestamped status output
|
||||
},
|
||||
# Verbose preset - Full interactive output with panels
|
||||
# Shows: Task prompt, Tool calls (inline), Response panel
|
||||
# Best for interactive terminal use
|
||||
"verbose": {
|
||||
"verbose": True,
|
||||
"markdown": True,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
# Debug preset - trace + metrics (NO boxes)
|
||||
# Shows: [timestamp] tool calls, metrics footer, reasoning steps
|
||||
# Best for developers debugging agent behavior
|
||||
"debug": {
|
||||
"verbose": False, # No boxes
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": True, # Shows metrics footer
|
||||
"reasoning_steps": True,
|
||||
"actions_trace": True,
|
||||
"status_trace": True, # Timestamps like trace
|
||||
"show_parameters": True,
|
||||
},
|
||||
# Streaming preset - enables streaming by default
|
||||
"stream": {
|
||||
@@ -84,8 +116,73 @@ OUTPUT_PRESETS: Dict[str, Dict[str, Any]] = {
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
# JSON preset - JSONL output for piping/scripting
|
||||
"json": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": True,
|
||||
"json_output": True, # Output as JSONL
|
||||
},
|
||||
|
||||
# ==========================================================================
|
||||
# ALIASES (for backward compatibility)
|
||||
# ==========================================================================
|
||||
# plain → silent (identical behavior)
|
||||
"plain": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": False,
|
||||
},
|
||||
# minimal → silent (identical behavior)
|
||||
"minimal": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
# normal → verbose (consolidated)
|
||||
"normal": {
|
||||
"verbose": True,
|
||||
"markdown": True,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
},
|
||||
# actions → status (renamed for clarity)
|
||||
"actions": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": True,
|
||||
"simple_output": True,
|
||||
},
|
||||
# text → status (old name, kept for backward compat)
|
||||
"text": {
|
||||
"verbose": False,
|
||||
"markdown": False,
|
||||
"stream": False,
|
||||
"metrics": False,
|
||||
"reasoning_steps": False,
|
||||
"actions_trace": True,
|
||||
"simple_output": True,
|
||||
},
|
||||
}
|
||||
|
||||
# Default output mode - can be overridden by PRAISONAI_OUTPUT env var
|
||||
# "silent" = zero overhead, fastest performance (DEFAULT)
|
||||
# "verbose" = full interactive display
|
||||
# "debug" = verbose + metrics
|
||||
DEFAULT_OUTPUT_MODE = "silent"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Execution Presets
|
||||
|
||||
@@ -157,6 +157,16 @@ __all__ = [
|
||||
"FastContextResult",
|
||||
"FileMatch",
|
||||
"LineRange",
|
||||
# Artifacts (Dynamic Context Discovery)
|
||||
"ArtifactRef",
|
||||
"ArtifactMetadata",
|
||||
"GrepMatch",
|
||||
"QueueConfig",
|
||||
"ArtifactStoreProtocol",
|
||||
"HistoryStoreProtocol",
|
||||
"TerminalLoggerProtocol",
|
||||
"compute_checksum",
|
||||
"generate_summary",
|
||||
]
|
||||
|
||||
|
||||
@@ -252,4 +262,11 @@ def __getattr__(name: str):
|
||||
from praisonaiagents.context.fast.result import LineRange
|
||||
return LineRange
|
||||
|
||||
# Artifacts (Dynamic Context Discovery)
|
||||
if name in ("ArtifactRef", "ArtifactMetadata", "GrepMatch", "QueueConfig",
|
||||
"ArtifactStoreProtocol", "HistoryStoreProtocol", "TerminalLoggerProtocol",
|
||||
"compute_checksum", "generate_summary"):
|
||||
from . import artifacts
|
||||
return getattr(artifacts, name)
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,580 @@
|
||||
"""
|
||||
Artifact Types and Protocols for Dynamic Context Discovery.
|
||||
|
||||
This module provides lightweight types and protocols for artifact-based
|
||||
context management. Implementations live in the wrapper (praisonai).
|
||||
|
||||
Zero Performance Impact:
|
||||
- Pure dataclasses and protocols
|
||||
- No heavy imports
|
||||
- No I/O at import time
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.context.artifacts import ArtifactRef, ArtifactStoreProtocol
|
||||
|
||||
# Create a reference to a stored artifact
|
||||
ref = ArtifactRef(
|
||||
path="/path/to/artifact.json",
|
||||
summary="API response with 50,000 records",
|
||||
size_bytes=1_200_000,
|
||||
mime_type="application/json",
|
||||
)
|
||||
|
||||
# Implement the protocol in wrapper
|
||||
class FileSystemArtifactStore:
|
||||
def store(self, content, metadata): ...
|
||||
def load(self, ref): ...
|
||||
"""
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional, Protocol, Union, runtime_checkable
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArtifactRef:
|
||||
"""
|
||||
Reference to a stored artifact.
|
||||
|
||||
Used to replace large tool outputs in context with a lightweight pointer.
|
||||
The agent can then use tail/grep/load operations to access content on demand.
|
||||
|
||||
Attributes:
|
||||
path: Absolute path to the artifact file
|
||||
summary: Brief description or first ~200 chars of content
|
||||
size_bytes: Original content size in bytes
|
||||
mime_type: Content type (e.g., "application/json", "text/plain")
|
||||
checksum: SHA256 hash for integrity verification
|
||||
created_at: Unix timestamp when artifact was created
|
||||
artifact_id: Unique identifier for the artifact
|
||||
agent_id: ID of the agent that created this artifact
|
||||
run_id: ID of the run/session this artifact belongs to
|
||||
tool_name: Name of the tool that produced this output (if applicable)
|
||||
turn_id: Conversation turn number when artifact was created
|
||||
"""
|
||||
path: str
|
||||
summary: str
|
||||
size_bytes: int
|
||||
mime_type: str = "application/octet-stream"
|
||||
checksum: str = ""
|
||||
created_at: float = field(default_factory=time.time)
|
||||
artifact_id: str = ""
|
||||
agent_id: str = ""
|
||||
run_id: str = ""
|
||||
tool_name: Optional[str] = None
|
||||
turn_id: int = 0
|
||||
|
||||
def to_inline(self) -> str:
|
||||
"""
|
||||
Generate inline representation for context.
|
||||
|
||||
Returns a compact string that can be included in the conversation
|
||||
context, allowing the agent to reference the artifact.
|
||||
"""
|
||||
size_str = self._format_size(self.size_bytes)
|
||||
tool_info = f" from {self.tool_name}" if self.tool_name else ""
|
||||
return f"[Artifact{tool_info}: {self.path} ({size_str}) - {self.summary}]"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for serialization."""
|
||||
return {
|
||||
"path": self.path,
|
||||
"summary": self.summary,
|
||||
"size_bytes": self.size_bytes,
|
||||
"mime_type": self.mime_type,
|
||||
"checksum": self.checksum,
|
||||
"created_at": self.created_at,
|
||||
"artifact_id": self.artifact_id,
|
||||
"agent_id": self.agent_id,
|
||||
"run_id": self.run_id,
|
||||
"tool_name": self.tool_name,
|
||||
"turn_id": self.turn_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "ArtifactRef":
|
||||
"""Create from dictionary."""
|
||||
return cls(
|
||||
path=data.get("path", ""),
|
||||
summary=data.get("summary", ""),
|
||||
size_bytes=data.get("size_bytes", 0),
|
||||
mime_type=data.get("mime_type", "application/octet-stream"),
|
||||
checksum=data.get("checksum", ""),
|
||||
created_at=data.get("created_at", time.time()),
|
||||
artifact_id=data.get("artifact_id", ""),
|
||||
agent_id=data.get("agent_id", ""),
|
||||
run_id=data.get("run_id", ""),
|
||||
tool_name=data.get("tool_name"),
|
||||
turn_id=data.get("turn_id", 0),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _format_size(size_bytes: int) -> str:
|
||||
"""Format size in human-readable form."""
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
elif size_bytes < 1024 * 1024:
|
||||
return f"{size_bytes / 1024:.1f} KB"
|
||||
elif size_bytes < 1024 * 1024 * 1024:
|
||||
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
||||
else:
|
||||
return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArtifactMetadata:
|
||||
"""
|
||||
Metadata for artifact creation and tracking.
|
||||
|
||||
Passed to ArtifactStore.store() to provide context about the artifact.
|
||||
"""
|
||||
agent_id: str = ""
|
||||
run_id: str = ""
|
||||
tool_name: Optional[str] = None
|
||||
turn_id: int = 0
|
||||
content_type: str = "auto" # auto, json, text, binary
|
||||
tags: List[str] = field(default_factory=list)
|
||||
extra: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"agent_id": self.agent_id,
|
||||
"run_id": self.run_id,
|
||||
"tool_name": self.tool_name,
|
||||
"turn_id": self.turn_id,
|
||||
"content_type": self.content_type,
|
||||
"tags": self.tags,
|
||||
"extra": self.extra,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class GrepMatch:
|
||||
"""A single match from grep operation."""
|
||||
line_number: int
|
||||
line_content: str
|
||||
context_before: List[str] = field(default_factory=list)
|
||||
context_after: List[str] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"line_number": self.line_number,
|
||||
"line_content": self.line_content,
|
||||
"context_before": self.context_before,
|
||||
"context_after": self.context_after,
|
||||
}
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ArtifactStoreProtocol(Protocol):
|
||||
"""
|
||||
Protocol for artifact storage backends.
|
||||
|
||||
Implementations should handle:
|
||||
- Storing content to persistent storage
|
||||
- Loading content back
|
||||
- Efficient tail/grep operations for large files
|
||||
- Checksum verification
|
||||
- Secret redaction (if enabled)
|
||||
|
||||
Example implementation in wrapper:
|
||||
class FileSystemArtifactStore:
|
||||
def store(self, content, metadata):
|
||||
# Write to ~/.praison/runs/{run_id}/artifacts/
|
||||
...
|
||||
"""
|
||||
|
||||
def store(
|
||||
self,
|
||||
content: Any,
|
||||
metadata: ArtifactMetadata,
|
||||
) -> ArtifactRef:
|
||||
"""
|
||||
Store content as an artifact.
|
||||
|
||||
Args:
|
||||
content: The content to store (will be serialized)
|
||||
metadata: Metadata about the artifact
|
||||
|
||||
Returns:
|
||||
ArtifactRef pointing to the stored artifact
|
||||
"""
|
||||
...
|
||||
|
||||
def load(self, ref: ArtifactRef) -> Any:
|
||||
"""
|
||||
Load full content from an artifact.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
|
||||
Returns:
|
||||
The deserialized content
|
||||
"""
|
||||
...
|
||||
|
||||
def tail(self, ref: ArtifactRef, lines: int = 50) -> str:
|
||||
"""
|
||||
Get the last N lines of an artifact.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
lines: Number of lines to return
|
||||
|
||||
Returns:
|
||||
String containing the last N lines
|
||||
"""
|
||||
...
|
||||
|
||||
def head(self, ref: ArtifactRef, lines: int = 50) -> str:
|
||||
"""
|
||||
Get the first N lines of an artifact.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
lines: Number of lines to return
|
||||
|
||||
Returns:
|
||||
String containing the first N lines
|
||||
"""
|
||||
...
|
||||
|
||||
def grep(
|
||||
self,
|
||||
ref: ArtifactRef,
|
||||
pattern: str,
|
||||
context_lines: int = 2,
|
||||
max_matches: int = 50,
|
||||
) -> List[GrepMatch]:
|
||||
"""
|
||||
Search for pattern in artifact content.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
pattern: Regex pattern to search for
|
||||
context_lines: Number of context lines before/after match
|
||||
max_matches: Maximum number of matches to return
|
||||
|
||||
Returns:
|
||||
List of GrepMatch objects
|
||||
"""
|
||||
...
|
||||
|
||||
def chunk(
|
||||
self,
|
||||
ref: ArtifactRef,
|
||||
start_line: int = 1,
|
||||
end_line: Optional[int] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Get a chunk of lines from an artifact.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
start_line: Starting line number (1-indexed)
|
||||
end_line: Ending line number (inclusive), None for end of file
|
||||
|
||||
Returns:
|
||||
String containing the requested lines
|
||||
"""
|
||||
...
|
||||
|
||||
def delete(self, ref: ArtifactRef) -> bool:
|
||||
"""
|
||||
Delete an artifact.
|
||||
|
||||
Args:
|
||||
ref: Reference to the artifact
|
||||
|
||||
Returns:
|
||||
True if deleted successfully
|
||||
"""
|
||||
...
|
||||
|
||||
def list_artifacts(
|
||||
self,
|
||||
run_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
tool_name: Optional[str] = None,
|
||||
) -> List[ArtifactRef]:
|
||||
"""
|
||||
List artifacts matching filters.
|
||||
|
||||
Args:
|
||||
run_id: Filter by run ID
|
||||
agent_id: Filter by agent ID
|
||||
tool_name: Filter by tool name
|
||||
|
||||
Returns:
|
||||
List of matching ArtifactRef objects
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class HistoryStoreProtocol(Protocol):
|
||||
"""
|
||||
Protocol for conversation history storage.
|
||||
|
||||
Enables loss recovery during summarization by persisting
|
||||
full conversation history to artifacts.
|
||||
"""
|
||||
|
||||
def append(
|
||||
self,
|
||||
message: Dict[str, Any],
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
) -> None:
|
||||
"""
|
||||
Append a message to history.
|
||||
|
||||
Args:
|
||||
message: The message dict (role, content, etc.)
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
"""
|
||||
...
|
||||
|
||||
def get_ref(self, agent_id: str, run_id: str) -> Optional[ArtifactRef]:
|
||||
"""
|
||||
Get artifact reference for history file.
|
||||
|
||||
Args:
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
|
||||
Returns:
|
||||
ArtifactRef if history exists, None otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
max_results: int = 20,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search history for matching messages.
|
||||
|
||||
Args:
|
||||
query: Search query (supports regex)
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
max_results: Maximum results to return
|
||||
|
||||
Returns:
|
||||
List of matching message dicts
|
||||
"""
|
||||
...
|
||||
|
||||
def get_messages(
|
||||
self,
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
start_turn: int = 0,
|
||||
end_turn: Optional[int] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get messages from history.
|
||||
|
||||
Args:
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
start_turn: Starting turn number
|
||||
end_turn: Ending turn number (None for all)
|
||||
|
||||
Returns:
|
||||
List of message dicts
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class TerminalLoggerProtocol(Protocol):
|
||||
"""
|
||||
Protocol for terminal session logging.
|
||||
|
||||
Captures shell command outputs to artifacts for later retrieval.
|
||||
"""
|
||||
|
||||
def log_command(
|
||||
self,
|
||||
command: str,
|
||||
output: str,
|
||||
exit_code: int,
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
) -> ArtifactRef:
|
||||
"""
|
||||
Log a command execution.
|
||||
|
||||
Args:
|
||||
command: The command that was executed
|
||||
output: Combined stdout/stderr output
|
||||
exit_code: Command exit code
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
|
||||
Returns:
|
||||
ArtifactRef to the logged output
|
||||
"""
|
||||
...
|
||||
|
||||
def get_session_ref(self, agent_id: str, run_id: str) -> Optional[ArtifactRef]:
|
||||
"""
|
||||
Get artifact reference for terminal session log.
|
||||
|
||||
Args:
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
|
||||
Returns:
|
||||
ArtifactRef if session exists, None otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
def tail_session(
|
||||
self,
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
lines: int = 100,
|
||||
) -> str:
|
||||
"""
|
||||
Get last N lines from terminal session.
|
||||
|
||||
Args:
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
lines: Number of lines to return
|
||||
|
||||
Returns:
|
||||
String containing the last N lines
|
||||
"""
|
||||
...
|
||||
|
||||
def grep_session(
|
||||
self,
|
||||
pattern: str,
|
||||
agent_id: str,
|
||||
run_id: str,
|
||||
max_matches: int = 50,
|
||||
) -> List[GrepMatch]:
|
||||
"""
|
||||
Search terminal session for pattern.
|
||||
|
||||
Args:
|
||||
pattern: Regex pattern to search for
|
||||
agent_id: ID of the agent
|
||||
run_id: ID of the run/session
|
||||
max_matches: Maximum matches to return
|
||||
|
||||
Returns:
|
||||
List of GrepMatch objects
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@dataclass
|
||||
class QueueConfig:
|
||||
"""
|
||||
Configuration for tool output queuing.
|
||||
|
||||
Controls when and how large tool outputs are queued to artifacts
|
||||
instead of being included inline in the context.
|
||||
"""
|
||||
enabled: bool = True
|
||||
inline_max_bytes: int = 32 * 1024 # 32KB default
|
||||
redact_secrets: bool = True
|
||||
summary_max_chars: int = 200
|
||||
checksum_algorithm: str = "sha256"
|
||||
|
||||
# Secret patterns to redact (regex)
|
||||
secret_patterns: List[str] = field(default_factory=lambda: [
|
||||
r'(?i)(api[_-]?key|apikey)["\']?\s*[:=]\s*["\']?[\w\-]+',
|
||||
r'(?i)(secret|password|passwd|pwd)["\']?\s*[:=]\s*["\']?[\w\-]+',
|
||||
r'(?i)(token|bearer)["\']?\s*[:=]\s*["\']?[\w\-]+',
|
||||
r'(?i)(auth|authorization)["\']?\s*[:=]\s*["\']?[\w\-]+',
|
||||
r'sk-[a-zA-Z0-9]{20,}', # OpenAI keys
|
||||
r'ghp_[a-zA-Z0-9]{36}', # GitHub tokens
|
||||
r'gho_[a-zA-Z0-9]{36}', # GitHub OAuth
|
||||
r'xox[baprs]-[a-zA-Z0-9\-]+', # Slack tokens
|
||||
])
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"enabled": self.enabled,
|
||||
"inline_max_bytes": self.inline_max_bytes,
|
||||
"redact_secrets": self.redact_secrets,
|
||||
"summary_max_chars": self.summary_max_chars,
|
||||
"checksum_algorithm": self.checksum_algorithm,
|
||||
"secret_patterns": self.secret_patterns,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "QueueConfig":
|
||||
return cls(
|
||||
enabled=data.get("enabled", True),
|
||||
inline_max_bytes=data.get("inline_max_bytes", 32 * 1024),
|
||||
redact_secrets=data.get("redact_secrets", True),
|
||||
summary_max_chars=data.get("summary_max_chars", 200),
|
||||
checksum_algorithm=data.get("checksum_algorithm", "sha256"),
|
||||
secret_patterns=data.get("secret_patterns", cls().secret_patterns),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def compute_checksum(content: Union[str, bytes], algorithm: str = "sha256") -> str:
|
||||
"""
|
||||
Compute checksum of content.
|
||||
|
||||
Args:
|
||||
content: String or bytes to hash
|
||||
algorithm: Hash algorithm (sha256, md5, etc.)
|
||||
|
||||
Returns:
|
||||
Hex digest of the hash
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
content = content.encode("utf-8")
|
||||
|
||||
hasher = hashlib.new(algorithm)
|
||||
hasher.update(content)
|
||||
return hasher.hexdigest()
|
||||
|
||||
|
||||
def generate_summary(content: Any, max_chars: int = 200) -> str:
|
||||
"""
|
||||
Generate a brief summary of content.
|
||||
|
||||
Args:
|
||||
content: Content to summarize
|
||||
max_chars: Maximum characters in summary
|
||||
|
||||
Returns:
|
||||
Brief summary string
|
||||
"""
|
||||
if isinstance(content, (dict, list)):
|
||||
# For structured data, describe the structure
|
||||
if isinstance(content, dict):
|
||||
keys = list(content.keys())[:5]
|
||||
key_str = ", ".join(str(k) for k in keys)
|
||||
if len(content) > 5:
|
||||
key_str += f", ... ({len(content)} total keys)"
|
||||
return f"Dict with keys: {key_str}"[:max_chars]
|
||||
else:
|
||||
if len(content) > 0:
|
||||
first_type = type(content[0]).__name__
|
||||
return f"List of {len(content)} {first_type} items"[:max_chars]
|
||||
return "Empty list"
|
||||
|
||||
# For strings, take first N chars
|
||||
text = str(content)
|
||||
if len(text) <= max_chars:
|
||||
return text
|
||||
return text[:max_chars - 3] + "..."
|
||||
@@ -7,19 +7,35 @@ Visual display with agents in center and tools on sides.
|
||||
from typing import Dict, List, Set, Tuple
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.align import Align
|
||||
from rich.columns import Columns
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
|
||||
# Lazy imports for rich components
|
||||
_rich_cache = {}
|
||||
|
||||
def _get_rich_components():
|
||||
"""Lazy import all rich components."""
|
||||
if not _rich_cache:
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.align import Align
|
||||
from rich.columns import Columns
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
_rich_cache['Console'] = Console
|
||||
_rich_cache['Panel'] = Panel
|
||||
_rich_cache['Text'] = Text
|
||||
_rich_cache['Align'] = Align
|
||||
_rich_cache['Columns'] = Columns
|
||||
_rich_cache['Table'] = Table
|
||||
_rich_cache['box'] = box
|
||||
return _rich_cache
|
||||
|
||||
class FlowDisplay:
|
||||
"""Displays agent workflow with agents centered and tools on sides."""
|
||||
|
||||
def __init__(self):
|
||||
self.console = Console()
|
||||
rich = _get_rich_components()
|
||||
self.console = rich['Console']()
|
||||
self.agents = [] # List of agents in order
|
||||
self.agent_tools = defaultdict(list) # agent -> [tools]
|
||||
self.tracking = False
|
||||
@@ -98,6 +114,13 @@ class FlowDisplay:
|
||||
|
||||
def _display_agent_with_tools(self, agent: str):
|
||||
"""Display agent with tools on the sides."""
|
||||
rich = _get_rich_components()
|
||||
Table = rich['Table']
|
||||
Panel = rich['Panel']
|
||||
Text = rich['Text']
|
||||
Align = rich['Align']
|
||||
box = rich['box']
|
||||
|
||||
tools = self.agent_tools.get(agent, [])
|
||||
|
||||
if not tools:
|
||||
@@ -149,8 +172,13 @@ class FlowDisplay:
|
||||
|
||||
self.console.print(Align.center(Text("".join(arrow_parts))))
|
||||
|
||||
def _create_tools_panel(self, tools: List[str]) -> Panel:
|
||||
def _create_tools_panel(self, tools: List[str]):
|
||||
"""Create a panel for tools."""
|
||||
rich = _get_rich_components()
|
||||
Panel = rich['Panel']
|
||||
Text = rich['Text']
|
||||
box = rich['box']
|
||||
|
||||
if not tools:
|
||||
return ""
|
||||
|
||||
@@ -173,6 +201,12 @@ class FlowDisplay:
|
||||
|
||||
def _display_centered_node(self, label: str, color: str):
|
||||
"""Display a centered node."""
|
||||
rich = _get_rich_components()
|
||||
Panel = rich['Panel']
|
||||
Text = rich['Text']
|
||||
Align = rich['Align']
|
||||
box = rich['box']
|
||||
|
||||
panel = Panel(
|
||||
Text(label, style=f"white on {color}", justify="center"),
|
||||
style=f"white on {color}",
|
||||
@@ -183,6 +217,9 @@ class FlowDisplay:
|
||||
|
||||
def _display_arrow_down(self):
|
||||
"""Display a downward arrow."""
|
||||
rich = _get_rich_components()
|
||||
Align = rich['Align']
|
||||
|
||||
self.console.print()
|
||||
self.console.print(Align.center("↓"))
|
||||
self.console.print()
|
||||
|
||||
@@ -68,8 +68,8 @@ class LLM:
|
||||
MODEL_WINDOWS = {
|
||||
# OpenAI
|
||||
"gpt-4": 6144, # 8,192 actual
|
||||
"gpt-5-nano": 96000, # 128,000 actual
|
||||
"gpt-5-nano": 96000, # 128,000 actual
|
||||
"gpt-4o-mini": 96000, # 128,000 actual
|
||||
"gpt-4o-mini": 96000, # 128,000 actual
|
||||
"gpt-4-turbo": 96000, # 128,000 actual
|
||||
"o1-preview": 96000, # 128,000 actual
|
||||
"o1-mini": 96000, # 128,000 actual
|
||||
@@ -379,7 +379,7 @@ Respond with ONLY a valid JSON tool call in this format:
|
||||
base_url=self.base_url,
|
||||
verbose=self.verbose,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
reflection=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
reasoning_steps=self.reasoning_steps,
|
||||
@@ -1458,7 +1458,7 @@ Now provide your final answer using this result."""
|
||||
base_url=self.base_url,
|
||||
verbose=self.verbose,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
reflection=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
reasoning_steps=self.reasoning_steps
|
||||
@@ -1476,7 +1476,7 @@ Now provide your final answer using this result."""
|
||||
output_pydantic=output_pydantic,
|
||||
verbose=verbose,
|
||||
markdown=markdown,
|
||||
self_reflect=self_reflect,
|
||||
reflection=self_reflect,
|
||||
max_reflect=max_reflect,
|
||||
min_reflect=min_reflect,
|
||||
agent_name=agent_name,
|
||||
@@ -1540,6 +1540,7 @@ Now provide your final answer using this result."""
|
||||
max_iterations = 10 # Prevent infinite loops
|
||||
iteration_count = 0
|
||||
final_response_text = ""
|
||||
response_text = "" # Initialize to prevent UnboundLocalError on API errors
|
||||
stored_reasoning_content = None # Store reasoning content from tool execution
|
||||
accumulated_tool_results = [] # Store all tool results across iterations
|
||||
|
||||
@@ -1547,6 +1548,10 @@ Now provide your final answer using this result."""
|
||||
try:
|
||||
# Get response from LiteLLM
|
||||
current_time = time.time()
|
||||
|
||||
# Trigger LLM callback for subsequent iterations (after tool calls)
|
||||
# Initial callback is in agent._chat_completion, so this triggers for all loop iterations
|
||||
execute_sync_callback('llm_start', model=self.model, agent_name=agent_name)
|
||||
|
||||
# If reasoning_steps is True, do a single non-streaming call
|
||||
if reasoning_steps:
|
||||
@@ -1569,6 +1574,39 @@ Now provide your final answer using this result."""
|
||||
if self.metrics:
|
||||
self._track_token_usage(final_response, self.model)
|
||||
|
||||
# Trigger llm_end callback with metrics for debug output
|
||||
llm_latency_ms = (time.time() - current_time) * 1000
|
||||
|
||||
# Extract usage - handle both dict and ModelResponse object
|
||||
tokens_in = 0
|
||||
tokens_out = 0
|
||||
if isinstance(final_response, dict):
|
||||
usage = final_response.get("usage", {})
|
||||
tokens_in = usage.get("prompt_tokens", 0)
|
||||
tokens_out = usage.get("completion_tokens", 0)
|
||||
else:
|
||||
# ModelResponse object
|
||||
usage = getattr(final_response, 'usage', None)
|
||||
if usage:
|
||||
tokens_in = getattr(usage, 'prompt_tokens', 0) or 0
|
||||
tokens_out = getattr(usage, 'completion_tokens', 0) or 0
|
||||
|
||||
# Calculate cost if available
|
||||
llm_cost = None
|
||||
try:
|
||||
llm_cost = litellm.completion_cost(completion_response=final_response)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
execute_sync_callback(
|
||||
'llm_end',
|
||||
model=self.model,
|
||||
tokens_in=tokens_in,
|
||||
tokens_out=tokens_out,
|
||||
cost=llm_cost,
|
||||
latency_ms=llm_latency_ms
|
||||
)
|
||||
|
||||
# Execute callbacks and display based on verbose setting
|
||||
generation_time_val = time.time() - current_time
|
||||
response_content = f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}" if reasoning_content else response_text
|
||||
@@ -2154,6 +2192,16 @@ Now provide your final answer using this result."""
|
||||
|
||||
logging.debug(f"[TOOL_EXEC_DEBUG] About to display tool call with message: {display_message}")
|
||||
display_tool_call(display_message, console=self.console)
|
||||
|
||||
# Always trigger callback for status output (even when verbose=False)
|
||||
result_str = json.dumps(tool_result) if tool_result else "empty"
|
||||
execute_sync_callback(
|
||||
'tool_call',
|
||||
message=f"Calling function: {function_name}",
|
||||
tool_name=function_name,
|
||||
tool_input=arguments,
|
||||
tool_output=result_str[:200] if result_str else None
|
||||
)
|
||||
|
||||
# Check if this is Ollama provider
|
||||
if self._is_ollama_provider():
|
||||
@@ -2932,7 +2980,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
base_url=self.base_url,
|
||||
verbose=self.verbose,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
reflection=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
reasoning_steps=self.reasoning_steps
|
||||
@@ -2950,7 +2998,7 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
output_pydantic=output_pydantic,
|
||||
verbose=verbose,
|
||||
markdown=markdown,
|
||||
self_reflect=self_reflect,
|
||||
reflection=self_reflect,
|
||||
max_reflect=max_reflect,
|
||||
min_reflect=min_reflect,
|
||||
agent_name=agent_name,
|
||||
@@ -3636,12 +3684,14 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
self.session_token_metrics = self.session_token_metrics + metrics
|
||||
|
||||
# Track in global collector
|
||||
# Extract provider from model string (e.g., 'openai' from 'openai/gpt-4o-mini')
|
||||
provider = model.split('/')[0] if '/' in model else 'litellm'
|
||||
_token_collector.track_tokens(
|
||||
model=model,
|
||||
agent=self.current_agent_name,
|
||||
metrics=metrics,
|
||||
metadata={
|
||||
"provider": self.provider,
|
||||
"provider": provider,
|
||||
"stream": False
|
||||
}
|
||||
)
|
||||
@@ -3706,9 +3756,19 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
output_json = override_params.get('output_json')
|
||||
output_pydantic = override_params.get('output_pydantic')
|
||||
|
||||
# Always remove these from params as they're not native litellm parameters
|
||||
params.pop('output_json', None)
|
||||
params.pop('output_pydantic', None)
|
||||
# Always remove internal PraisonAI parameters that are not native litellm parameters
|
||||
# These are used internally by PraisonAI but should not be passed to the LLM API
|
||||
internal_params = [
|
||||
'output_json', 'output_pydantic', # Structured output handling
|
||||
'reflection', 'self_reflect', 'max_reflect', 'min_reflect', 'reflect_llm', # Self-reflection
|
||||
'verbose', 'markdown', # Display options
|
||||
'agent_name', 'agent_role', 'agent_tools', # Agent metadata
|
||||
'task_name', 'task_description', 'task_id', # Task metadata
|
||||
'execute_tool_fn', 'stream_callback', 'emit_events', # Callbacks
|
||||
'console', # Rich console
|
||||
]
|
||||
for param in internal_params:
|
||||
params.pop(param, None)
|
||||
|
||||
if output_json or output_pydantic:
|
||||
|
||||
@@ -3727,18 +3787,18 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
logging.debug(f"Using Gemini native structured output with schema: {json.dumps(schema, indent=2)}")
|
||||
|
||||
# Add tool_choice="auto" when tools are provided (unless already specified)
|
||||
# This encourages the model to use tools when appropriate
|
||||
if 'tools' in params and params['tools'] and 'tool_choice' not in params:
|
||||
# For Gemini models, use tool_choice to encourage tool usage
|
||||
if self._is_gemini_model():
|
||||
try:
|
||||
import litellm
|
||||
# Check if model supports function calling before setting tool_choice
|
||||
if litellm.supports_function_calling(model=self.model):
|
||||
params['tool_choice'] = 'auto'
|
||||
except Exception as e:
|
||||
# If check fails, still set tool_choice for known Gemini models
|
||||
logging.debug(f"Could not verify function calling support: {e}. Setting tool_choice anyway.")
|
||||
try:
|
||||
import litellm
|
||||
# Check if model supports function calling before setting tool_choice
|
||||
if litellm.supports_function_calling(model=self.model):
|
||||
params['tool_choice'] = 'auto'
|
||||
logging.debug(f"Set tool_choice='auto' for model {self.model}")
|
||||
except Exception as e:
|
||||
# If check fails, still set tool_choice to encourage tool usage
|
||||
logging.debug(f"Could not verify function calling support: {e}. Setting tool_choice='auto' anyway.")
|
||||
params['tool_choice'] = 'auto'
|
||||
|
||||
# Add web_search_options if web_search is enabled and model supports it
|
||||
if self.web_search and self._supports_web_search():
|
||||
|
||||
@@ -51,7 +51,7 @@ class ModelRouter:
|
||||
DEFAULT_MODELS = [
|
||||
# Lightweight/cheap models for simple tasks
|
||||
ModelProfile(
|
||||
name="gpt-5-nano",
|
||||
name="gpt-4o-mini",
|
||||
provider="openai",
|
||||
complexity_range=(TaskComplexity.SIMPLE, TaskComplexity.MODERATE),
|
||||
cost_per_1k_tokens=0.00075, # Average of $0.00015 input, $0.0006 output
|
||||
@@ -80,7 +80,7 @@ class ModelRouter:
|
||||
|
||||
# Mid-tier models for moderate complexity
|
||||
ModelProfile(
|
||||
name="gpt-5-nano",
|
||||
name="gpt-4o-mini",
|
||||
provider="openai",
|
||||
complexity_range=(TaskComplexity.MODERATE, TaskComplexity.COMPLEX),
|
||||
cost_per_1k_tokens=0.0075, # Average of $0.0025 input, $0.01 output
|
||||
@@ -145,7 +145,7 @@ class ModelRouter:
|
||||
preferred_providers: List of preferred providers in order
|
||||
"""
|
||||
self.models = models or self.DEFAULT_MODELS
|
||||
self.default_model = default_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
||||
self.default_model = default_model or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o-mini')
|
||||
self.cost_threshold = cost_threshold
|
||||
self.preferred_providers = preferred_providers or []
|
||||
|
||||
|
||||
@@ -10,15 +10,52 @@ import logging
|
||||
import time
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Any, Dict, List, Optional, Union, AsyncIterator, Iterator, Callable, Tuple
|
||||
from openai import OpenAI, AsyncOpenAI
|
||||
from openai.types.chat import ChatCompletionChunk
|
||||
from typing import Any, Dict, List, Optional, Union, AsyncIterator, Iterator, Callable, Tuple, TYPE_CHECKING
|
||||
from pydantic import BaseModel
|
||||
from dataclasses import dataclass
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
import inspect
|
||||
|
||||
# Lazy imports for optional dependencies
|
||||
_openai_module = None
|
||||
_rich_console = None
|
||||
_rich_live = None
|
||||
|
||||
def _get_openai():
|
||||
"""Lazy import openai module."""
|
||||
global _openai_module
|
||||
if _openai_module is None:
|
||||
try:
|
||||
import openai as _openai
|
||||
_openai_module = _openai
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"openai is required for OpenAI client. "
|
||||
"Install with: pip install openai"
|
||||
)
|
||||
return _openai_module
|
||||
|
||||
def _get_openai_classes():
|
||||
"""Get OpenAI and AsyncOpenAI classes lazily."""
|
||||
openai = _get_openai()
|
||||
return openai.OpenAI, openai.AsyncOpenAI
|
||||
|
||||
def _get_rich_console():
|
||||
"""Lazy import rich Console."""
|
||||
global _rich_console
|
||||
if _rich_console is None:
|
||||
from rich.console import Console
|
||||
_rich_console = Console
|
||||
return _rich_console
|
||||
|
||||
def _get_rich_live():
|
||||
"""Lazy import rich Live."""
|
||||
global _rich_live
|
||||
if _rich_live is None:
|
||||
from rich.live import Live
|
||||
_rich_live = Live
|
||||
return _rich_live
|
||||
|
||||
|
||||
# Import display_tool_call for callback support (lazy import to avoid circular imports)
|
||||
_display_tool_call = None
|
||||
def _get_display_tool_call():
|
||||
@@ -258,21 +295,23 @@ class OpenAIClient:
|
||||
def console(self):
|
||||
"""Lazily initialize Rich Console only when needed."""
|
||||
if self._console is None:
|
||||
from rich.console import Console
|
||||
Console = _get_rich_console()
|
||||
self._console = Console()
|
||||
return self._console
|
||||
|
||||
@property
|
||||
def sync_client(self) -> OpenAI:
|
||||
def sync_client(self):
|
||||
"""Get the synchronous OpenAI client (lazy initialization)."""
|
||||
if self._sync_client is None:
|
||||
OpenAI, _ = _get_openai_classes()
|
||||
self._sync_client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||
return self._sync_client
|
||||
|
||||
@property
|
||||
def async_client(self) -> AsyncOpenAI:
|
||||
def async_client(self):
|
||||
"""Get the asynchronous OpenAI client (lazy initialization)."""
|
||||
if self._async_client is None:
|
||||
_, AsyncOpenAI = _get_openai_classes()
|
||||
self._async_client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||
return self._async_client
|
||||
|
||||
@@ -579,7 +618,7 @@ class OpenAIClient:
|
||||
temperature: float = 1.0,
|
||||
tools: Optional[List[Dict]] = None,
|
||||
start_time: Optional[float] = None,
|
||||
console: Optional[Console] = None,
|
||||
console: Optional[Any] = None,
|
||||
display_fn: Optional[Callable] = None,
|
||||
reasoning_steps: bool = False,
|
||||
stream_callback: Optional[Callable] = None,
|
||||
@@ -649,9 +688,11 @@ class OpenAIClient:
|
||||
chunks = []
|
||||
first_token_emitted = False
|
||||
last_content_time = None
|
||||
ttft_logged = False
|
||||
|
||||
# If display function provided, use Live display
|
||||
if display_fn:
|
||||
Live = _get_rich_live()
|
||||
with Live(
|
||||
display_fn("", start_time),
|
||||
console=console,
|
||||
@@ -669,6 +710,12 @@ class OpenAIClient:
|
||||
full_response_text += content
|
||||
last_content_time = time.perf_counter()
|
||||
|
||||
# Log TTFT on first token
|
||||
if not ttft_logged:
|
||||
ttft_ms = (last_content_time - request_start_perf) * 1000
|
||||
logging.debug(f"TTFT (Time To First Token): {ttft_ms:.0f}ms")
|
||||
ttft_logged = True
|
||||
|
||||
# Emit FIRST_TOKEN on first content
|
||||
if _emit and not first_token_emitted:
|
||||
stream_callback(StreamEvent(
|
||||
@@ -715,18 +762,25 @@ class OpenAIClient:
|
||||
for chunk in response_stream:
|
||||
chunks.append(chunk)
|
||||
|
||||
if _emit and chunk.choices and chunk.choices[0].delta.content:
|
||||
if chunk.choices and chunk.choices[0].delta.content:
|
||||
content = chunk.choices[0].delta.content
|
||||
last_content_time = time.perf_counter()
|
||||
|
||||
if not first_token_emitted:
|
||||
# Log TTFT on first token
|
||||
if not ttft_logged:
|
||||
ttft_ms = (last_content_time - request_start_perf) * 1000
|
||||
logging.debug(f"TTFT (Time To First Token): {ttft_ms:.0f}ms")
|
||||
ttft_logged = True
|
||||
|
||||
if _emit and not first_token_emitted:
|
||||
stream_callback(StreamEvent(
|
||||
type=StreamEventType.FIRST_TOKEN,
|
||||
timestamp=last_content_time,
|
||||
content=content
|
||||
))
|
||||
first_token_emitted = True
|
||||
else:
|
||||
elif _emit:
|
||||
# Emit DELTA_TEXT for subsequent tokens
|
||||
stream_callback(StreamEvent(
|
||||
type=StreamEventType.DELTA_TEXT,
|
||||
timestamp=last_content_time,
|
||||
@@ -774,7 +828,7 @@ class OpenAIClient:
|
||||
temperature: float = 1.0,
|
||||
tools: Optional[List[Dict]] = None,
|
||||
start_time: Optional[float] = None,
|
||||
console: Optional[Console] = None,
|
||||
console: Optional[Any] = None,
|
||||
display_fn: Optional[Callable] = None,
|
||||
reasoning_steps: bool = False,
|
||||
stream_callback: Optional[Callable] = None,
|
||||
@@ -861,6 +915,7 @@ class OpenAIClient:
|
||||
|
||||
# If display function provided, use Live display
|
||||
if display_fn:
|
||||
Live = _get_rich_live()
|
||||
with Live(
|
||||
display_fn("", start_time),
|
||||
console=console,
|
||||
@@ -975,13 +1030,13 @@ class OpenAIClient:
|
||||
def create_completion(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
stream: bool = False,
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
) -> Union[Any, Iterator[ChatCompletionChunk]]:
|
||||
) -> Union[Any, Iterator[Any]]:
|
||||
"""
|
||||
Create a chat completion using the synchronous client.
|
||||
|
||||
@@ -1020,13 +1075,13 @@ class OpenAIClient:
|
||||
async def acreate_completion(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
stream: bool = False,
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
) -> Union[Any, AsyncIterator[ChatCompletionChunk]]:
|
||||
) -> Union[Any, AsyncIterator[Any]]:
|
||||
"""
|
||||
Create a chat completion using the asynchronous client.
|
||||
|
||||
@@ -1065,12 +1120,12 @@ class OpenAIClient:
|
||||
def chat_completion_with_tools(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
tools: Optional[List[Any]] = None,
|
||||
execute_tool_fn: Optional[Callable] = None,
|
||||
stream: bool = True,
|
||||
console: Optional[Console] = None,
|
||||
console: Optional[Any] = None,
|
||||
display_fn: Optional[Callable] = None,
|
||||
reasoning_steps: bool = False,
|
||||
verbose: bool = True,
|
||||
@@ -1112,6 +1167,10 @@ class OpenAIClient:
|
||||
iteration_count = 0
|
||||
|
||||
while iteration_count < max_iterations:
|
||||
# Trigger LLM callback for status/trace output
|
||||
from ..main import execute_sync_callback
|
||||
execute_sync_callback('llm_start', model=model, agent_name=None)
|
||||
|
||||
if stream:
|
||||
# Process as streaming response with formatted tools
|
||||
final_response = self.process_stream_response(
|
||||
@@ -1130,7 +1189,10 @@ class OpenAIClient:
|
||||
if display_fn and console:
|
||||
# When verbose (display_fn provided), use streaming for better UX
|
||||
try:
|
||||
with Live(display_fn("", start_time), console=console, refresh_per_second=4, transient=True) as live:
|
||||
request_start_perf = time.perf_counter()
|
||||
ttft_logged = False
|
||||
Live = _get_rich_live()
|
||||
with Live(display_fn("", start_time), console=console, refresh_per_second=10, transient=True) as live:
|
||||
# Use streaming when display_fn is provided for progressive display
|
||||
response_stream = self.create_completion(
|
||||
messages=messages,
|
||||
@@ -1148,6 +1210,11 @@ class OpenAIClient:
|
||||
for chunk in response_stream:
|
||||
chunks.append(chunk)
|
||||
if chunk.choices[0].delta.content:
|
||||
# Log TTFT on first token
|
||||
if not ttft_logged:
|
||||
ttft_ms = (time.perf_counter() - request_start_perf) * 1000
|
||||
logging.debug(f"TTFT (Time To First Token): {ttft_ms:.0f}ms")
|
||||
ttft_logged = True
|
||||
full_response_text += chunk.choices[0].delta.content
|
||||
live.update(display_fn(full_response_text, start_time))
|
||||
|
||||
@@ -1180,6 +1247,35 @@ class OpenAIClient:
|
||||
if not final_response:
|
||||
return None
|
||||
|
||||
# Trigger llm_end callback with metrics for debug output
|
||||
llm_end_time = time.perf_counter()
|
||||
llm_latency_ms = (llm_end_time - start_time) * 1000
|
||||
|
||||
# Extract usage info if available
|
||||
usage = getattr(final_response, 'usage', None)
|
||||
tokens_in = getattr(usage, 'prompt_tokens', 0) if usage else 0
|
||||
tokens_out = getattr(usage, 'completion_tokens', 0) if usage else 0
|
||||
|
||||
# Calculate cost if litellm available
|
||||
cost = None
|
||||
try:
|
||||
import litellm
|
||||
if hasattr(final_response, 'model_dump'):
|
||||
cost = litellm.completion_cost(completion_response=final_response.model_dump())
|
||||
elif isinstance(final_response, dict):
|
||||
cost = litellm.completion_cost(completion_response=final_response)
|
||||
except Exception:
|
||||
pass # Cost calculation is optional
|
||||
|
||||
execute_sync_callback(
|
||||
'llm_end',
|
||||
model=model,
|
||||
tokens_in=tokens_in,
|
||||
tokens_out=tokens_out,
|
||||
cost=cost,
|
||||
latency_ms=llm_latency_ms
|
||||
)
|
||||
|
||||
# Check for tool calls
|
||||
tool_calls = getattr(final_response.choices[0].message, 'tool_calls', None)
|
||||
|
||||
@@ -1215,17 +1311,19 @@ class OpenAIClient:
|
||||
|
||||
# Always trigger callback for tool call tracking (even when verbose=False)
|
||||
display_tool_call_fn = _get_display_tool_call()
|
||||
display_tool_call_fn(f"Calling function: {function_name}", console=console if verbose else None)
|
||||
|
||||
if verbose and console:
|
||||
console.print(f"[dim]Arguments:[/dim] {arguments}")
|
||||
|
||||
# Execute the tool
|
||||
tool_result = execute_tool_fn(function_name, arguments)
|
||||
results_str = json.dumps(tool_result) if tool_result else "Function returned an empty output"
|
||||
|
||||
# Trigger callback with result
|
||||
display_tool_call_fn(f"Function {function_name} returned: {results_str[:200]}{'...' if len(results_str) > 200 else ''}", console=console if verbose else None)
|
||||
# Trigger callback with structured parameters for status output
|
||||
display_tool_call_fn(
|
||||
f"Calling function: {function_name}",
|
||||
console=console if verbose else None,
|
||||
tool_name=function_name,
|
||||
tool_input=arguments,
|
||||
tool_output=results_str[:200] if results_str else None
|
||||
)
|
||||
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
@@ -1246,12 +1344,12 @@ class OpenAIClient:
|
||||
async def achat_completion_with_tools(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
tools: Optional[List[Any]] = None,
|
||||
execute_tool_fn: Optional[Callable] = None,
|
||||
stream: bool = True,
|
||||
console: Optional[Console] = None,
|
||||
console: Optional[Any] = None,
|
||||
display_fn: Optional[Callable] = None,
|
||||
reasoning_steps: bool = False,
|
||||
verbose: bool = True,
|
||||
@@ -1305,6 +1403,7 @@ class OpenAIClient:
|
||||
if display_fn and console:
|
||||
# When verbose (display_fn provided), use streaming for better UX
|
||||
try:
|
||||
Live = _get_rich_live()
|
||||
with Live(display_fn("", start_time), console=console, refresh_per_second=4, transient=True) as live:
|
||||
# Use streaming when display_fn is provided for progressive display
|
||||
response_stream = await self.acreate_completion(
|
||||
@@ -1430,7 +1529,7 @@ class OpenAIClient:
|
||||
def chat_completion_with_tools_stream(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
tools: Optional[List[Any]] = None,
|
||||
execute_tool_fn: Optional[Callable] = None,
|
||||
@@ -1582,7 +1681,7 @@ class OpenAIClient:
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
response_format: BaseModel,
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
**kwargs
|
||||
) -> Any:
|
||||
@@ -1616,7 +1715,7 @@ class OpenAIClient:
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
response_format: BaseModel,
|
||||
model: str = "gpt-5-nano",
|
||||
model: str = "gpt-4o-mini",
|
||||
temperature: float = 1.0,
|
||||
**kwargs
|
||||
) -> Any:
|
||||
|
||||
@@ -30,6 +30,53 @@ async_display_callbacks = {}
|
||||
# Global approval callback registry
|
||||
approval_callback = None
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PraisonAI Unique Color Palette: "Elegant Intelligence"
|
||||
# Creates a visual narrative flow: Agent → Task → Working → Response
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
PRAISON_COLORS = {
|
||||
# Agent identity - grounded, stable
|
||||
"agent": "#86A789", # Soft Sage Green
|
||||
"agent_text": "#D2E3C8", # Light sage for text
|
||||
|
||||
# Task/Question - input, attention-grabbing
|
||||
"task": "#FF9B9B", # Warm Coral
|
||||
"task_text": "#FFE5E5", # Light coral for text
|
||||
|
||||
# Working/Processing - action, energy
|
||||
"working": "#FFB347", # Amber
|
||||
"working_text": "#FFF3E0", # Light amber for text
|
||||
|
||||
# Response/Output - completion, calm
|
||||
"response": "#4A90D9", # Ocean Blue
|
||||
"response_text": "#E3F2FD", # Light blue for text
|
||||
|
||||
# Tool calls - special action
|
||||
"tool": "#9B7EDE", # Violet Accent
|
||||
"tool_text": "#EDE7F6", # Light violet for text
|
||||
|
||||
# Reasoning - thought process
|
||||
"reasoning": "#78909C", # Blue Gray
|
||||
"reasoning_text": "#ECEFF1", # Light gray for text
|
||||
|
||||
# Error/Warning - alert
|
||||
"error": "#E57373", # Alert Red
|
||||
"error_text": "#FFEBEE", # Light red for text
|
||||
|
||||
# Metrics - meta information
|
||||
"metrics": "#B4B4B3", # Cool Gray
|
||||
"metrics_text": "#FAFAFA", # Near white for text
|
||||
}
|
||||
|
||||
# Status animation frames for "Working" indicator
|
||||
WORKING_FRAMES = ["●○○", "○●○", "○○●", "○●○"]
|
||||
WORKING_PHASES = [
|
||||
"Analyzing query...",
|
||||
"Processing context...",
|
||||
"Generating response...",
|
||||
"Finalizing output...",
|
||||
]
|
||||
|
||||
# At the top of the file, add display_callbacks to __all__
|
||||
__all__ = [
|
||||
'error_logs',
|
||||
@@ -39,7 +86,19 @@ __all__ = [
|
||||
'async_display_callbacks',
|
||||
'execute_callback',
|
||||
'approval_callback',
|
||||
# ... other exports
|
||||
# Color palette and animation constants
|
||||
'PRAISON_COLORS',
|
||||
'WORKING_FRAMES',
|
||||
'WORKING_PHASES',
|
||||
# Display functions
|
||||
'display_interaction',
|
||||
'display_instruction',
|
||||
'display_tool_call',
|
||||
'display_error',
|
||||
'display_generating',
|
||||
'display_reasoning_steps',
|
||||
'display_working_status',
|
||||
'display_self_reflection',
|
||||
]
|
||||
|
||||
def register_display_callback(display_type: str, callback_fn, is_async: bool = False):
|
||||
@@ -149,10 +208,14 @@ def _clean_display_content(content: str, max_length: int = 20000) -> str:
|
||||
|
||||
return content.strip()
|
||||
|
||||
def display_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None):
|
||||
def display_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None, metrics=None):
|
||||
"""Synchronous version of display_interaction.
|
||||
|
||||
Displays the task/message and response in clean panels with response time in title.
|
||||
Displays the task/message and response in clean panels with semantic colors
|
||||
and optional metrics footer. Uses PraisonAI's unique color palette.
|
||||
|
||||
Args:
|
||||
metrics: Optional dict with token_in, token_out, cost, model for footer display
|
||||
"""
|
||||
if console is None:
|
||||
console = Console()
|
||||
@@ -180,20 +243,40 @@ def display_interaction(message, response, markdown=True, generation_time=None,
|
||||
agent_tools=agent_tools,
|
||||
task_name=task_name,
|
||||
task_description=task_description,
|
||||
task_id=task_id
|
||||
task_id=task_id,
|
||||
metrics=metrics
|
||||
)
|
||||
|
||||
# Build response title with time (Agno-style: "Response (1.2s)")
|
||||
# Build response title with time
|
||||
response_title = "Response"
|
||||
if generation_time:
|
||||
response_title = f"Response ({generation_time:.1f}s)"
|
||||
|
||||
# Build response content with optional metrics footer
|
||||
response_content = response
|
||||
if metrics and isinstance(metrics, dict):
|
||||
# Add dashed separator and compact metrics line
|
||||
tokens_in = metrics.get('tokens_in', 0)
|
||||
tokens_out = metrics.get('tokens_out', 0)
|
||||
cost = metrics.get('cost', 0)
|
||||
model = metrics.get('model', '')
|
||||
|
||||
metrics_line = f"\n\n─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\n📊 {tokens_in} tokens in · {tokens_out} out"
|
||||
if cost > 0:
|
||||
metrics_line += f" · ${cost:.4f}"
|
||||
if model:
|
||||
metrics_line += f" · {model}"
|
||||
response_content = response + metrics_line
|
||||
|
||||
# Task is inline (less visual weight), Response keeps panel (it's the main content)
|
||||
# Format: 📝 Task message
|
||||
task_prefix = "[bold #FF9B9B]📝[/]"
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title=response_title, border_style="cyan"))
|
||||
console.print(f"{task_prefix} {message}\n")
|
||||
console.print(Panel.fit(Markdown(response_content), title=response_title, border_style=PRAISON_COLORS["response"]))
|
||||
else:
|
||||
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_title, border_style="cyan"))
|
||||
console.print(f"{task_prefix} [bold {PRAISON_COLORS['task_text']}]{message}[/]\n")
|
||||
console.print(Panel.fit(Text(response_content, style=f"bold {PRAISON_COLORS['response_text']}"), title=response_title, border_style=PRAISON_COLORS["response"]))
|
||||
|
||||
def display_self_reflection(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
@@ -231,8 +314,11 @@ def display_instruction(message: str, console=None, agent_name: str = None, agen
|
||||
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, tool_name: str = None, tool_input: dict = None, tool_output: str = None):
|
||||
"""Display tool call information in a consolidated panel.
|
||||
def display_tool_call(message: str, console=None, tool_name: str = None, tool_input: dict = None, tool_output: str = None, elapsed_time: float = None, success: bool = True):
|
||||
"""Display tool call information in PraisonAI's unique timeline format.
|
||||
|
||||
Uses ▸ prefix, inline timing [X.Xs], and status icons ✓/✗ for a clean,
|
||||
scannable tool activity display.
|
||||
|
||||
Args:
|
||||
message: The tool call message (legacy format)
|
||||
@@ -240,6 +326,8 @@ def display_tool_call(message: str, console=None, tool_name: str = None, tool_in
|
||||
tool_name: Name of the tool being called
|
||||
tool_input: Input arguments to the tool
|
||||
tool_output: Output from the tool (if available)
|
||||
elapsed_time: Time taken for tool execution in seconds
|
||||
success: Whether the tool call succeeded
|
||||
"""
|
||||
logging.debug(f"display_tool_call called with message: {repr(message)}")
|
||||
if not message or not message.strip():
|
||||
@@ -251,19 +339,41 @@ def display_tool_call(message: str, console=None, tool_name: str = None, tool_in
|
||||
# Execute synchronous callbacks (always, even when console is None)
|
||||
execute_sync_callback('tool_call', message=message, tool_name=tool_name, tool_input=tool_input, tool_output=tool_output)
|
||||
|
||||
# Only print panel if console is provided (verbose mode)
|
||||
# Only print if console is provided (verbose mode)
|
||||
if console is not None:
|
||||
# Build consolidated tool call display (Agno-style)
|
||||
if tool_name and tool_input is not None:
|
||||
# Format: tool_name(arg1=val1, arg2=val2)
|
||||
args_str = ", ".join(f"{k}={v}" for k, v in tool_input.items()) if tool_input else ""
|
||||
tool_display = f"• {tool_name}({args_str})"
|
||||
# Build clean inline format - no panels, just prefixed lines
|
||||
if tool_name:
|
||||
# Format: ▸ tool_name(args) → result [X.Xs] ✓
|
||||
args_str = ""
|
||||
if tool_input:
|
||||
# Truncate long values for display
|
||||
args_parts = []
|
||||
for k, v in tool_input.items():
|
||||
v_str = str(v)
|
||||
if len(v_str) > 50:
|
||||
v_str = v_str[:47] + "..."
|
||||
args_parts.append(f"{k}={repr(v_str) if isinstance(v, str) else v_str}")
|
||||
args_str = ", ".join(args_parts)
|
||||
|
||||
# Build the inline entry
|
||||
status_icon = "[green]✓[/]" if success else "[red]✗[/]"
|
||||
time_str = f"[dim][{elapsed_time:.1f}s][/]" if elapsed_time else ""
|
||||
|
||||
# Base tool call line
|
||||
tool_line = f"[bold #86A789]▸[/] [#B4D4FF]{tool_name}[/]({args_str})"
|
||||
|
||||
# Add output inline with arrow if available
|
||||
if tool_output:
|
||||
tool_display += f"\n → {tool_output}"
|
||||
console.print(Panel.fit(Text(tool_display, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
output_str = str(tool_output)
|
||||
if len(output_str) > 80:
|
||||
output_str = output_str[:77] + "..."
|
||||
tool_line += f" [dim]→[/] [italic]{output_str}[/]"
|
||||
|
||||
tool_line += f" {time_str} {status_icon}"
|
||||
console.print(tool_line)
|
||||
else:
|
||||
# Legacy format
|
||||
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
# Legacy format - simple inline
|
||||
console.print(f"[bold #86A789]▸[/] {message}")
|
||||
|
||||
def display_error(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
@@ -275,7 +385,12 @@ def display_error(message: str, console=None):
|
||||
# Execute synchronous callbacks
|
||||
execute_sync_callback('error', message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold red"), title="Error", border_style="red"))
|
||||
# Use semantic error color
|
||||
console.print(Panel.fit(
|
||||
Text(message, style=f"bold {PRAISON_COLORS['error_text']}"),
|
||||
title="⚠ Error",
|
||||
border_style=PRAISON_COLORS["error"]
|
||||
))
|
||||
error_logs.append(message)
|
||||
|
||||
def display_generating(content: str = "", start_time: Optional[float] = None):
|
||||
@@ -293,7 +408,73 @@ def display_generating(content: str = "", start_time: Optional[float] = None):
|
||||
# Execute synchronous callbacks
|
||||
execute_sync_callback('generating', content=content, elapsed_time=elapsed_str.strip() if elapsed_str else None)
|
||||
|
||||
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style="green")
|
||||
# Use semantic response color
|
||||
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style=PRAISON_COLORS["response"])
|
||||
|
||||
def display_reasoning_steps(steps: List[str], console=None):
|
||||
"""Display reasoning steps with unique numbered circles.
|
||||
|
||||
Uses ①②③ numbered circles for a distinctive, scannable format
|
||||
that shows the agent's thought process.
|
||||
|
||||
Args:
|
||||
steps: List of reasoning step descriptions
|
||||
console: Rich console for output
|
||||
"""
|
||||
if not steps:
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
|
||||
# Circle number mapping (supports up to 20 steps)
|
||||
circle_numbers = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||
|
||||
# Build reasoning display with numbered circles
|
||||
reasoning_lines = []
|
||||
for i, step in enumerate(steps):
|
||||
circle = circle_numbers[i] if i < len(circle_numbers) else f"({i+1})"
|
||||
step_text = _clean_display_content(str(step))
|
||||
if len(step_text) > 80:
|
||||
step_text = step_text[:77] + "..."
|
||||
reasoning_lines.append(f"{circle} {step_text}")
|
||||
|
||||
reasoning_display = "\n".join(reasoning_lines)
|
||||
|
||||
console.print(Panel.fit(
|
||||
Text(reasoning_display, style=f"italic {PRAISON_COLORS['reasoning_text']}"),
|
||||
title="Reasoning",
|
||||
border_style=PRAISON_COLORS["reasoning"]
|
||||
))
|
||||
|
||||
def display_working_status(phase: int = 0, status_text: str = None, console=None):
|
||||
"""Display animated working status with pulsing dots.
|
||||
|
||||
Shows a unique "Working ●○○" indicator with phase-specific status.
|
||||
This is PraisonAI's distinctive approach to showing processing status.
|
||||
|
||||
Args:
|
||||
phase: Current animation phase (0-3)
|
||||
status_text: Optional status description
|
||||
console: Rich console for output
|
||||
|
||||
Returns:
|
||||
Panel object for use with Rich.Live
|
||||
"""
|
||||
if console is None:
|
||||
console = Console()
|
||||
|
||||
# Get current frame and phase text
|
||||
frame = WORKING_FRAMES[phase % len(WORKING_FRAMES)]
|
||||
phase_text = status_text or WORKING_PHASES[phase % len(WORKING_PHASES)]
|
||||
|
||||
# Build working status display
|
||||
working_display = f"Working {frame} {phase_text}"
|
||||
|
||||
return Panel.fit(
|
||||
Text(working_display, style=f"bold {PRAISON_COLORS['working_text']}"),
|
||||
title="Status",
|
||||
border_style=PRAISON_COLORS["working"]
|
||||
)
|
||||
|
||||
# Async versions with 'a' prefix
|
||||
async def adisplay_interaction(message, response, markdown=True, generation_time=None, console=None, agent_name=None, agent_role=None, agent_tools=None, task_name=None, task_description=None, task_id=None):
|
||||
|
||||
@@ -45,6 +45,15 @@ def __getattr__(name):
|
||||
elif name == "python_type_to_json_schema":
|
||||
from .mcp_utils import python_type_to_json_schema
|
||||
return python_type_to_json_schema
|
||||
elif name == "fix_array_schemas":
|
||||
from .mcp_schema_utils import fix_array_schemas
|
||||
return fix_array_schemas
|
||||
elif name == "ThreadLocalEventLoop":
|
||||
from .mcp_schema_utils import ThreadLocalEventLoop
|
||||
return ThreadLocalEventLoop
|
||||
elif name == "get_thread_local_event_loop":
|
||||
from .mcp_schema_utils import get_thread_local_event_loop
|
||||
return get_thread_local_event_loop
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
__all__ = [
|
||||
@@ -57,6 +66,10 @@ __all__ = [
|
||||
"function_to_mcp_schema",
|
||||
"get_tool_metadata",
|
||||
"python_type_to_json_schema",
|
||||
# Schema utilities (lazy loaded)
|
||||
"fix_array_schemas",
|
||||
"ThreadLocalEventLoop",
|
||||
"get_thread_local_event_loop",
|
||||
# Transport components (lazy loaded)
|
||||
"WebSocketMCPClient",
|
||||
"SessionManager",
|
||||
|
||||
@@ -162,7 +162,7 @@ class MCP:
|
||||
# Method 1: Using command and args separately
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant...",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP(
|
||||
command="/path/to/python",
|
||||
args=["/path/to/app.py"]
|
||||
@@ -172,14 +172,14 @@ class MCP:
|
||||
# Method 2: Using a single command string
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant...",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP("/path/to/python /path/to/app.py")
|
||||
)
|
||||
|
||||
# Method 3: Using an SSE endpoint
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant...",
|
||||
llm="gpt-5-nano",
|
||||
llm="gpt-4o-mini",
|
||||
tools=MCP("http://localhost:8080/sse")
|
||||
)
|
||||
|
||||
@@ -507,6 +507,26 @@ class MCP:
|
||||
"""
|
||||
return iter(self._tools)
|
||||
|
||||
def get_tools(self) -> List[Callable]:
|
||||
"""
|
||||
Get the list of tool functions from this MCP instance.
|
||||
|
||||
This method provides explicit access to the tools list, which is useful
|
||||
when you need to inspect or manipulate the tools programmatically.
|
||||
|
||||
Returns:
|
||||
List[Callable]: List of tool functions that can be called
|
||||
|
||||
Example:
|
||||
```python
|
||||
mcp = MCP("npx -y @modelcontextprotocol/server-time")
|
||||
tools = mcp.get_tools()
|
||||
for tool in tools:
|
||||
print(f"Tool: {tool.__name__}")
|
||||
```
|
||||
"""
|
||||
return self._tools
|
||||
|
||||
def _fix_array_schemas(self, schema):
|
||||
"""
|
||||
Fix array schemas by adding missing 'items' attribute required by OpenAI.
|
||||
@@ -548,7 +568,7 @@ class MCP:
|
||||
"""Convert the MCP tool to an OpenAI-compatible tool definition.
|
||||
|
||||
This method is specifically invoked by the Agent class when using
|
||||
provider/model format (e.g., "openai/gpt-5-nano").
|
||||
provider/model format (e.g., "openai/gpt-4o-mini").
|
||||
|
||||
Returns:
|
||||
dict or list: OpenAI-compatible tool definition(s)
|
||||
|
||||
@@ -30,20 +30,19 @@ except ImportError:
|
||||
|
||||
logger = logging.getLogger("mcp-http-stream")
|
||||
|
||||
# Global event loop for async operations
|
||||
_event_loop = None
|
||||
# Import shared utilities for thread-safe event loop and schema fixing
|
||||
from .mcp_schema_utils import ThreadLocalEventLoop, fix_array_schemas
|
||||
|
||||
# Thread-local event loop for async operations (thread-safe)
|
||||
_event_loop_manager = ThreadLocalEventLoop()
|
||||
|
||||
# Global registry of active clients for cleanup
|
||||
_active_clients = weakref.WeakSet()
|
||||
_cleanup_registered = False
|
||||
|
||||
def get_event_loop():
|
||||
"""Get or create a global event loop."""
|
||||
global _event_loop
|
||||
if _event_loop is None or _event_loop.is_closed():
|
||||
_event_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(_event_loop)
|
||||
return _event_loop
|
||||
"""Get or create a thread-local event loop."""
|
||||
return _event_loop_manager.get_loop()
|
||||
|
||||
|
||||
def _cleanup_all_clients():
|
||||
@@ -148,47 +147,10 @@ class HTTPStreamMCPTool:
|
||||
logger.error(f"Error in _async_call for {self.name}: {e}")
|
||||
raise
|
||||
|
||||
def _fix_array_schemas(self, schema):
|
||||
"""
|
||||
Fix array schemas by adding missing 'items' attribute required by OpenAI.
|
||||
|
||||
This ensures compatibility with OpenAI's function calling format which
|
||||
requires array types to specify the type of items they contain.
|
||||
|
||||
Args:
|
||||
schema: The schema dictionary to fix
|
||||
|
||||
Returns:
|
||||
dict: The fixed schema
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
# Create a copy to avoid modifying the original
|
||||
fixed_schema = schema.copy()
|
||||
|
||||
# Fix array types at the current level
|
||||
if fixed_schema.get("type") == "array" and "items" not in fixed_schema:
|
||||
# Add a default items schema for arrays without it
|
||||
fixed_schema["items"] = {"type": "string"}
|
||||
|
||||
# Recursively fix nested schemas
|
||||
if "properties" in fixed_schema:
|
||||
fixed_properties = {}
|
||||
for prop_name, prop_schema in fixed_schema["properties"].items():
|
||||
fixed_properties[prop_name] = self._fix_array_schemas(prop_schema)
|
||||
fixed_schema["properties"] = fixed_properties
|
||||
|
||||
# Fix items schema if it exists
|
||||
if "items" in fixed_schema:
|
||||
fixed_schema["items"] = self._fix_array_schemas(fixed_schema["items"])
|
||||
|
||||
return fixed_schema
|
||||
|
||||
def to_openai_tool(self):
|
||||
"""Convert the tool to OpenAI format."""
|
||||
# Fix array schemas to include 'items' attribute
|
||||
fixed_schema = self._fix_array_schemas(self.input_schema)
|
||||
# Fix array schemas to include 'items' attribute (using shared utility)
|
||||
fixed_schema = fix_array_schemas(self.input_schema)
|
||||
|
||||
return {
|
||||
"type": "function",
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
MCP Schema Utilities.
|
||||
|
||||
This module provides shared schema manipulation utilities for MCP transports.
|
||||
These utilities ensure compatibility with OpenAI's function calling format
|
||||
and other LLM providers.
|
||||
|
||||
DRY: This module centralizes schema fixing logic that was previously
|
||||
duplicated across mcp.py, mcp_sse.py, mcp_http_stream.py, and mcp_websocket.py.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
def fix_array_schemas(schema: Any) -> Any:
|
||||
"""
|
||||
Fix array schemas by adding missing 'items' attribute required by OpenAI.
|
||||
|
||||
This ensures compatibility with OpenAI's function calling format which
|
||||
requires array types to specify the type of items they contain.
|
||||
|
||||
Args:
|
||||
schema: The schema dictionary to fix
|
||||
|
||||
Returns:
|
||||
dict: The fixed schema with 'items' added to array types
|
||||
|
||||
Example:
|
||||
>>> schema = {"type": "array"}
|
||||
>>> fixed = fix_array_schemas(schema)
|
||||
>>> fixed
|
||||
{'type': 'array', 'items': {'type': 'string'}}
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
# Create a copy to avoid modifying the original
|
||||
fixed_schema = schema.copy()
|
||||
|
||||
# Fix array types at the current level
|
||||
if fixed_schema.get("type") == "array" and "items" not in fixed_schema:
|
||||
# Add a default items schema for arrays without it
|
||||
fixed_schema["items"] = {"type": "string"}
|
||||
|
||||
# Recursively fix nested schemas
|
||||
if "properties" in fixed_schema:
|
||||
fixed_properties = {}
|
||||
for prop_name, prop_schema in fixed_schema["properties"].items():
|
||||
fixed_properties[prop_name] = fix_array_schemas(prop_schema)
|
||||
fixed_schema["properties"] = fixed_properties
|
||||
|
||||
# Fix items schema if it exists
|
||||
if "items" in fixed_schema:
|
||||
fixed_schema["items"] = fix_array_schemas(fixed_schema["items"])
|
||||
|
||||
# Fix additionalProperties if it's a schema
|
||||
if isinstance(fixed_schema.get("additionalProperties"), dict):
|
||||
fixed_schema["additionalProperties"] = fix_array_schemas(
|
||||
fixed_schema["additionalProperties"]
|
||||
)
|
||||
|
||||
# Fix anyOf/oneOf/allOf schemas
|
||||
for key in ("anyOf", "oneOf", "allOf"):
|
||||
if key in fixed_schema and isinstance(fixed_schema[key], list):
|
||||
fixed_schema[key] = [fix_array_schemas(s) for s in fixed_schema[key]]
|
||||
|
||||
return fixed_schema
|
||||
|
||||
|
||||
class ThreadLocalEventLoop:
|
||||
"""
|
||||
Thread-local event loop storage for MCP transports.
|
||||
|
||||
This class provides thread-safe event loop management, ensuring that
|
||||
multiple MCP instances don't share the same event loop, which could
|
||||
cause race conditions.
|
||||
|
||||
Usage:
|
||||
event_loop_manager = ThreadLocalEventLoop()
|
||||
loop = event_loop_manager.get_loop()
|
||||
# Use loop for async operations
|
||||
event_loop_manager.cleanup()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize thread-local storage."""
|
||||
self._local = threading.local()
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def get_loop(self):
|
||||
"""
|
||||
Get or create an event loop for the current thread.
|
||||
|
||||
Returns:
|
||||
asyncio.AbstractEventLoop: Event loop for current thread
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
# Check if we have a loop for this thread
|
||||
loop = getattr(self._local, 'loop', None)
|
||||
|
||||
if loop is None or loop.is_closed():
|
||||
with self._lock:
|
||||
# Double-check after acquiring lock
|
||||
loop = getattr(self._local, 'loop', None)
|
||||
if loop is None or loop.is_closed():
|
||||
loop = asyncio.new_event_loop()
|
||||
self._local.loop = loop
|
||||
|
||||
return loop
|
||||
|
||||
def set_loop(self, loop):
|
||||
"""
|
||||
Set the event loop for the current thread.
|
||||
|
||||
Args:
|
||||
loop: The event loop to set
|
||||
"""
|
||||
self._local.loop = loop
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up the event loop for the current thread."""
|
||||
loop = getattr(self._local, 'loop', None)
|
||||
if loop is not None and not loop.is_closed():
|
||||
try:
|
||||
loop.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._local.loop = None
|
||||
|
||||
|
||||
# Singleton instance for backward compatibility with existing code
|
||||
# that uses get_event_loop() function pattern
|
||||
_default_event_loop_manager = ThreadLocalEventLoop()
|
||||
|
||||
|
||||
def get_thread_local_event_loop():
|
||||
"""
|
||||
Get or create a thread-local event loop.
|
||||
|
||||
This is a drop-in replacement for the global get_event_loop() functions
|
||||
in the transport modules, providing thread safety.
|
||||
|
||||
Returns:
|
||||
asyncio.AbstractEventLoop: Event loop for current thread
|
||||
"""
|
||||
return _default_event_loop_manager.get_loop()
|
||||
|
||||
|
||||
def json_type_to_python_type(json_type: str) -> type:
|
||||
"""
|
||||
Convert JSON Schema type to Python type annotation.
|
||||
|
||||
Args:
|
||||
json_type: JSON Schema type string
|
||||
|
||||
Returns:
|
||||
Python type corresponding to the JSON type
|
||||
"""
|
||||
type_map = {
|
||||
'string': str,
|
||||
'integer': int,
|
||||
'number': float,
|
||||
'boolean': bool,
|
||||
'array': list,
|
||||
'object': dict,
|
||||
'null': type(None),
|
||||
}
|
||||
return type_map.get(json_type, str)
|
||||
|
||||
|
||||
def extract_schema_properties(input_schema: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract properties and required fields from an input schema.
|
||||
|
||||
Args:
|
||||
input_schema: JSON Schema for tool input
|
||||
|
||||
Returns:
|
||||
Dictionary with 'properties' and 'required' keys
|
||||
"""
|
||||
if not input_schema:
|
||||
return {'properties': {}, 'required': []}
|
||||
|
||||
return {
|
||||
'properties': input_schema.get('properties', {}),
|
||||
'required': input_schema.get('required', [])
|
||||
}
|
||||
@@ -22,16 +22,15 @@ except ImportError:
|
||||
|
||||
logger = logging.getLogger("mcp-sse")
|
||||
|
||||
# Global event loop for async operations
|
||||
_event_loop = None
|
||||
# Import shared utilities for thread-safe event loop and schema fixing
|
||||
from .mcp_schema_utils import ThreadLocalEventLoop, fix_array_schemas
|
||||
|
||||
# Thread-local event loop for async operations (thread-safe)
|
||||
_event_loop_manager = ThreadLocalEventLoop()
|
||||
|
||||
def get_event_loop():
|
||||
"""Get or create a global event loop."""
|
||||
global _event_loop
|
||||
if _event_loop is None or _event_loop.is_closed():
|
||||
_event_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(_event_loop)
|
||||
return _event_loop
|
||||
"""Get or create a thread-local event loop."""
|
||||
return _event_loop_manager.get_loop()
|
||||
|
||||
|
||||
class SSEMCPTool:
|
||||
@@ -111,47 +110,10 @@ class SSEMCPTool:
|
||||
logger.error(f"Error in _async_call for {self.name}: {e}")
|
||||
raise
|
||||
|
||||
def _fix_array_schemas(self, schema):
|
||||
"""
|
||||
Fix array schemas by adding missing 'items' attribute required by OpenAI.
|
||||
|
||||
This ensures compatibility with OpenAI's function calling format which
|
||||
requires array types to specify the type of items they contain.
|
||||
|
||||
Args:
|
||||
schema: The schema dictionary to fix
|
||||
|
||||
Returns:
|
||||
dict: The fixed schema
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
# Create a copy to avoid modifying the original
|
||||
fixed_schema = schema.copy()
|
||||
|
||||
# Fix array types at the current level
|
||||
if fixed_schema.get("type") == "array" and "items" not in fixed_schema:
|
||||
# Add a default items schema for arrays without it
|
||||
fixed_schema["items"] = {"type": "string"}
|
||||
|
||||
# Recursively fix nested schemas
|
||||
if "properties" in fixed_schema:
|
||||
fixed_properties = {}
|
||||
for prop_name, prop_schema in fixed_schema["properties"].items():
|
||||
fixed_properties[prop_name] = self._fix_array_schemas(prop_schema)
|
||||
fixed_schema["properties"] = fixed_properties
|
||||
|
||||
# Fix items schema if it exists
|
||||
if "items" in fixed_schema:
|
||||
fixed_schema["items"] = self._fix_array_schemas(fixed_schema["items"])
|
||||
|
||||
return fixed_schema
|
||||
|
||||
def to_openai_tool(self):
|
||||
"""Convert the tool to OpenAI format."""
|
||||
# Fix array schemas to include 'items' attribute
|
||||
fixed_schema = self._fix_array_schemas(self.input_schema)
|
||||
# Fix array schemas to include 'items' attribute (using shared utility)
|
||||
fixed_schema = fix_array_schemas(self.input_schema)
|
||||
|
||||
return {
|
||||
"type": "function",
|
||||
|
||||
@@ -26,6 +26,9 @@ from typing import Any, Dict, Optional, List
|
||||
|
||||
logger = logging.getLogger("mcp-websocket")
|
||||
|
||||
# Import shared utilities for thread-safe event loop and schema fixing
|
||||
from .mcp_schema_utils import ThreadLocalEventLoop, fix_array_schemas
|
||||
|
||||
|
||||
def is_websocket_url(url: str) -> bool:
|
||||
"""
|
||||
@@ -58,18 +61,13 @@ def calculate_backoff(attempt: int, base_delay: float = 1.0, max_delay: float =
|
||||
return min(delay, max_delay)
|
||||
|
||||
|
||||
# Global event loop for WebSocket operations (similar to other MCP transports)
|
||||
_event_loop = None
|
||||
_loop_lock = threading.Lock()
|
||||
# Thread-local event loop for WebSocket operations (thread-safe)
|
||||
_event_loop_manager = ThreadLocalEventLoop()
|
||||
|
||||
|
||||
def get_event_loop():
|
||||
"""Get or create a global event loop for WebSocket operations."""
|
||||
global _event_loop
|
||||
with _loop_lock:
|
||||
if _event_loop is None or _event_loop.is_closed():
|
||||
_event_loop = asyncio.new_event_loop()
|
||||
return _event_loop
|
||||
"""Get or create a thread-local event loop for WebSocket operations."""
|
||||
return _event_loop_manager.get_loop()
|
||||
|
||||
|
||||
class WebSocketTransport:
|
||||
@@ -359,36 +357,10 @@ class WebSocketMCPTool:
|
||||
logger.error(f"Error in _async_call for {self.name}: {e}")
|
||||
raise
|
||||
|
||||
def _fix_array_schemas(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Fix array schemas by adding missing 'items' attribute.
|
||||
|
||||
OpenAI requires array types to specify item types.
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
fixed_schema = schema.copy()
|
||||
|
||||
# Fix array types at current level
|
||||
if fixed_schema.get("type") == "array" and "items" not in fixed_schema:
|
||||
fixed_schema["items"] = {"type": "string"}
|
||||
|
||||
# Recursively fix nested schemas
|
||||
if "properties" in fixed_schema:
|
||||
fixed_properties = {}
|
||||
for prop_name, prop_schema in fixed_schema["properties"].items():
|
||||
fixed_properties[prop_name] = self._fix_array_schemas(prop_schema)
|
||||
fixed_schema["properties"] = fixed_properties
|
||||
|
||||
if "items" in fixed_schema:
|
||||
fixed_schema["items"] = self._fix_array_schemas(fixed_schema["items"])
|
||||
|
||||
return fixed_schema
|
||||
|
||||
def to_openai_tool(self) -> Dict[str, Any]:
|
||||
"""Convert the tool to OpenAI function calling format."""
|
||||
fixed_schema = self._fix_array_schemas(self.input_schema)
|
||||
# Fix array schemas to include 'items' attribute (using shared utility)
|
||||
fixed_schema = fix_array_schemas(self.input_schema)
|
||||
|
||||
return {
|
||||
"type": "function",
|
||||
|
||||
@@ -18,6 +18,11 @@ This module provides memory management capabilities including:
|
||||
- Docs management (like Cursor docs)
|
||||
- MCP config management (like Cursor .cursor/mcp/)
|
||||
|
||||
Protocols:
|
||||
- MemoryProtocol: Minimal interface for memory implementations
|
||||
- AsyncMemoryProtocol: Async interface for memory
|
||||
- ResettableMemoryProtocol: Interface with reset methods
|
||||
|
||||
Memory Providers:
|
||||
- FileMemory: Zero-dependency JSON file-based storage (default)
|
||||
- Memory: Full-featured with SQLite, ChromaDB, Mem0, MongoDB support
|
||||
@@ -33,6 +38,13 @@ from .file_memory import FileMemory, create_memory
|
||||
from .rules_manager import RulesManager, Rule, create_rules_manager
|
||||
from .docs_manager import DocsManager, Doc
|
||||
from .mcp_config import MCPConfigManager, MCPConfig
|
||||
from .protocols import (
|
||||
MemoryProtocol,
|
||||
AsyncMemoryProtocol,
|
||||
ResettableMemoryProtocol,
|
||||
EntityMemoryProtocol,
|
||||
)
|
||||
|
||||
|
||||
# Lazy imports for optional modules to avoid dependency issues and improve startup time
|
||||
def __getattr__(name):
|
||||
@@ -110,6 +122,31 @@ def __getattr__(name):
|
||||
if name == "create_hooks_manager":
|
||||
from .hooks import create_hooks_manager
|
||||
return create_hooks_manager
|
||||
# Learn module
|
||||
if name == "LearnManager":
|
||||
from .learn import LearnManager
|
||||
return LearnManager
|
||||
if name == "PersonaStore":
|
||||
from .learn import PersonaStore
|
||||
return PersonaStore
|
||||
if name == "InsightStore":
|
||||
from .learn import InsightStore
|
||||
return InsightStore
|
||||
if name == "ThreadStore":
|
||||
from .learn import ThreadStore
|
||||
return ThreadStore
|
||||
if name == "PatternStore":
|
||||
from .learn import PatternStore
|
||||
return PatternStore
|
||||
if name == "DecisionStore":
|
||||
from .learn import DecisionStore
|
||||
return DecisionStore
|
||||
if name == "FeedbackStore":
|
||||
from .learn import FeedbackStore
|
||||
return FeedbackStore
|
||||
if name == "ImprovementStore":
|
||||
from .learn import ImprovementStore
|
||||
return ImprovementStore
|
||||
# DocsManager and MCPConfigManager are already imported at module level
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -153,4 +190,18 @@ __all__ = [
|
||||
# MCP config management
|
||||
"MCPConfigManager",
|
||||
"MCPConfig",
|
||||
# Learn module
|
||||
"LearnManager",
|
||||
"PersonaStore",
|
||||
"InsightStore",
|
||||
"ThreadStore",
|
||||
"PatternStore",
|
||||
"DecisionStore",
|
||||
"FeedbackStore",
|
||||
"ImprovementStore",
|
||||
# Protocols
|
||||
"MemoryProtocol",
|
||||
"AsyncMemoryProtocol",
|
||||
"ResettableMemoryProtocol",
|
||||
"EntityMemoryProtocol",
|
||||
]
|
||||
@@ -18,7 +18,7 @@ Storage Structure:
|
||||
|
||||
import json
|
||||
import time
|
||||
import fcntl
|
||||
import sys
|
||||
import hashlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
@@ -26,9 +26,17 @@ from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
from dataclasses import dataclass, asdict, field
|
||||
|
||||
# fcntl is Unix-only; on Windows, skip file locking (acceptable for single-process usage)
|
||||
if sys.platform != 'win32':
|
||||
import fcntl
|
||||
_HAS_FCNTL = True
|
||||
else:
|
||||
_HAS_FCNTL = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemoryItem:
|
||||
"""A single memory item."""
|
||||
@@ -178,35 +186,41 @@ class FileMemory:
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _read_json(self, filepath: Path, default: Any = None) -> Any:
|
||||
"""Read JSON file with file locking."""
|
||||
"""Read JSON file with file locking (Unix only)."""
|
||||
if not filepath.exists():
|
||||
return default if default is not None else []
|
||||
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
|
||||
try:
|
||||
data = json.load(f)
|
||||
finally:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
return data
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
self._log(f"Error reading {filepath}: {e}", logging.WARNING)
|
||||
return default if default is not None else []
|
||||
|
||||
|
||||
def _write_json(self, filepath: Path, data: Any) -> bool:
|
||||
"""Write JSON file with file locking."""
|
||||
"""Write JSON file with file locking (Unix only)."""
|
||||
try:
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
try:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
finally:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
return True
|
||||
except IOError as e:
|
||||
self._log(f"Error writing {filepath}: {e}", logging.ERROR)
|
||||
return False
|
||||
|
||||
|
||||
def _load_config(self, overrides: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""Load or create configuration."""
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Learn Module - Continuous Learning within Memory System.
|
||||
|
||||
Provides learning capabilities for agents to capture patterns, preferences,
|
||||
and insights from interactions to improve future responses.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents import Agent, MemoryConfig, LearnConfig
|
||||
|
||||
# Simple enable
|
||||
agent = Agent(memory=MemoryConfig(learn=True))
|
||||
|
||||
# With specific capabilities
|
||||
agent = Agent(memory=MemoryConfig(
|
||||
learn=LearnConfig(
|
||||
persona=True,
|
||||
insights=True,
|
||||
patterns=True,
|
||||
)
|
||||
))
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"LearnManager",
|
||||
"PersonaStore",
|
||||
"InsightStore",
|
||||
"ThreadStore",
|
||||
"PatternStore",
|
||||
"DecisionStore",
|
||||
"FeedbackStore",
|
||||
"ImprovementStore",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy loading for learn module components."""
|
||||
if name == "LearnManager":
|
||||
from .manager import LearnManager
|
||||
return LearnManager
|
||||
if name == "PersonaStore":
|
||||
from .stores import PersonaStore
|
||||
return PersonaStore
|
||||
if name == "InsightStore":
|
||||
from .stores import InsightStore
|
||||
return InsightStore
|
||||
if name == "ThreadStore":
|
||||
from .stores import ThreadStore
|
||||
return ThreadStore
|
||||
if name == "PatternStore":
|
||||
from .stores import PatternStore
|
||||
return PatternStore
|
||||
if name == "DecisionStore":
|
||||
from .stores import DecisionStore
|
||||
return DecisionStore
|
||||
if name == "FeedbackStore":
|
||||
from .stores import FeedbackStore
|
||||
return FeedbackStore
|
||||
if name == "ImprovementStore":
|
||||
from .stores import ImprovementStore
|
||||
return ImprovementStore
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
LearnManager - Central manager for continuous learning capabilities.
|
||||
|
||||
Coordinates all learning stores and provides a unified interface for
|
||||
capturing and retrieving learned information.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from ...config.feature_configs import LearnConfig, LearnScope
|
||||
from .stores import (
|
||||
PersonaStore,
|
||||
InsightStore,
|
||||
ThreadStore,
|
||||
PatternStore,
|
||||
DecisionStore,
|
||||
FeedbackStore,
|
||||
ImprovementStore,
|
||||
LearnEntry,
|
||||
)
|
||||
|
||||
|
||||
class LearnManager:
|
||||
"""
|
||||
Central manager for continuous learning within the memory system.
|
||||
|
||||
Coordinates all learning stores based on LearnConfig settings.
|
||||
|
||||
Usage:
|
||||
config = LearnConfig(persona=True, insights=True)
|
||||
manager = LearnManager(config, user_id="alice")
|
||||
|
||||
# Capture learning
|
||||
manager.capture_persona("User prefers concise responses")
|
||||
manager.capture_insight("User works in data science")
|
||||
|
||||
# Retrieve learnings
|
||||
context = manager.get_learning_context()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Optional[LearnConfig] = None,
|
||||
user_id: Optional[str] = None,
|
||||
store_path: Optional[str] = None,
|
||||
):
|
||||
self.config = config or LearnConfig()
|
||||
self.user_id = user_id or "default"
|
||||
self.store_path = store_path or self.config.store_path
|
||||
|
||||
scope = self.config.scope
|
||||
if isinstance(scope, LearnScope):
|
||||
scope = scope.value
|
||||
|
||||
self._stores: Dict[str, Any] = {}
|
||||
|
||||
if self.config.persona:
|
||||
self._stores["persona"] = PersonaStore(
|
||||
store_path=self._get_store_path("persona"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.insights:
|
||||
self._stores["insights"] = InsightStore(
|
||||
store_path=self._get_store_path("insights"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.thread:
|
||||
self._stores["threads"] = ThreadStore(
|
||||
store_path=self._get_store_path("threads"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.patterns:
|
||||
self._stores["patterns"] = PatternStore(
|
||||
store_path=self._get_store_path("patterns"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.decisions:
|
||||
self._stores["decisions"] = DecisionStore(
|
||||
store_path=self._get_store_path("decisions"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.feedback:
|
||||
self._stores["feedback"] = FeedbackStore(
|
||||
store_path=self._get_store_path("feedback"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
if self.config.improvements:
|
||||
self._stores["improvements"] = ImprovementStore(
|
||||
store_path=self._get_store_path("improvements"),
|
||||
user_id=self.user_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
def _get_store_path(self, store_name: str) -> Optional[str]:
|
||||
"""Get path for a specific store."""
|
||||
if self.store_path:
|
||||
return f"{self.store_path}/{store_name}.json"
|
||||
return None
|
||||
|
||||
def capture_persona(self, content: str, category: str = "general") -> Optional[LearnEntry]:
|
||||
"""Capture user preference or profile information."""
|
||||
if "persona" in self._stores:
|
||||
return self._stores["persona"].add_preference(content, category)
|
||||
return None
|
||||
|
||||
def capture_insight(self, content: str, source: str = "interaction") -> Optional[LearnEntry]:
|
||||
"""Capture an observation or learning."""
|
||||
if "insights" in self._stores:
|
||||
return self._stores["insights"].add_insight(content, source)
|
||||
return None
|
||||
|
||||
def capture_thread(self, summary: str, thread_id: str) -> Optional[LearnEntry]:
|
||||
"""Capture thread/session context."""
|
||||
if "threads" in self._stores:
|
||||
return self._stores["threads"].add_thread_summary(summary, thread_id)
|
||||
return None
|
||||
|
||||
def capture_pattern(self, pattern: str, pattern_type: str = "general") -> Optional[LearnEntry]:
|
||||
"""Capture a reusable knowledge pattern."""
|
||||
if "patterns" in self._stores:
|
||||
return self._stores["patterns"].add_pattern(pattern, pattern_type)
|
||||
return None
|
||||
|
||||
def capture_decision(
|
||||
self,
|
||||
decision: str,
|
||||
reasoning: str = "",
|
||||
outcome: str = "",
|
||||
) -> Optional[LearnEntry]:
|
||||
"""Capture a decision record."""
|
||||
if "decisions" in self._stores:
|
||||
return self._stores["decisions"].add_decision(decision, reasoning, outcome)
|
||||
return None
|
||||
|
||||
def capture_feedback(
|
||||
self,
|
||||
feedback: str,
|
||||
rating: Optional[int] = None,
|
||||
source: str = "user",
|
||||
) -> Optional[LearnEntry]:
|
||||
"""Capture feedback."""
|
||||
if "feedback" in self._stores:
|
||||
return self._stores["feedback"].add_feedback(feedback, rating, source)
|
||||
return None
|
||||
|
||||
def capture_improvement(
|
||||
self,
|
||||
proposal: str,
|
||||
area: str = "general",
|
||||
priority: str = "medium",
|
||||
) -> Optional[LearnEntry]:
|
||||
"""Capture an improvement proposal."""
|
||||
if "improvements" in self._stores:
|
||||
return self._stores["improvements"].add_improvement(proposal, area, priority)
|
||||
return None
|
||||
|
||||
def get_learning_context(self, limit_per_store: int = 5) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""
|
||||
Get learning context from all enabled stores.
|
||||
|
||||
Returns a dictionary with entries from each store, suitable for
|
||||
injection into agent context.
|
||||
"""
|
||||
context = {}
|
||||
for store_name, store in self._stores.items():
|
||||
entries = store.list_all(limit=limit_per_store)
|
||||
context[store_name] = [e.to_dict() for e in entries]
|
||||
return context
|
||||
|
||||
def search(self, query: str, limit: int = 10) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""Search across all enabled stores."""
|
||||
results = {}
|
||||
for store_name, store in self._stores.items():
|
||||
entries = store.search(query, limit=limit)
|
||||
if entries:
|
||||
results[store_name] = [e.to_dict() for e in entries]
|
||||
return results
|
||||
|
||||
def get_persona_context(self, limit: int = 10) -> List[str]:
|
||||
"""Get persona entries as context strings."""
|
||||
if "persona" not in self._stores:
|
||||
return []
|
||||
entries = self._stores["persona"].list_all(limit=limit)
|
||||
return [e.content for e in entries]
|
||||
|
||||
def get_insights_context(self, limit: int = 10) -> List[str]:
|
||||
"""Get insight entries as context strings."""
|
||||
if "insights" not in self._stores:
|
||||
return []
|
||||
entries = self._stores["insights"].list_all(limit=limit)
|
||||
return [e.content for e in entries]
|
||||
|
||||
def clear_all(self) -> Dict[str, int]:
|
||||
"""Clear all stores."""
|
||||
cleared = {}
|
||||
for store_name, store in self._stores.items():
|
||||
cleared[store_name] = store.clear()
|
||||
return cleared
|
||||
|
||||
def get_stats(self) -> Dict[str, int]:
|
||||
"""Get entry counts for all stores."""
|
||||
return {
|
||||
store_name: len(store._entries)
|
||||
for store_name, store in self._stores.items()
|
||||
}
|
||||
|
||||
def to_system_prompt_context(self) -> str:
|
||||
"""
|
||||
Generate context suitable for injection into system prompt.
|
||||
|
||||
Returns a formatted string with relevant learnings.
|
||||
"""
|
||||
parts = []
|
||||
|
||||
if "persona" in self._stores:
|
||||
personas = self.get_persona_context(limit=5)
|
||||
if personas:
|
||||
parts.append("User Preferences:")
|
||||
for p in personas:
|
||||
parts.append(f"- {p}")
|
||||
|
||||
if "insights" in self._stores:
|
||||
insights = self.get_insights_context(limit=5)
|
||||
if insights:
|
||||
parts.append("\nLearned Insights:")
|
||||
for i in insights:
|
||||
parts.append(f"- {i}")
|
||||
|
||||
return "\n".join(parts) if parts else ""
|
||||
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
Learning Stores - Storage backends for different learning capabilities.
|
||||
|
||||
Each store handles a specific type of learning data:
|
||||
- PersonaStore: User preferences and profile
|
||||
- InsightStore: Observations and learnings
|
||||
- ThreadStore: Session/conversation context
|
||||
- PatternStore: Reusable knowledge patterns
|
||||
- DecisionStore: Decision logging
|
||||
- FeedbackStore: Outcome signals
|
||||
- ImprovementStore: Self-improvement proposals
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class LearnEntry:
|
||||
"""Base entry for all learning stores."""
|
||||
id: str
|
||||
content: str
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||
updated_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"content": self.content,
|
||||
"metadata": self.metadata,
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "LearnEntry":
|
||||
return cls(
|
||||
id=data["id"],
|
||||
content=data["content"],
|
||||
metadata=data.get("metadata", {}),
|
||||
created_at=data.get("created_at", datetime.utcnow().isoformat()),
|
||||
updated_at=data.get("updated_at", datetime.utcnow().isoformat()),
|
||||
)
|
||||
|
||||
|
||||
class BaseStore(ABC):
|
||||
"""Abstract base class for learning stores."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
store_path: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
scope: str = "private",
|
||||
):
|
||||
self.user_id = user_id or "default"
|
||||
self.scope = scope
|
||||
self.store_path = store_path or self._default_path()
|
||||
self._entries: Dict[str, LearnEntry] = {}
|
||||
self._load()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def store_name(self) -> str:
|
||||
"""Name of the store (used for file naming)."""
|
||||
pass
|
||||
|
||||
def _default_path(self) -> str:
|
||||
"""Default storage path."""
|
||||
base = Path.home() / ".praison" / "learn" / self.scope / self.user_id
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
return str(base / f"{self.store_name}.json")
|
||||
|
||||
def _load(self) -> None:
|
||||
"""Load entries from storage."""
|
||||
if os.path.exists(self.store_path):
|
||||
try:
|
||||
with open(self.store_path, "r") as f:
|
||||
data = json.load(f)
|
||||
self._entries = {
|
||||
k: LearnEntry.from_dict(v) for k, v in data.items()
|
||||
}
|
||||
except (json.JSONDecodeError, IOError):
|
||||
self._entries = {}
|
||||
|
||||
def _save(self) -> None:
|
||||
"""Save entries to storage."""
|
||||
os.makedirs(os.path.dirname(self.store_path), exist_ok=True)
|
||||
with open(self.store_path, "w") as f:
|
||||
json.dump(
|
||||
{k: v.to_dict() for k, v in self._entries.items()},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
def add(self, content: str, metadata: Optional[Dict[str, Any]] = None) -> LearnEntry:
|
||||
"""Add a new entry."""
|
||||
entry_id = f"{self.store_name}_{len(self._entries)}_{datetime.utcnow().timestamp()}"
|
||||
entry = LearnEntry(
|
||||
id=entry_id,
|
||||
content=content,
|
||||
metadata=metadata or {},
|
||||
)
|
||||
self._entries[entry_id] = entry
|
||||
self._save()
|
||||
return entry
|
||||
|
||||
def get(self, entry_id: str) -> Optional[LearnEntry]:
|
||||
"""Get entry by ID."""
|
||||
return self._entries.get(entry_id)
|
||||
|
||||
def search(self, query: str, limit: int = 10) -> List[LearnEntry]:
|
||||
"""Simple text search (can be overridden for semantic search)."""
|
||||
query_lower = query.lower()
|
||||
results = [
|
||||
entry for entry in self._entries.values()
|
||||
if query_lower in entry.content.lower()
|
||||
]
|
||||
return results[:limit]
|
||||
|
||||
def list_all(self, limit: int = 100) -> List[LearnEntry]:
|
||||
"""List all entries."""
|
||||
entries = list(self._entries.values())
|
||||
entries.sort(key=lambda x: x.updated_at, reverse=True)
|
||||
return entries[:limit]
|
||||
|
||||
def update(self, entry_id: str, content: str, metadata: Optional[Dict[str, Any]] = None) -> Optional[LearnEntry]:
|
||||
"""Update an existing entry."""
|
||||
if entry_id not in self._entries:
|
||||
return None
|
||||
entry = self._entries[entry_id]
|
||||
entry.content = content
|
||||
if metadata:
|
||||
entry.metadata.update(metadata)
|
||||
entry.updated_at = datetime.utcnow().isoformat()
|
||||
self._save()
|
||||
return entry
|
||||
|
||||
def delete(self, entry_id: str) -> bool:
|
||||
"""Delete an entry."""
|
||||
if entry_id in self._entries:
|
||||
del self._entries[entry_id]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def clear(self) -> int:
|
||||
"""Clear all entries."""
|
||||
count = len(self._entries)
|
||||
self._entries = {}
|
||||
self._save()
|
||||
return count
|
||||
|
||||
|
||||
class PersonaStore(BaseStore):
|
||||
"""Store for user preferences and profile information."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "persona"
|
||||
|
||||
def add_preference(self, preference: str, category: str = "general") -> LearnEntry:
|
||||
"""Add a user preference."""
|
||||
return self.add(preference, {"category": category, "type": "preference"})
|
||||
|
||||
def add_profile(self, profile_data: str, aspect: str = "general") -> LearnEntry:
|
||||
"""Add profile information."""
|
||||
return self.add(profile_data, {"aspect": aspect, "type": "profile"})
|
||||
|
||||
|
||||
class InsightStore(BaseStore):
|
||||
"""Store for observations and learnings."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "insights"
|
||||
|
||||
def add_insight(self, insight: str, source: str = "interaction") -> LearnEntry:
|
||||
"""Add an insight."""
|
||||
return self.add(insight, {"source": source, "type": "insight"})
|
||||
|
||||
def add_observation(self, observation: str, context: str = "") -> LearnEntry:
|
||||
"""Add an observation."""
|
||||
return self.add(observation, {"context": context, "type": "observation"})
|
||||
|
||||
|
||||
class ThreadStore(BaseStore):
|
||||
"""Store for session/conversation context."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "threads"
|
||||
|
||||
def add_thread_summary(self, summary: str, thread_id: str) -> LearnEntry:
|
||||
"""Add a thread summary."""
|
||||
return self.add(summary, {"thread_id": thread_id, "type": "summary"})
|
||||
|
||||
def add_context(self, context: str, thread_id: str) -> LearnEntry:
|
||||
"""Add thread context."""
|
||||
return self.add(context, {"thread_id": thread_id, "type": "context"})
|
||||
|
||||
|
||||
class PatternStore(BaseStore):
|
||||
"""Store for reusable knowledge patterns."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "patterns"
|
||||
|
||||
def add_pattern(self, pattern: str, pattern_type: str = "general") -> LearnEntry:
|
||||
"""Add a knowledge pattern."""
|
||||
return self.add(pattern, {"pattern_type": pattern_type, "type": "pattern"})
|
||||
|
||||
|
||||
class DecisionStore(BaseStore):
|
||||
"""Store for decision logging."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "decisions"
|
||||
|
||||
def add_decision(
|
||||
self,
|
||||
decision: str,
|
||||
reasoning: str = "",
|
||||
outcome: str = "",
|
||||
) -> LearnEntry:
|
||||
"""Add a decision record."""
|
||||
return self.add(decision, {
|
||||
"reasoning": reasoning,
|
||||
"outcome": outcome,
|
||||
"type": "decision",
|
||||
})
|
||||
|
||||
|
||||
class FeedbackStore(BaseStore):
|
||||
"""Store for outcome signals and feedback."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "feedback"
|
||||
|
||||
def add_feedback(
|
||||
self,
|
||||
feedback: str,
|
||||
rating: Optional[int] = None,
|
||||
source: str = "user",
|
||||
) -> LearnEntry:
|
||||
"""Add feedback."""
|
||||
metadata = {"source": source, "type": "feedback"}
|
||||
if rating is not None:
|
||||
metadata["rating"] = rating
|
||||
return self.add(feedback, metadata)
|
||||
|
||||
|
||||
class ImprovementStore(BaseStore):
|
||||
"""Store for self-improvement proposals."""
|
||||
|
||||
@property
|
||||
def store_name(self) -> str:
|
||||
return "improvements"
|
||||
|
||||
def add_improvement(
|
||||
self,
|
||||
proposal: str,
|
||||
area: str = "general",
|
||||
priority: str = "medium",
|
||||
) -> LearnEntry:
|
||||
"""Add an improvement proposal."""
|
||||
return self.add(proposal, {
|
||||
"area": area,
|
||||
"priority": priority,
|
||||
"type": "improvement",
|
||||
})
|
||||
@@ -23,39 +23,102 @@ def trace(self, message, *args, **kwargs):
|
||||
|
||||
logging.Logger.trace = trace
|
||||
|
||||
try:
|
||||
import chromadb
|
||||
from chromadb.config import Settings as ChromaSettings
|
||||
CHROMADB_AVAILABLE = True
|
||||
except ImportError:
|
||||
CHROMADB_AVAILABLE = False
|
||||
pass
|
||||
# Lazy availability flags and cached imports
|
||||
_chromadb_cache = {"available": None, "module": None, "settings": None}
|
||||
_mem0_cache = {"available": None, "module": None}
|
||||
_openai_cache = {"available": None, "module": None}
|
||||
_litellm_cache = {"available": None, "module": None}
|
||||
_pymongo_cache = {"available": None, "module": None, "client": None}
|
||||
|
||||
try:
|
||||
import mem0
|
||||
MEM0_AVAILABLE = True
|
||||
except ImportError:
|
||||
MEM0_AVAILABLE = False
|
||||
def _check_chromadb():
|
||||
"""Lazily check chromadb availability and cache."""
|
||||
if _chromadb_cache["available"] is None:
|
||||
try:
|
||||
import chromadb
|
||||
from chromadb.config import Settings as ChromaSettings
|
||||
_chromadb_cache["available"] = True
|
||||
_chromadb_cache["module"] = chromadb
|
||||
_chromadb_cache["settings"] = ChromaSettings
|
||||
except ImportError:
|
||||
_chromadb_cache["available"] = False
|
||||
return _chromadb_cache["available"]
|
||||
|
||||
try:
|
||||
import openai
|
||||
OPENAI_AVAILABLE = True
|
||||
except ImportError:
|
||||
OPENAI_AVAILABLE = False
|
||||
def _get_chromadb():
|
||||
"""Get chromadb module (lazy load)."""
|
||||
if not _check_chromadb():
|
||||
raise ImportError("chromadb is required. Install with: pip install chromadb")
|
||||
return _chromadb_cache["module"], _chromadb_cache["settings"]
|
||||
|
||||
try:
|
||||
import litellm
|
||||
litellm.telemetry = False # Disable telemetry
|
||||
LITELLM_AVAILABLE = True
|
||||
except ImportError:
|
||||
LITELLM_AVAILABLE = False
|
||||
def _check_mem0():
|
||||
"""Lazily check mem0 availability."""
|
||||
if _mem0_cache["available"] is None:
|
||||
try:
|
||||
import mem0
|
||||
_mem0_cache["available"] = True
|
||||
_mem0_cache["module"] = mem0
|
||||
except ImportError:
|
||||
_mem0_cache["available"] = False
|
||||
return _mem0_cache["available"]
|
||||
|
||||
try:
|
||||
import pymongo
|
||||
from pymongo import MongoClient
|
||||
PYMONGO_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYMONGO_AVAILABLE = False
|
||||
def _get_mem0():
|
||||
"""Get mem0 module (lazy load)."""
|
||||
if not _check_mem0():
|
||||
raise ImportError("mem0 is required. Install with: pip install mem0ai")
|
||||
return _mem0_cache["module"]
|
||||
|
||||
def _check_openai():
|
||||
"""Lazily check openai availability."""
|
||||
if _openai_cache["available"] is None:
|
||||
try:
|
||||
import openai
|
||||
_openai_cache["available"] = True
|
||||
_openai_cache["module"] = openai
|
||||
except ImportError:
|
||||
_openai_cache["available"] = False
|
||||
return _openai_cache["available"]
|
||||
|
||||
def _get_openai():
|
||||
"""Get openai module (lazy load)."""
|
||||
if not _check_openai():
|
||||
raise ImportError("openai is required. Install with: pip install openai")
|
||||
return _openai_cache["module"]
|
||||
|
||||
def _check_litellm():
|
||||
"""Lazily check litellm availability."""
|
||||
if _litellm_cache["available"] is None:
|
||||
try:
|
||||
import litellm
|
||||
litellm.telemetry = False
|
||||
_litellm_cache["available"] = True
|
||||
_litellm_cache["module"] = litellm
|
||||
except ImportError:
|
||||
_litellm_cache["available"] = False
|
||||
return _litellm_cache["available"]
|
||||
|
||||
def _get_litellm():
|
||||
"""Get litellm module (lazy load)."""
|
||||
if not _check_litellm():
|
||||
raise ImportError("litellm is required. Install with: pip install litellm")
|
||||
return _litellm_cache["module"]
|
||||
|
||||
def _check_pymongo():
|
||||
"""Lazily check pymongo availability."""
|
||||
if _pymongo_cache["available"] is None:
|
||||
try:
|
||||
import pymongo
|
||||
from pymongo import MongoClient
|
||||
_pymongo_cache["available"] = True
|
||||
_pymongo_cache["module"] = pymongo
|
||||
_pymongo_cache["client"] = MongoClient
|
||||
except ImportError:
|
||||
_pymongo_cache["available"] = False
|
||||
return _pymongo_cache["available"]
|
||||
|
||||
def _get_pymongo():
|
||||
"""Get pymongo module and MongoClient (lazy load)."""
|
||||
if not _check_pymongo():
|
||||
raise ImportError("pymongo is required. Install with: pip install pymongo")
|
||||
return _pymongo_cache["module"], _pymongo_cache["client"]
|
||||
|
||||
|
||||
|
||||
@@ -109,7 +172,7 @@ class Memory:
|
||||
},
|
||||
"llm": {
|
||||
"provider": "openai",
|
||||
"config": {"model": "gpt-5-nano", "api_key": "..."}
|
||||
"config": {"model": "gpt-4o-mini", "api_key": "..."}
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
@@ -142,10 +205,12 @@ class Memory:
|
||||
logging.getLogger('litellm.utils').setLevel(logging.WARNING)
|
||||
|
||||
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)
|
||||
self.use_mongodb = (self.provider.lower() == "mongodb") and PYMONGO_AVAILABLE
|
||||
self.use_mem0 = (self.provider.lower() == "mem0") and _check_mem0()
|
||||
self.use_rag = (self.provider.lower() == "rag") and _check_chromadb() and self.cfg.get("use_embedding", False)
|
||||
self.use_mongodb = (self.provider.lower() == "mongodb") and _check_pymongo()
|
||||
self.graph_enabled = False # Initialize graph support flag
|
||||
self._learn_manager = None # Lazy-loaded LearnManager
|
||||
self._learn_config = self.cfg.get("learn", None) # Learn configuration
|
||||
|
||||
# Extract embedding model from config
|
||||
self.embedder_config = self.cfg.get("embedder", {})
|
||||
@@ -272,6 +337,9 @@ class Memory:
|
||||
rag_path = self.cfg.get("rag_db_path", "chroma_db")
|
||||
os.makedirs(rag_path, exist_ok=True)
|
||||
|
||||
# Get chromadb lazily
|
||||
chromadb, ChromaSettings = _get_chromadb()
|
||||
|
||||
# Initialize ChromaDB with persistent storage
|
||||
self.chroma_client = chromadb.PersistentClient(
|
||||
path=rag_path,
|
||||
@@ -397,7 +465,7 @@ class Memory:
|
||||
def _get_embedding(self, text: str) -> List[float]:
|
||||
"""Get embedding for text using available embedding services."""
|
||||
try:
|
||||
if LITELLM_AVAILABLE:
|
||||
if _check_litellm():
|
||||
# Use LiteLLM for consistency with the rest of the codebase
|
||||
import litellm
|
||||
|
||||
@@ -406,7 +474,7 @@ class Memory:
|
||||
input=text
|
||||
)
|
||||
return response.data[0]["embedding"]
|
||||
elif OPENAI_AVAILABLE:
|
||||
elif _check_openai():
|
||||
# Fallback to OpenAI client
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
@@ -622,7 +690,7 @@ class Memory:
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
if LITELLM_AVAILABLE:
|
||||
if _check_litellm():
|
||||
# Use LiteLLM for consistency with the rest of the codebase
|
||||
import litellm
|
||||
|
||||
@@ -631,7 +699,7 @@ class Memory:
|
||||
input=query
|
||||
)
|
||||
query_embedding = response.data[0]["embedding"]
|
||||
elif OPENAI_AVAILABLE:
|
||||
elif _check_openai():
|
||||
# Fallback to OpenAI client
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
@@ -785,7 +853,7 @@ class Memory:
|
||||
# Store in vector database if enabled
|
||||
if self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
if LITELLM_AVAILABLE:
|
||||
if _check_litellm():
|
||||
# Use LiteLLM for consistency with the rest of the codebase
|
||||
import litellm
|
||||
|
||||
@@ -800,7 +868,7 @@ class Memory:
|
||||
logger.info("Successfully got embeddings from LiteLLM")
|
||||
logger.trace(f"Received embedding of length: {len(embedding)}")
|
||||
|
||||
elif OPENAI_AVAILABLE:
|
||||
elif _check_openai():
|
||||
# Fallback to OpenAI client
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
@@ -938,7 +1006,7 @@ class Memory:
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
if LITELLM_AVAILABLE:
|
||||
if _check_litellm():
|
||||
# Use LiteLLM for consistency with the rest of the codebase
|
||||
import litellm
|
||||
|
||||
@@ -947,7 +1015,7 @@ class Memory:
|
||||
input=query
|
||||
)
|
||||
query_embedding = response.data[0]["embedding"]
|
||||
elif OPENAI_AVAILABLE:
|
||||
elif _check_openai():
|
||||
# Fallback to OpenAI client
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
@@ -1437,12 +1505,12 @@ class Memory:
|
||||
"""
|
||||
|
||||
try:
|
||||
if LITELLM_AVAILABLE:
|
||||
if _check_litellm():
|
||||
# Use LiteLLM for consistency with the rest of the codebase
|
||||
import litellm
|
||||
|
||||
# Convert model name if it's in litellm format
|
||||
model_name = llm or "gpt-5-nano"
|
||||
model_name = llm or "gpt-4o-mini"
|
||||
|
||||
response = litellm.completion(
|
||||
model=model_name,
|
||||
@@ -1453,13 +1521,13 @@ class Memory:
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.3
|
||||
)
|
||||
elif OPENAI_AVAILABLE:
|
||||
elif _check_openai():
|
||||
# Fallback to OpenAI client
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=llm or "gpt-5-nano",
|
||||
model=llm or "gpt-4o-mini",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": custom_prompt or default_prompt
|
||||
@@ -1599,3 +1667,50 @@ class Memory:
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Error getting all memories: {e}", logging.ERROR)
|
||||
return []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Learn Integration
|
||||
# -------------------------------------------------------------------------
|
||||
@property
|
||||
def learn(self):
|
||||
"""
|
||||
Get the LearnManager for continuous learning capabilities.
|
||||
|
||||
Returns None if learn is not enabled in config.
|
||||
|
||||
Usage:
|
||||
memory = Memory({"learn": True})
|
||||
memory.learn.capture_persona("User prefers concise responses")
|
||||
memory.learn.capture_insight("User works in data science")
|
||||
"""
|
||||
if self._learn_manager is not None:
|
||||
return self._learn_manager
|
||||
|
||||
if self._learn_config is None or self._learn_config is False:
|
||||
return None
|
||||
|
||||
from .learn import LearnManager
|
||||
from ..config.feature_configs import LearnConfig
|
||||
|
||||
if self._learn_config is True:
|
||||
config = LearnConfig()
|
||||
elif isinstance(self._learn_config, dict):
|
||||
config = LearnConfig(**self._learn_config)
|
||||
elif isinstance(self._learn_config, LearnConfig):
|
||||
config = self._learn_config
|
||||
else:
|
||||
return None
|
||||
|
||||
user_id = self.cfg.get("user_id", "default")
|
||||
self._learn_manager = LearnManager(config=config, user_id=user_id)
|
||||
return self._learn_manager
|
||||
|
||||
def get_learn_context(self) -> str:
|
||||
"""
|
||||
Get learning context suitable for injection into system prompt.
|
||||
|
||||
Returns empty string if learn is not enabled.
|
||||
"""
|
||||
if self.learn is None:
|
||||
return ""
|
||||
return self.learn.to_system_prompt_context()
|
||||
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Memory Protocol Definitions.
|
||||
|
||||
Provides Protocol interfaces that define the minimal contract for memory implementations.
|
||||
This enables:
|
||||
- Mocking memory in tests without real database dependencies
|
||||
- Creating custom memory backends (Redis, PostgreSQL, etc.)
|
||||
- Type checking with static analyzers
|
||||
|
||||
These protocols are lightweight and have zero performance impact.
|
||||
"""
|
||||
from typing import Protocol, runtime_checkable, Optional, Any, Dict, List
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class MemoryProtocol(Protocol):
|
||||
"""
|
||||
Minimal Protocol for memory implementations.
|
||||
|
||||
This defines the essential interface that any memory backend must provide.
|
||||
It enables proper mocking and testing without real database dependencies.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Create a simple in-memory store for testing
|
||||
class InMemoryStore:
|
||||
def __init__(self):
|
||||
self._data = []
|
||||
|
||||
def store_short_term(self, text: str, metadata=None, **kwargs) -> str:
|
||||
self._data.append({"text": text, "type": "short"})
|
||||
return str(len(self._data))
|
||||
|
||||
def search_short_term(self, query: str, limit: int = 5, **kwargs):
|
||||
return self._data[:limit]
|
||||
|
||||
def store_long_term(self, text: str, metadata=None, **kwargs) -> str:
|
||||
self._data.append({"text": text, "type": "long"})
|
||||
return str(len(self._data))
|
||||
|
||||
def search_long_term(self, query: str, limit: int = 5, **kwargs):
|
||||
return self._data[:limit]
|
||||
|
||||
# Use in agent
|
||||
memory: MemoryProtocol = InMemoryStore()
|
||||
```
|
||||
"""
|
||||
|
||||
def store_short_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Store content in short-term memory.
|
||||
|
||||
Args:
|
||||
text: The content to store
|
||||
metadata: Optional metadata dictionary
|
||||
**kwargs: Additional backend-specific parameters
|
||||
|
||||
Returns:
|
||||
An identifier for the stored content
|
||||
"""
|
||||
...
|
||||
|
||||
def search_short_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search short-term memory.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
limit: Maximum number of results
|
||||
**kwargs: Additional backend-specific parameters
|
||||
|
||||
Returns:
|
||||
List of matching memory entries
|
||||
"""
|
||||
...
|
||||
|
||||
def store_long_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
Store content in long-term memory.
|
||||
|
||||
Args:
|
||||
text: The content to store
|
||||
metadata: Optional metadata dictionary
|
||||
**kwargs: Additional backend-specific parameters
|
||||
|
||||
Returns:
|
||||
An identifier for the stored content
|
||||
"""
|
||||
...
|
||||
|
||||
def search_long_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search long-term memory.
|
||||
|
||||
Args:
|
||||
query: The search query
|
||||
limit: Maximum number of results
|
||||
**kwargs: Additional backend-specific parameters
|
||||
|
||||
Returns:
|
||||
List of matching memory entries
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ResettableMemoryProtocol(MemoryProtocol, Protocol):
|
||||
"""
|
||||
Extended Protocol for memory that can be reset/cleared.
|
||||
"""
|
||||
|
||||
def reset_short_term(self) -> None:
|
||||
"""Clear all short-term memory."""
|
||||
...
|
||||
|
||||
def reset_long_term(self) -> None:
|
||||
"""Clear all long-term memory."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncMemoryProtocol(Protocol):
|
||||
"""
|
||||
Async Protocol for memory implementations.
|
||||
|
||||
For memory backends that support async operations.
|
||||
"""
|
||||
|
||||
async def astore_short_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Async store in short-term memory."""
|
||||
...
|
||||
|
||||
async def asearch_short_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Async search short-term memory."""
|
||||
...
|
||||
|
||||
async def astore_long_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Async store in long-term memory."""
|
||||
...
|
||||
|
||||
async def asearch_long_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Async search long-term memory."""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class EntityMemoryProtocol(Protocol):
|
||||
"""
|
||||
Protocol for memory that supports entity storage.
|
||||
"""
|
||||
|
||||
def store_entity(
|
||||
self,
|
||||
name: str,
|
||||
type_: str,
|
||||
desc: str,
|
||||
relations: str = ""
|
||||
) -> str:
|
||||
"""Store an entity in memory."""
|
||||
...
|
||||
|
||||
def get_entity(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Retrieve an entity by name."""
|
||||
...
|
||||
|
||||
|
||||
__all__ = [
|
||||
'MemoryProtocol',
|
||||
'ResettableMemoryProtocol',
|
||||
'AsyncMemoryProtocol',
|
||||
'EntityMemoryProtocol',
|
||||
]
|
||||
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Output Styles Module for PraisonAI Agents.
|
||||
|
||||
Provides configurable output formatting:
|
||||
- Predefined styles (concise, detailed, technical, etc.)
|
||||
- Custom formatting rules
|
||||
- Markdown/plain text/JSON output
|
||||
- Response length control
|
||||
|
||||
Zero Performance Impact:
|
||||
- All imports are lazy loaded via __getattr__
|
||||
- Styles only applied when configured
|
||||
- No overhead when not in use
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.output import OutputStyle, OutputFormatter
|
||||
|
||||
# Use predefined style
|
||||
style = OutputStyle.concise()
|
||||
|
||||
# Apply to agent
|
||||
agent = Agent(
|
||||
instructions="...",
|
||||
output_style=style
|
||||
)
|
||||
|
||||
# Or format manually
|
||||
formatter = OutputFormatter(style)
|
||||
formatted = formatter.format(response)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
# Core classes
|
||||
"OutputStyle",
|
||||
"OutputFormatter",
|
||||
# Style presets
|
||||
"StylePreset",
|
||||
# Configuration
|
||||
"OutputConfig",
|
||||
# Status output (for status preset - no timestamps)
|
||||
"StatusOutput",
|
||||
"enable_status_output",
|
||||
"disable_status_output",
|
||||
"is_status_output_enabled",
|
||||
"get_status_output",
|
||||
# Trace output (for trace preset - with timestamps)
|
||||
"TraceOutput",
|
||||
"enable_trace_output",
|
||||
"disable_trace_output",
|
||||
"is_trace_output_enabled",
|
||||
"get_trace_output",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy load module components to avoid import overhead."""
|
||||
if name == "OutputStyle":
|
||||
from .style import OutputStyle
|
||||
return OutputStyle
|
||||
|
||||
if name == "OutputFormatter":
|
||||
from .formatter import OutputFormatter
|
||||
return OutputFormatter
|
||||
|
||||
if name == "StylePreset":
|
||||
from .style import StylePreset
|
||||
return StylePreset
|
||||
|
||||
if name == "OutputConfig":
|
||||
from .config import OutputConfig
|
||||
return OutputConfig
|
||||
|
||||
# Status output (for status preset - no timestamps)
|
||||
if name == "StatusOutput":
|
||||
from .status import StatusOutput
|
||||
return StatusOutput
|
||||
|
||||
if name == "enable_status_output":
|
||||
from .status import enable_status_output
|
||||
return enable_status_output
|
||||
|
||||
if name == "disable_status_output":
|
||||
from .status import disable_status_output
|
||||
return disable_status_output
|
||||
|
||||
if name == "is_status_output_enabled":
|
||||
from .status import is_status_output_enabled
|
||||
return is_status_output_enabled
|
||||
|
||||
if name == "get_status_output":
|
||||
from .status import get_status_output
|
||||
return get_status_output
|
||||
|
||||
# Trace output (for trace preset - with timestamps)
|
||||
if name == "TraceOutput":
|
||||
from .trace import TraceOutput
|
||||
return TraceOutput
|
||||
|
||||
if name == "enable_trace_output":
|
||||
from .trace import enable_trace_output
|
||||
return enable_trace_output
|
||||
|
||||
if name == "disable_trace_output":
|
||||
from .trace import disable_trace_output
|
||||
return disable_trace_output
|
||||
|
||||
if name == "is_trace_output_enabled":
|
||||
from .trace import is_trace_output_enabled
|
||||
return is_trace_output_enabled
|
||||
|
||||
if name == "get_trace_output":
|
||||
from .trace import get_trace_output
|
||||
return get_trace_output
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Output Configuration for PraisonAI Agents.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class OutputConfig:
|
||||
"""Configuration for output formatting."""
|
||||
enabled: bool = True
|
||||
default_format: str = "markdown" # markdown, plain, json
|
||||
max_length: Optional[int] = None
|
||||
truncate_long_responses: bool = False
|
||||
include_metadata: bool = False
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Output Formatter for PraisonAI Agents.
|
||||
|
||||
Formats output according to style settings.
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from .style import OutputStyle
|
||||
|
||||
|
||||
class OutputFormatter:
|
||||
"""
|
||||
Formats agent output according to style settings.
|
||||
|
||||
Example:
|
||||
style = OutputStyle.concise()
|
||||
formatter = OutputFormatter(style)
|
||||
formatted = formatter.format(response)
|
||||
"""
|
||||
|
||||
def __init__(self, style: Optional[OutputStyle] = None):
|
||||
"""
|
||||
Initialize the formatter.
|
||||
|
||||
Args:
|
||||
style: Output style to use
|
||||
"""
|
||||
self.style = style or OutputStyle()
|
||||
|
||||
def format(self, text: str) -> str:
|
||||
"""
|
||||
Format text according to style.
|
||||
|
||||
Args:
|
||||
text: Text to format
|
||||
|
||||
Returns:
|
||||
Formatted text
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
result = text
|
||||
|
||||
# Apply length limits
|
||||
if self.style.max_length and len(result) > self.style.max_length:
|
||||
result = self._truncate(result, self.style.max_length)
|
||||
|
||||
# Apply format conversion
|
||||
if self.style.format == "plain":
|
||||
result = self._to_plain(result)
|
||||
elif self.style.format == "json":
|
||||
result = self._to_json(result)
|
||||
|
||||
return result
|
||||
|
||||
def _truncate(self, text: str, max_length: int) -> str:
|
||||
"""Truncate text to max length."""
|
||||
if len(text) <= max_length:
|
||||
return text
|
||||
|
||||
# Try to truncate at sentence boundary
|
||||
truncated = text[:max_length]
|
||||
|
||||
# Find last sentence end
|
||||
for end_char in ['. ', '! ', '? ', '\n']:
|
||||
last_end = truncated.rfind(end_char)
|
||||
if last_end > max_length * 0.7: # At least 70% of max
|
||||
return truncated[:last_end + 1].strip() + "..."
|
||||
|
||||
# Fall back to word boundary
|
||||
last_space = truncated.rfind(' ')
|
||||
if last_space > max_length * 0.8:
|
||||
return truncated[:last_space].strip() + "..."
|
||||
|
||||
return truncated.strip() + "..."
|
||||
|
||||
def _to_plain(self, text: str) -> str:
|
||||
"""Convert markdown to plain text."""
|
||||
result = text
|
||||
|
||||
# Remove headers
|
||||
result = re.sub(r'^#{1,6}\s+', '', result, flags=re.MULTILINE)
|
||||
|
||||
# Remove bold/italic
|
||||
result = re.sub(r'\*\*([^*]+)\*\*', r'\1', result)
|
||||
result = re.sub(r'\*([^*]+)\*', r'\1', result)
|
||||
result = re.sub(r'__([^_]+)__', r'\1', result)
|
||||
result = re.sub(r'_([^_]+)_', r'\1', result)
|
||||
|
||||
# Remove code blocks (keep content)
|
||||
result = re.sub(r'```[a-z]*\n?', '', result)
|
||||
|
||||
# Remove inline code
|
||||
result = re.sub(r'`([^`]+)`', r'\1', result)
|
||||
|
||||
# Remove links (keep text)
|
||||
result = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', result)
|
||||
|
||||
# Remove horizontal rules
|
||||
result = re.sub(r'^---+$', '', result, flags=re.MULTILINE)
|
||||
|
||||
# Clean up extra whitespace
|
||||
result = re.sub(r'\n{3,}', '\n\n', result)
|
||||
|
||||
return result.strip()
|
||||
|
||||
def _to_json(self, text: str) -> str:
|
||||
"""Wrap text in JSON structure."""
|
||||
return json.dumps({
|
||||
"response": text,
|
||||
"format": "text"
|
||||
}, indent=2)
|
||||
|
||||
def get_word_count(self, text: str) -> int:
|
||||
"""Get word count of text."""
|
||||
return len(text.split())
|
||||
|
||||
def get_char_count(self, text: str) -> int:
|
||||
"""Get character count of text."""
|
||||
return len(text)
|
||||
|
||||
def estimate_tokens(self, text: str) -> int:
|
||||
"""Estimate token count (rough approximation)."""
|
||||
# Rough estimate: ~4 chars per token
|
||||
return len(text) // 4
|
||||
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
Actions Output Mode for PraisonAI Agents.
|
||||
|
||||
Provides a filtered output mode that shows only:
|
||||
1. Agent lifecycle events (start/end)
|
||||
2. Tool calls (name, args, duration, status)
|
||||
3. Final agent output
|
||||
|
||||
This module integrates with the existing display callback system
|
||||
and uses the existing redaction utilities.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Enable via preset
|
||||
agent = Agent(instructions="...", output="actions")
|
||||
agent.start("Do something")
|
||||
|
||||
# Or via OutputConfig
|
||||
from praisonaiagents.config import OutputConfig
|
||||
agent = Agent(
|
||||
instructions="...",
|
||||
output=OutputConfig(actions_trace=True)
|
||||
)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional, TextIO
|
||||
|
||||
# Global state for actions mode
|
||||
_status_output_enabled = False
|
||||
_status_output: Optional['StatusOutput'] = None
|
||||
_agent_start_times: Dict[str, float] = {}
|
||||
_output_lock = threading.Lock() # Multi-agent safe output lock
|
||||
_ai_call_count = 0 # Track AI call count for context display
|
||||
|
||||
|
||||
def _format_timestamp(ts: float) -> str:
|
||||
"""Format timestamp as HH:MM:SS.mmm."""
|
||||
dt = datetime.fromtimestamp(ts)
|
||||
return dt.strftime("%H:%M:%S.") + f"{int(dt.microsecond / 1000):03d}"
|
||||
|
||||
|
||||
def _format_duration(duration_ms: Optional[float]) -> str:
|
||||
"""Format duration in human-readable form."""
|
||||
if duration_ms is None:
|
||||
return ""
|
||||
if duration_ms < 1000:
|
||||
return f"{duration_ms:.0f}ms"
|
||||
return f"{duration_ms / 1000:.1f}s"
|
||||
|
||||
|
||||
def _truncate(text: str, max_len: int = 50) -> str:
|
||||
"""Truncate text with ellipsis."""
|
||||
if not text:
|
||||
return ""
|
||||
if len(text) <= max_len:
|
||||
return text
|
||||
return text[:max_len - 3] + "..."
|
||||
|
||||
|
||||
class StatusOutput:
|
||||
"""
|
||||
Sink for actions-only output.
|
||||
|
||||
Formats and outputs action events in a clean, readable format.
|
||||
Supports both human-readable text and JSONL output.
|
||||
|
||||
Thread-safe for multi-agent concurrent execution.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file: TextIO = None,
|
||||
format: str = "text", # "text" or "jsonl"
|
||||
redact: bool = True,
|
||||
use_color: bool = True,
|
||||
show_timestamps: bool = True, # NEW: control timestamp display
|
||||
show_metrics: bool = False, # Enable metrics display for debug mode
|
||||
):
|
||||
self._file = file or sys.stderr # Use stderr to not interfere with agent output
|
||||
self._format = format
|
||||
self._redact = redact
|
||||
self._use_color = use_color
|
||||
self._show_timestamps = show_timestamps
|
||||
self._show_metrics = show_metrics
|
||||
self._console = None
|
||||
self._tool_start_times: Dict[str, float] = {}
|
||||
self._lock = threading.Lock() # Per-sink lock for thread safety
|
||||
|
||||
def _get_console(self):
|
||||
"""Get Rich console for colored output."""
|
||||
if self._console is None and self._use_color:
|
||||
try:
|
||||
from rich.console import Console
|
||||
self._console = Console(file=self._file, force_terminal=True)
|
||||
except ImportError:
|
||||
self._console = None
|
||||
return self._console
|
||||
|
||||
def _redact_args(self, args: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Redact sensitive data from args."""
|
||||
if args is None or not self._redact:
|
||||
return args
|
||||
try:
|
||||
from ..trace.redact import redact_dict
|
||||
return redact_dict(args)
|
||||
except ImportError:
|
||||
return args
|
||||
|
||||
def _format_args(self, args: Optional[Dict[str, Any]]) -> str:
|
||||
"""Format tool arguments for display."""
|
||||
if not args:
|
||||
return ""
|
||||
|
||||
redacted = self._redact_args(args)
|
||||
parts = []
|
||||
for k, v in (redacted or {}).items():
|
||||
if isinstance(v, str):
|
||||
v_str = f'"{_truncate(v, 30)}"'
|
||||
else:
|
||||
v_str = _truncate(str(v), 30)
|
||||
parts.append(f"{k}={v_str}")
|
||||
|
||||
return _truncate(", ".join(parts), 80)
|
||||
|
||||
def agent_start(self, agent_name: str) -> None:
|
||||
"""Record agent start."""
|
||||
ts = time.time()
|
||||
_agent_start_times[agent_name] = ts
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl("agent_start", agent_name=agent_name, timestamp=ts)
|
||||
else:
|
||||
self._emit_text(f"▶ Agent:{agent_name} started", ts, "green")
|
||||
|
||||
def agent_end(self, agent_name: str, duration_ms: Optional[float] = None) -> None:
|
||||
"""Record agent end."""
|
||||
ts = time.time()
|
||||
|
||||
# Calculate duration if not provided
|
||||
if duration_ms is None and agent_name in _agent_start_times:
|
||||
start_ts = _agent_start_times.pop(agent_name, None)
|
||||
if start_ts:
|
||||
duration_ms = (ts - start_ts) * 1000
|
||||
|
||||
duration_str = f" [{_format_duration(duration_ms)}]" if duration_ms else ""
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl("agent_end", agent_name=agent_name, timestamp=ts, duration_ms=duration_ms)
|
||||
else:
|
||||
self._emit_text(f"◀ Agent:{agent_name} completed{duration_str}", ts, "green")
|
||||
|
||||
def llm_start(self, model: str = None, agent_name: Optional[str] = None) -> None:
|
||||
"""Record LLM call start."""
|
||||
global _ai_call_count
|
||||
ts = time.time()
|
||||
self._llm_start_time = ts
|
||||
_ai_call_count += 1
|
||||
|
||||
# Context based on call sequence
|
||||
if _ai_call_count == 1:
|
||||
context = "thinking"
|
||||
else:
|
||||
context = "responding"
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl("llm_start", model=model, agent_name=agent_name, timestamp=ts)
|
||||
else:
|
||||
self._emit_text(f"▸ AI → {context}...", ts, "yellow")
|
||||
|
||||
def llm_end(
|
||||
self,
|
||||
duration_ms: Optional[float] = None,
|
||||
agent_name: Optional[str] = None,
|
||||
model: Optional[str] = None,
|
||||
tokens_in: int = 0,
|
||||
tokens_out: int = 0,
|
||||
cost: Optional[float] = None,
|
||||
latency_ms: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Record LLM call end with optional metrics for debug mode."""
|
||||
ts = time.time()
|
||||
|
||||
# Use latency_ms if provided, otherwise calculate from start time
|
||||
if latency_ms is not None:
|
||||
duration_ms = latency_ms
|
||||
elif duration_ms is None and hasattr(self, '_llm_start_time'):
|
||||
start_ts = self._llm_start_time
|
||||
if start_ts:
|
||||
duration_ms = (ts - start_ts) * 1000
|
||||
|
||||
# Track session totals for summary
|
||||
if not hasattr(self, '_session_tokens_in'):
|
||||
self._session_tokens_in = 0
|
||||
self._session_tokens_out = 0
|
||||
self._session_cost = 0.0
|
||||
self._session_llm_calls = 0
|
||||
|
||||
self._session_tokens_in += tokens_in
|
||||
self._session_tokens_out += tokens_out
|
||||
if cost:
|
||||
self._session_cost += cost
|
||||
self._session_llm_calls += 1
|
||||
|
||||
# Only show metrics line in debug mode (when show_metrics is enabled)
|
||||
show_metrics = getattr(self, '_show_metrics', False)
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl("llm_end", agent_name=agent_name, timestamp=ts,
|
||||
duration_ms=duration_ms, model=model,
|
||||
tokens_in=tokens_in, tokens_out=tokens_out, cost=cost)
|
||||
elif show_metrics and (tokens_in > 0 or tokens_out > 0):
|
||||
# Debug mode: show metrics line
|
||||
duration_str = f" [{_format_duration(duration_ms)}]" if duration_ms else ""
|
||||
model_str = model.split('/')[-1] if model else "?" # Short model name
|
||||
cost_str = f" (~${cost:.4f})" if cost else ""
|
||||
|
||||
metrics_line = f" │ 📊 {model_str}: {tokens_in}→{tokens_out} tokens{cost_str}{duration_str}"
|
||||
self._emit_text(metrics_line, ts, "dim", show_timestamp=False)
|
||||
|
||||
def tool_start(self, tool_name: str, tool_args: Optional[Dict[str, Any]] = None, agent_name: Optional[str] = None) -> None:
|
||||
"""Record tool start - stores info for inline display with result."""
|
||||
ts = time.time()
|
||||
self._tool_start_times[tool_name] = ts
|
||||
self._pending_tool_args = tool_args # Store for display with result
|
||||
self._pending_tool_name = tool_name
|
||||
|
||||
# Don't emit anything here - wait for result to show inline
|
||||
|
||||
def tool_end(
|
||||
self,
|
||||
tool_name: str,
|
||||
duration_ms: Optional[float] = None,
|
||||
status: str = "ok",
|
||||
result_summary: Optional[str] = None,
|
||||
agent_name: Optional[str] = None,
|
||||
error_message: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Record tool end with inline display showing tool call and result."""
|
||||
ts = time.time()
|
||||
|
||||
# Calculate duration if not provided
|
||||
if duration_ms is None and tool_name in self._tool_start_times:
|
||||
start_ts = self._tool_start_times.pop(tool_name, None)
|
||||
if start_ts:
|
||||
duration_ms = (ts - start_ts) * 1000
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl(
|
||||
"tool_end",
|
||||
tool_name=tool_name,
|
||||
duration_ms=duration_ms,
|
||||
status=status,
|
||||
result_summary=_truncate(result_summary, 200) if result_summary else None,
|
||||
agent_name=agent_name,
|
||||
error_message=error_message,
|
||||
timestamp=ts,
|
||||
)
|
||||
else:
|
||||
# Build inline tool call with result: │ → tool(args) → result
|
||||
args_str = ""
|
||||
if hasattr(self, '_pending_tool_args') and self._pending_tool_args:
|
||||
args_str = self._format_args(self._pending_tool_args)
|
||||
|
||||
result_str = ""
|
||||
if result_summary:
|
||||
result_str = f" → {_truncate(result_summary, 50)}"
|
||||
elif error_message:
|
||||
result_str = f" → ✗ {_truncate(error_message, 50)}"
|
||||
|
||||
color = "cyan" if status == "ok" else "red"
|
||||
self._emit_text(f" │ → {tool_name}({args_str}){result_str}", ts, color)
|
||||
|
||||
def output(self, content: str, agent_name: Optional[str] = None) -> None:
|
||||
"""Record final output."""
|
||||
ts = time.time()
|
||||
|
||||
if self._format == "jsonl":
|
||||
self._emit_jsonl("output", content=_truncate(content, 500), agent_name=agent_name, timestamp=ts)
|
||||
else:
|
||||
separator = "─" * 50
|
||||
self._emit_text(separator, ts, "dim", show_timestamp=False)
|
||||
self._emit_text("Final Output:", ts, "bold", show_timestamp=False)
|
||||
# Print actual content without truncation for final output
|
||||
with self._lock: # Thread-safe output
|
||||
print(content, file=self._file)
|
||||
|
||||
def _emit_text(self, message: str, ts: float, style: str = None, show_timestamp: bool = True) -> None:
|
||||
"""Emit a text line. Thread-safe for multi-agent execution."""
|
||||
# Respect both instance-level and per-call timestamp settings
|
||||
should_show_ts = show_timestamp and self._show_timestamps
|
||||
ts_str = f"[{_format_timestamp(ts)}] " if should_show_ts else ""
|
||||
|
||||
with self._lock: # Thread-safe output
|
||||
console = self._get_console()
|
||||
if console and self._use_color:
|
||||
if style:
|
||||
console.print(f"[dim]{ts_str}[/dim][{style}]{message}[/{style}]")
|
||||
else:
|
||||
console.print(f"[dim]{ts_str}[/dim]{message}")
|
||||
else:
|
||||
print(f"{ts_str}{message}", file=self._file)
|
||||
|
||||
def _emit_jsonl(self, event_type: str, **kwargs) -> None:
|
||||
"""Emit a JSONL line. Thread-safe for multi-agent execution."""
|
||||
import json
|
||||
data = {"event": event_type, **{k: v for k, v in kwargs.items() if v is not None}}
|
||||
with self._lock: # Thread-safe output
|
||||
print(json.dumps(data, default=str), file=self._file)
|
||||
|
||||
|
||||
def enable_status_output(
|
||||
file: TextIO = None,
|
||||
format: str = "text",
|
||||
redact: bool = True,
|
||||
use_color: bool = True,
|
||||
show_timestamps: bool = True,
|
||||
show_metrics: bool = False, # Enable metrics for debug mode
|
||||
) -> StatusOutput:
|
||||
"""
|
||||
Enable actions output mode globally.
|
||||
|
||||
This registers callbacks with the display system to capture
|
||||
tool calls and agent lifecycle events.
|
||||
|
||||
Args:
|
||||
file: Output file (default: stderr)
|
||||
format: Output format ("text" or "jsonl")
|
||||
redact: Whether to redact sensitive data (default: True)
|
||||
use_color: Whether to use colored output (default: True)
|
||||
show_timestamps: Whether to show timestamps (default: True)
|
||||
show_metrics: Whether to show token/cost metrics (default: False)
|
||||
|
||||
Returns:
|
||||
StatusOutput instance for programmatic access
|
||||
"""
|
||||
global _status_output_enabled, _status_output, _ai_call_count
|
||||
|
||||
# Reset AI call counter for new agent run
|
||||
_ai_call_count = 0
|
||||
|
||||
_status_output = StatusOutput(
|
||||
file=file,
|
||||
format=format,
|
||||
redact=redact,
|
||||
use_color=use_color,
|
||||
show_timestamps=show_timestamps,
|
||||
show_metrics=show_metrics,
|
||||
)
|
||||
_status_output_enabled = True
|
||||
|
||||
# Register callbacks with the display system
|
||||
from ..main import register_display_callback
|
||||
|
||||
def on_tool_call(message: str = None, tool_name: str = None, tool_input: dict = None, tool_output: str = None, **kwargs):
|
||||
"""Callback for tool calls."""
|
||||
if not _status_output_enabled or _status_output is None:
|
||||
return
|
||||
|
||||
if tool_name:
|
||||
# Emit tool_start when we see a tool call
|
||||
_status_output.tool_start(tool_name, tool_input)
|
||||
# Emit tool_end immediately since we have the output
|
||||
if tool_output is not None:
|
||||
_status_output.tool_end(
|
||||
tool_name,
|
||||
status="ok",
|
||||
result_summary=str(tool_output)[:200] if tool_output else None,
|
||||
)
|
||||
|
||||
def on_interaction(message: str = None, response: str = None, agent_name: str = None, generation_time: float = None, **kwargs):
|
||||
"""Callback for agent interactions (final output)."""
|
||||
if not _status_output_enabled or _status_output is None:
|
||||
return
|
||||
|
||||
# Only emit output for final responses
|
||||
if response:
|
||||
_status_output.output(response, agent_name)
|
||||
|
||||
def on_error(message: str = None, **kwargs):
|
||||
"""Callback for errors."""
|
||||
if not _status_output_enabled or _status_output is None:
|
||||
return
|
||||
|
||||
if message:
|
||||
ts = time.time()
|
||||
_status_output._emit_text(f"✗ Error: {message}", ts, "red")
|
||||
|
||||
def on_llm_start(model: str = None, agent_name: str = None, **kwargs):
|
||||
"""Callback for LLM calls."""
|
||||
if not _status_output_enabled or _status_output is None:
|
||||
return
|
||||
|
||||
_status_output.llm_start(model=model, agent_name=agent_name)
|
||||
|
||||
def on_llm_end(model: str = None, tokens_in: int = 0, tokens_out: int = 0, cost: float = None, latency_ms: float = None, **kwargs):
|
||||
"""Callback for LLM call completion with metrics."""
|
||||
if not _status_output_enabled or _status_output is None:
|
||||
return
|
||||
|
||||
_status_output.llm_end(model=model, tokens_in=tokens_in, tokens_out=tokens_out, cost=cost, latency_ms=latency_ms)
|
||||
|
||||
# Register the callbacks
|
||||
register_display_callback('tool_call', on_tool_call)
|
||||
register_display_callback('interaction', on_interaction)
|
||||
register_display_callback('error', on_error)
|
||||
register_display_callback('llm_start', on_llm_start)
|
||||
register_display_callback('llm_end', on_llm_end)
|
||||
|
||||
return _status_output
|
||||
|
||||
|
||||
def disable_status_output() -> None:
|
||||
"""Disable actions output mode."""
|
||||
global _status_output_enabled, _status_output
|
||||
_status_output_enabled = False
|
||||
_status_output = None
|
||||
|
||||
|
||||
def is_status_output_enabled() -> bool:
|
||||
"""Check if actions mode is enabled."""
|
||||
return _status_output_enabled
|
||||
|
||||
|
||||
def get_status_output() -> Optional[StatusOutput]:
|
||||
"""Get the current actions sink."""
|
||||
return _status_output
|
||||
|
||||
|
||||
__all__ = [
|
||||
"StatusOutput",
|
||||
"enable_status_output",
|
||||
"disable_status_output",
|
||||
"is_status_output_enabled",
|
||||
"get_status_output",
|
||||
]
|
||||
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
Output Style for PraisonAI Agents.
|
||||
|
||||
Defines output style configurations.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Any, List
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class StylePreset(str, Enum):
|
||||
"""Predefined output styles."""
|
||||
CONCISE = "concise"
|
||||
DETAILED = "detailed"
|
||||
TECHNICAL = "technical"
|
||||
CONVERSATIONAL = "conversational"
|
||||
STRUCTURED = "structured"
|
||||
MINIMAL = "minimal"
|
||||
|
||||
|
||||
@dataclass
|
||||
class OutputStyle:
|
||||
"""
|
||||
Output style configuration.
|
||||
|
||||
Controls how agent responses are formatted.
|
||||
"""
|
||||
name: str = "default"
|
||||
preset: Optional[StylePreset] = None
|
||||
|
||||
# Length control
|
||||
max_length: Optional[int] = None
|
||||
target_length: Optional[int] = None # Soft target
|
||||
|
||||
# Format settings
|
||||
format: str = "markdown" # markdown, plain, json, html
|
||||
use_headers: bool = True
|
||||
use_lists: bool = True
|
||||
use_code_blocks: bool = True
|
||||
|
||||
# Tone settings
|
||||
tone: str = "professional" # professional, casual, technical, friendly
|
||||
verbosity: str = "normal" # minimal, normal, verbose
|
||||
|
||||
# Content settings
|
||||
include_examples: bool = True
|
||||
include_explanations: bool = True
|
||||
include_caveats: bool = True
|
||||
|
||||
# Custom instructions
|
||||
custom_instructions: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.preset:
|
||||
self._apply_preset(self.preset)
|
||||
|
||||
def _apply_preset(self, preset: StylePreset):
|
||||
"""Apply preset settings."""
|
||||
if preset == StylePreset.CONCISE:
|
||||
self.verbosity = "minimal"
|
||||
self.include_examples = False
|
||||
self.include_caveats = False
|
||||
self.target_length = 500
|
||||
|
||||
elif preset == StylePreset.DETAILED:
|
||||
self.verbosity = "verbose"
|
||||
self.include_examples = True
|
||||
self.include_explanations = True
|
||||
self.include_caveats = True
|
||||
|
||||
elif preset == StylePreset.TECHNICAL:
|
||||
self.tone = "technical"
|
||||
self.use_code_blocks = True
|
||||
self.include_examples = True
|
||||
|
||||
elif preset == StylePreset.CONVERSATIONAL:
|
||||
self.tone = "friendly"
|
||||
self.use_headers = False
|
||||
self.format = "plain"
|
||||
|
||||
elif preset == StylePreset.STRUCTURED:
|
||||
self.use_headers = True
|
||||
self.use_lists = True
|
||||
self.format = "markdown"
|
||||
|
||||
elif preset == StylePreset.MINIMAL:
|
||||
self.verbosity = "minimal"
|
||||
self.use_headers = False
|
||||
self.use_lists = False
|
||||
self.include_examples = False
|
||||
self.include_explanations = False
|
||||
self.include_caveats = False
|
||||
self.target_length = 200
|
||||
|
||||
@classmethod
|
||||
def concise(cls) -> "OutputStyle":
|
||||
"""Create a concise style."""
|
||||
return cls(name="concise", preset=StylePreset.CONCISE)
|
||||
|
||||
@classmethod
|
||||
def detailed(cls) -> "OutputStyle":
|
||||
"""Create a detailed style."""
|
||||
return cls(name="detailed", preset=StylePreset.DETAILED)
|
||||
|
||||
@classmethod
|
||||
def technical(cls) -> "OutputStyle":
|
||||
"""Create a technical style."""
|
||||
return cls(name="technical", preset=StylePreset.TECHNICAL)
|
||||
|
||||
@classmethod
|
||||
def conversational(cls) -> "OutputStyle":
|
||||
"""Create a conversational style."""
|
||||
return cls(name="conversational", preset=StylePreset.CONVERSATIONAL)
|
||||
|
||||
@classmethod
|
||||
def structured(cls) -> "OutputStyle":
|
||||
"""Create a structured style."""
|
||||
return cls(name="structured", preset=StylePreset.STRUCTURED)
|
||||
|
||||
@classmethod
|
||||
def minimal(cls) -> "OutputStyle":
|
||||
"""Create a minimal style."""
|
||||
return cls(name="minimal", preset=StylePreset.MINIMAL)
|
||||
|
||||
def get_system_prompt_addition(self) -> str:
|
||||
"""Generate system prompt addition for this style."""
|
||||
parts = []
|
||||
|
||||
# Verbosity
|
||||
if self.verbosity == "minimal":
|
||||
parts.append("Be concise and direct. Avoid unnecessary elaboration.")
|
||||
elif self.verbosity == "verbose":
|
||||
parts.append("Provide detailed explanations with thorough coverage.")
|
||||
|
||||
# Tone
|
||||
if self.tone == "technical":
|
||||
parts.append("Use technical language appropriate for developers.")
|
||||
elif self.tone == "friendly":
|
||||
parts.append("Use a friendly, conversational tone.")
|
||||
elif self.tone == "casual":
|
||||
parts.append("Keep the tone casual and approachable.")
|
||||
|
||||
# Format
|
||||
if self.format == "markdown":
|
||||
parts.append("Format responses using Markdown.")
|
||||
elif self.format == "plain":
|
||||
parts.append("Use plain text without special formatting.")
|
||||
elif self.format == "json":
|
||||
parts.append("Return responses as JSON when appropriate.")
|
||||
|
||||
# Length
|
||||
if self.target_length:
|
||||
parts.append(f"Target response length: ~{self.target_length} characters.")
|
||||
if self.max_length:
|
||||
parts.append(f"Maximum response length: {self.max_length} characters.")
|
||||
|
||||
# Content
|
||||
if not self.include_examples:
|
||||
parts.append("Skip examples unless specifically requested.")
|
||||
if not self.include_caveats:
|
||||
parts.append("Skip caveats and warnings unless critical.")
|
||||
|
||||
# Custom
|
||||
if self.custom_instructions:
|
||||
parts.append(self.custom_instructions)
|
||||
|
||||
return " ".join(parts)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"name": self.name,
|
||||
"preset": self.preset.value if self.preset else None,
|
||||
"max_length": self.max_length,
|
||||
"target_length": self.target_length,
|
||||
"format": self.format,
|
||||
"tone": self.tone,
|
||||
"verbosity": self.verbosity,
|
||||
"use_headers": self.use_headers,
|
||||
"use_lists": self.use_lists,
|
||||
"use_code_blocks": self.use_code_blocks,
|
||||
"include_examples": self.include_examples,
|
||||
"include_explanations": self.include_explanations,
|
||||
"include_caveats": self.include_caveats
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "OutputStyle":
|
||||
"""Create from dictionary."""
|
||||
preset = None
|
||||
if data.get("preset"):
|
||||
preset = StylePreset(data["preset"])
|
||||
|
||||
return cls(
|
||||
name=data.get("name", "default"),
|
||||
preset=preset,
|
||||
max_length=data.get("max_length"),
|
||||
target_length=data.get("target_length"),
|
||||
format=data.get("format", "markdown"),
|
||||
tone=data.get("tone", "professional"),
|
||||
verbosity=data.get("verbosity", "normal"),
|
||||
use_headers=data.get("use_headers", True),
|
||||
use_lists=data.get("use_lists", True),
|
||||
use_code_blocks=data.get("use_code_blocks", True),
|
||||
include_examples=data.get("include_examples", True),
|
||||
include_explanations=data.get("include_explanations", True),
|
||||
include_caveats=data.get("include_caveats", True)
|
||||
)
|
||||
@@ -0,0 +1,345 @@
|
||||
"""
|
||||
Status Output Mode for PraisonAI Agents.
|
||||
|
||||
Provides a clean, simple output mode that shows:
|
||||
1. LLM call status (calling, completed)
|
||||
2. Tool execution status (name, duration, result)
|
||||
3. Final response (no boxes, just text)
|
||||
|
||||
This is ideal for:
|
||||
- CLI users who want minimal but informative output
|
||||
- External apps that parse status updates
|
||||
- Logging and monitoring
|
||||
|
||||
Usage:
|
||||
from praisonaiagents import Agent
|
||||
|
||||
# Enable via preset
|
||||
agent = Agent(instructions="...", output="status")
|
||||
agent.start("Do something")
|
||||
|
||||
# Output:
|
||||
# [13:45:02] Calling LLM (gpt-4o-mini)...
|
||||
# [13:45:03] Executing tool: get_weather
|
||||
# [13:45:03] Tool completed (0.1s)
|
||||
# Response: The weather in Paris is sunny.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional, TextIO
|
||||
|
||||
# Global state for status mode
|
||||
_trace_output_enabled = False
|
||||
_trace_output: Optional['TraceOutput'] = None
|
||||
_output_lock = threading.Lock() # Multi-agent safe output lock
|
||||
|
||||
|
||||
def _format_timestamp() -> str:
|
||||
"""Format current time as HH:MM:SS."""
|
||||
return datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
|
||||
class TraceOutput:
|
||||
"""
|
||||
Sink for clean status output.
|
||||
|
||||
Formats and outputs status events in a simple, readable format.
|
||||
No boxes, no panels, just timestamped status lines.
|
||||
|
||||
Thread-safe for multi-agent concurrent execution.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file: TextIO = None,
|
||||
use_color: bool = True,
|
||||
show_timestamps: bool = True,
|
||||
use_markdown: bool = True,
|
||||
):
|
||||
self._file = file or sys.stderr
|
||||
self._use_color = use_color
|
||||
self._show_timestamps = show_timestamps
|
||||
self._use_markdown = use_markdown
|
||||
self._console = None
|
||||
self._lock = threading.Lock()
|
||||
self._tool_start_times: Dict[str, float] = {}
|
||||
self._llm_start_time: Optional[float] = None
|
||||
|
||||
def _get_console(self):
|
||||
"""Get Rich console for colored output."""
|
||||
if self._console is None and self._use_color:
|
||||
try:
|
||||
from rich.console import Console
|
||||
self._console = Console(file=self._file, force_terminal=True)
|
||||
except ImportError:
|
||||
self._console = None
|
||||
return self._console
|
||||
|
||||
def _emit(self, message: str, style: str = None) -> None:
|
||||
"""Emit a status line. Thread-safe."""
|
||||
ts_str = f"[{_format_timestamp()}] " if self._show_timestamps else ""
|
||||
|
||||
with self._lock:
|
||||
console = self._get_console()
|
||||
if console and self._use_color:
|
||||
if style:
|
||||
console.print(f"[dim]{ts_str}[/dim][{style}]{message}[/{style}]")
|
||||
else:
|
||||
console.print(f"[dim]{ts_str}[/dim]{message}")
|
||||
else:
|
||||
print(f"{ts_str}{message}", file=self._file)
|
||||
|
||||
def llm_start(self, model: str = None) -> None:
|
||||
"""Record LLM call start."""
|
||||
self._llm_start_time = time.time()
|
||||
model_str = f" ({model})" if model else ""
|
||||
self._emit(f"Calling LLM{model_str}...", "cyan")
|
||||
|
||||
def llm_end(
|
||||
self,
|
||||
duration_ms: float = None,
|
||||
model: str = None,
|
||||
tokens_in: int = 0,
|
||||
tokens_out: int = 0,
|
||||
cost: float = None,
|
||||
) -> None:
|
||||
"""Record LLM call end with optional metrics."""
|
||||
# If duration_ms was passed and is positive, use it directly
|
||||
# Otherwise calculate from internal tracking
|
||||
if duration_ms is not None and duration_ms > 0:
|
||||
pass # Use the passed value
|
||||
elif self._llm_start_time:
|
||||
duration_ms = (time.time() - self._llm_start_time) * 1000
|
||||
|
||||
duration_str = f" [{duration_ms/1000:.1f}s]" if duration_ms and duration_ms > 0 else ""
|
||||
|
||||
# Show metrics if tokens are available
|
||||
if tokens_in > 0 or tokens_out > 0:
|
||||
model_str = model.split('/')[-1] if model else "?"
|
||||
cost_str = f" (~${cost:.4f})" if cost and cost > 0 else ""
|
||||
metrics_line = f" │ 📊 {model_str}: {tokens_in}→{tokens_out} tokens{cost_str}{duration_str}"
|
||||
self._emit(metrics_line, "dim")
|
||||
else:
|
||||
self._emit(f"LLM responded{duration_str}", "green")
|
||||
self._llm_start_time = None
|
||||
|
||||
def tool_start(self, tool_name: str, tool_args: Dict[str, Any] = None) -> None:
|
||||
"""Record tool execution start."""
|
||||
self._tool_start_times[tool_name] = time.time()
|
||||
|
||||
# Format args compactly
|
||||
args_str = ""
|
||||
if tool_args:
|
||||
parts = []
|
||||
for k, v in tool_args.items():
|
||||
v_str = str(v)
|
||||
if len(v_str) > 30:
|
||||
v_str = v_str[:27] + "..."
|
||||
parts.append(f"{k}={repr(v_str) if isinstance(v, str) else v_str}")
|
||||
args_str = f"({', '.join(parts)})"
|
||||
|
||||
self._emit(f"▸ {tool_name}{args_str}", "blue")
|
||||
|
||||
def tool_end(
|
||||
self,
|
||||
tool_name: str,
|
||||
duration_ms: float = None,
|
||||
result: str = None,
|
||||
success: bool = True,
|
||||
) -> None:
|
||||
"""Record tool execution end."""
|
||||
# Calculate duration if not provided
|
||||
if duration_ms is None and tool_name in self._tool_start_times:
|
||||
start_ts = self._tool_start_times.pop(tool_name, None)
|
||||
if start_ts:
|
||||
duration_ms = (time.time() - start_ts) * 1000
|
||||
|
||||
duration_str = f" [{duration_ms/1000:.1f}s]" if duration_ms else ""
|
||||
status_icon = "✓" if success else "✗"
|
||||
|
||||
# Show result inline if short
|
||||
result_str = ""
|
||||
if result:
|
||||
result_preview = str(result)
|
||||
if len(result_preview) > 50:
|
||||
result_preview = result_preview[:47] + "..."
|
||||
result_str = f" → {result_preview}"
|
||||
|
||||
color = "green" if success else "red"
|
||||
self._emit(f" └─ {tool_name}{result_str}{duration_str} {status_icon}", color)
|
||||
|
||||
def response(self, content: str, agent_name: str = None) -> None:
|
||||
"""Output final response with optional Markdown rendering."""
|
||||
with self._lock:
|
||||
console = self._get_console()
|
||||
|
||||
# Separator line
|
||||
if console and self._use_color:
|
||||
console.print("")
|
||||
console.print("[bold]Response:[/bold]")
|
||||
|
||||
# Render as Markdown if enabled
|
||||
if self._use_markdown:
|
||||
try:
|
||||
from rich.markdown import Markdown
|
||||
console.print(Markdown(content))
|
||||
except ImportError:
|
||||
# Fallback if markdown-it not available
|
||||
console.print(content)
|
||||
else:
|
||||
console.print(content)
|
||||
else:
|
||||
print("", file=self._file)
|
||||
print("Response:", file=self._file)
|
||||
print(content, file=self._file)
|
||||
|
||||
def error(self, message: str) -> None:
|
||||
"""Output error message."""
|
||||
self._emit(f"✗ Error: {message}", "red")
|
||||
|
||||
|
||||
def enable_trace_output(
|
||||
file: TextIO = None,
|
||||
use_color: bool = True,
|
||||
show_timestamps: bool = True,
|
||||
use_markdown: bool = True,
|
||||
) -> TraceOutput:
|
||||
"""
|
||||
Enable status output mode globally.
|
||||
|
||||
This registers callbacks with the display system to capture
|
||||
tool calls and output clean status updates.
|
||||
|
||||
Args:
|
||||
file: Output file (default: stderr)
|
||||
use_color: Whether to use colored output (default: True)
|
||||
show_timestamps: Whether to show timestamps (default: True)
|
||||
use_markdown: Whether to render response as Markdown (default: True)
|
||||
|
||||
Returns:
|
||||
TraceOutput instance
|
||||
"""
|
||||
global _trace_output_enabled, _trace_output
|
||||
|
||||
_trace_output = TraceOutput(
|
||||
file=file,
|
||||
use_color=use_color,
|
||||
show_timestamps=show_timestamps,
|
||||
use_markdown=use_markdown,
|
||||
)
|
||||
_trace_output_enabled = True
|
||||
|
||||
# Register callbacks with the display system
|
||||
from ..main import register_display_callback
|
||||
|
||||
def on_tool_call(
|
||||
message: str = None,
|
||||
tool_name: str = None,
|
||||
tool_input: dict = None,
|
||||
tool_output: str = None,
|
||||
**kwargs
|
||||
):
|
||||
"""Callback for tool calls."""
|
||||
if not _trace_output_enabled or _trace_output is None:
|
||||
return
|
||||
|
||||
# Handle structured tool call (when tool_name is set)
|
||||
if tool_name:
|
||||
_trace_output.tool_start(tool_name, tool_input)
|
||||
# If we have output, call tool_end immediately for inline display
|
||||
if tool_output is not None:
|
||||
_trace_output.tool_end(tool_name, result=str(tool_output)[:200], success=True)
|
||||
return
|
||||
|
||||
# Handle legacy message format (for backward compatibility)
|
||||
if message:
|
||||
# Check for "Function X returned: Y" pattern
|
||||
if "returned:" in message.lower():
|
||||
import re
|
||||
match = re.match(r'Function (\w+) returned: (.+)', message, re.IGNORECASE)
|
||||
if match:
|
||||
func_name = match.group(1)
|
||||
result = match.group(2).strip('"')
|
||||
_trace_output.tool_end(func_name, result=result, success=True)
|
||||
# "Calling function" message is already shown via tool_start, skip
|
||||
|
||||
def on_interaction(
|
||||
message: str = None,
|
||||
response: str = None,
|
||||
agent_name: str = None,
|
||||
generation_time: float = None,
|
||||
**kwargs
|
||||
):
|
||||
"""Callback for agent interactions (final output)."""
|
||||
if not _trace_output_enabled or _trace_output is None:
|
||||
return
|
||||
|
||||
# Output final response
|
||||
if response:
|
||||
_trace_output.response(response, agent_name)
|
||||
|
||||
def on_error(message: str = None, **kwargs):
|
||||
"""Callback for errors."""
|
||||
if not _trace_output_enabled or _trace_output is None:
|
||||
return
|
||||
|
||||
if message:
|
||||
_trace_output.error(message)
|
||||
|
||||
def on_llm_start(model: str = None, agent_name: str = None, **kwargs):
|
||||
"""Callback for LLM call start."""
|
||||
if not _trace_output_enabled or _trace_output is None:
|
||||
return
|
||||
|
||||
_trace_output.llm_start(model=model)
|
||||
|
||||
def on_llm_end(model: str = None, tokens_in: int = 0, tokens_out: int = 0, cost: float = None, latency_ms: float = None, **kwargs):
|
||||
"""Callback for LLM call completion with optional metrics."""
|
||||
if not _trace_output_enabled or _trace_output is None:
|
||||
return
|
||||
|
||||
_trace_output.llm_end(
|
||||
duration_ms=latency_ms,
|
||||
model=model,
|
||||
tokens_in=tokens_in,
|
||||
tokens_out=tokens_out,
|
||||
cost=cost
|
||||
)
|
||||
|
||||
# Register the callbacks
|
||||
register_display_callback('tool_call', on_tool_call)
|
||||
register_display_callback('interaction', on_interaction)
|
||||
register_display_callback('error', on_error)
|
||||
register_display_callback('llm_start', on_llm_start)
|
||||
register_display_callback('llm_end', on_llm_end)
|
||||
|
||||
return _trace_output
|
||||
|
||||
|
||||
def disable_trace_output() -> None:
|
||||
"""Disable status output mode."""
|
||||
global _trace_output_enabled, _trace_output
|
||||
_trace_output_enabled = False
|
||||
_trace_output = None
|
||||
|
||||
|
||||
def is_trace_output_enabled() -> bool:
|
||||
"""Check if status mode is enabled."""
|
||||
return _trace_output_enabled
|
||||
|
||||
|
||||
def get_trace_output() -> Optional[TraceOutput]:
|
||||
"""Get the current status sink."""
|
||||
return _trace_output
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TraceOutput",
|
||||
"enable_trace_output",
|
||||
"disable_trace_output",
|
||||
"is_trace_output_enabled",
|
||||
"get_trace_output",
|
||||
]
|
||||
Loaded 100 of 395 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user