Compare commits

..
Author SHA1 Message Date
MervinPraison fe87b9514e feat: add deep profiling benchmarks for Agent initialization
- deep_profile.py: cProfile analysis for 5000 agent creations
- deep_profile_bench.py: Combined timing and profiling for 1000 agents
- Helps identify performance bottlenecks in Agent.__init__
2026-04-02 19:49:16 +01:00
MervinPraison fec0b5fa32 feat: add @lru_cache to get_defaults_config for performance
- Cache defaults config to avoid repeated file I/O
- Add performance test files for benchmarking
- Further optimizes Agent initialization performance
2026-04-02 19:17:03 +01:00
MervinPraison c8a3881dda feat: JIT hydration optimization for Agent initialization
- Store raw params in __init__ for lazy config resolution
- Update OUTPUT, EXECUTION, CACHING, MEMORY, KNOWLEDGE, PLANNING, REFLECTION, WEB to use raw params
- Reduce eager config resolution overhead
- Benchmark shows 30µs initialization (down from previous baseline)
- Add smoke test for JIT hydration
- Phase 1 of incremental optimization plan

Fixes #performance
2026-04-02 18:51:31 +01:00
371 changed files with 2377 additions and 36645 deletions

No files matched your search

+28 -57
View File
@@ -70,8 +70,6 @@ runs:
- name: Install GitHub MCP Server
shell: bash
env:
INPUT_GITHUB_TOKEN: ${{ inputs.github_token }}
run: |
claude mcp add-json github '{
"command": "docker",
@@ -84,7 +82,7 @@ runs:
"ghcr.io/github/github-mcp-server:sha-ff3036d"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "'"${INPUT_GITHUB_TOKEN}"'"
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ inputs.github_token }}"
}
}'
@@ -209,44 +207,21 @@ runs:
User Request:
$USER_REQUEST
Please analyze the request and provide helpful assistance.
Please analyze the request and provide helpful assistance. You have access to repository tools and can help with:
- Code analysis and implementation
- GitHub workflows and automation
- Pull request reviews and feedback
- Issue resolution and bug fixes
- Documentation updates
- Testing and deployment
CRITICAL INSTRUCTION: If your task involves modifying codebase files to add a feature or fix a bug:
STEP 0 — SETUP GIT IDENTITY:
git config user.name "MervinPraison"
git config user.email "[email protected]"
STEP 1 — READ GUIDELINES:
MANDATORY: Before writing any code, you MUST carefully read "src/praisonai-agents/AGENTS.md" to understand this repository's extremely specific Architecture, Protocol-First design rules, and Agent-Centric Philosophy.
STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code):
Before implementing anything, answer these questions:
- CORE vs WRAPPER vs TOOLS ROUTING:
1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations.
2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies.
3. Tools (PraisonAI-Tools): Specific integrations, external tools (e.g. SurrealDB, Slack), and community extensions MUST go to the `MervinPraison/PraisonAI-Tools` repository. If the feature is a tool, clone `https://github.com/MervinPraison/PraisonAI-Tools`, implement it there, and create the PR using `gh pr create -R MervinPraison/PraisonAI-Tools`.
- Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory).
- Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition.
- Does it add new dependencies? Only optional deps allowed, must be lazy-imported.
- Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead.
- Is there a name collision with existing exports in __init__.py?
If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR.
STEP 3 — IMPLEMENT:
- Create a new git branch appropriately named (e.g. \`feature/issue-<number>\`)
- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper
- Keep changes small and backward-compatible
STEP 4 — TEST:
- Run: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
- Ensure no regressions
STEP 5 — CREATE PR:
- Commit with descriptive message, push the branch
- Use the GitHub CLI (\`gh pr create --title "..." --body "Fixes #<number>"\`) to open a Pull Request
CRITICAL: You MUST create the PR automatically using \`gh pr create\`. Do NOT just provide a link.
1. ALWAYS create a new git branch appropriately named (e.g. \`feature/issue-<number>\`).
2. MANDATORY: Before writing any code, you MUST carefully read "src/praisonai-agents/AGENTS.md" to understand this repository's extremely specific Architecture, Protocol-First design rules, and Agent-Centric Philosophy.
3. Make your necessary code changes honoring the rules in AGENTS.md.
4. Commit your changes and push the branch to the remote repository.
5. Use the GitHub CLI (\`gh pr create --title "..." --body "Fixes #<number>"\`) to open a Pull Request containing your work.
Respond naturally and helpfully to the user's request using the available tools.
EOF
@@ -256,14 +231,6 @@ runs:
- name: Run Claude Code
if: env.SKIP_EXECUTION != 'true'
shell: bash
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
GITHUB_TOKEN: ${{ inputs.github_token }}
INPUT_MAX_TURNS: ${{ inputs.max_turns }}
INPUT_ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
INPUT_DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }}
run: |
echo "🚀 Running Claude Code with GitHub context..."
@@ -271,26 +238,30 @@ runs:
CMD_ARGS=("-p" "--verbose" "--output-format" "stream-json")
# Add max turns if specified
if [[ -n "$INPUT_MAX_TURNS" ]]; then
CMD_ARGS+=("--max-turns" "$INPUT_MAX_TURNS")
if [[ -n "${{ inputs.max_turns }}" ]]; then
CMD_ARGS+=("--max-turns" "${{ inputs.max_turns }}")
fi
# Add allowed tools (include GitHub tools by default)
TOOLS="mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues,mcp__github__create_comment,Read,Write,Edit,Bash"
if [[ -n "$INPUT_ALLOWED_TOOLS" ]]; then
TOOLS="$TOOLS,$INPUT_ALLOWED_TOOLS"
if [[ -n "${{ inputs.allowed_tools }}" ]]; then
TOOLS="$TOOLS,${{ inputs.allowed_tools }}"
fi
CMD_ARGS+=("--allowedTools" "$TOOLS")
# Add disallowed tools
if [[ -n "$INPUT_DISALLOWED_TOOLS" ]]; then
CMD_ARGS+=("--disallowedTools" "$INPUT_DISALLOWED_TOOLS")
if [[ -n "${{ inputs.disallowed_tools }}" ]]; then
CMD_ARGS+=("--disallowedTools" "${{ inputs.disallowed_tools }}")
fi
echo "📝 Executing Claude Code with prompt from file..."
# Execute Claude Code with timeout, using stdin for the prompt
TIMEOUT_SECONDS=$(($INPUT_TIMEOUT_MINUTES * 60))
timeout $TIMEOUT_SECONDS claude "${CMD_ARGS[@]}" < "$PROMPT_FILE"
TIMEOUT_SECONDS=$((${{ inputs.timeout_minutes }} * 60))
timeout $TIMEOUT_SECONDS claude "${CMD_ARGS[@]}" < "${{ env.PROMPT_FILE }}"
echo "✅ Claude Code execution completed"
echo "✅ Claude Code execution completed"
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
GITHUB_TOKEN: ${{ inputs.github_token }}
@@ -19,7 +19,6 @@ runs:
- name: Checkout repository code
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Create prompt
+5 -7
View File
@@ -57,15 +57,13 @@ runs:
- name: Install Gemini CLI
id: install
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
if [[ "$INPUT_VERSION" == "latest" || "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?$ ]]; then
echo "Installing Gemini CLI from npm: @google/gemini-cli@$INPUT_VERSION"
npm install -g @google/gemini-cli@$INPUT_VERSION
if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?$ ]]; then
echo "Installing Gemini CLI from npm: @google/gemini-cli@${{ inputs.version }}"
npm install -g @google/gemini-cli@${{ inputs.version }}
else
echo "Installing Gemini CLI from GitHub: github:google-gemini/gemini-cli#$INPUT_VERSION"
npm install -g github:google-gemini/gemini-cli#$INPUT_VERSION
echo "Installing Gemini CLI from GitHub: github:google-gemini/gemini-cli#${{ inputs.version }}"
npm install -g github:google-gemini/gemini-cli#${{ inputs.version }}
fi
echo "Verifying installation:"
if command -v gemini >/dev/null 2>&1; then
Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

-72
View File
@@ -1,72 +0,0 @@
framework: "praisonai"
process: workflow
topic: "Autonomous Issue Resolution"
planning: false
roles:
issue_analyst:
role: "GitHub Issue Analyst"
goal: "Read the GitHub issue, understand the required changes, and map the problem to the local codebase"
backstory: "You are an expert software analyst who reads bug reports and feature requests. You excel at taking a problem description and finding exactly which files in the codebase need to be modified. Your primary job is discovery and planning."
tools:
- "execute_command"
software_engineer:
role: "Autonomous Software Engineer"
goal: "Implement required code changes on a branch, run tests, commit, and push"
backstory: "You are a senior software engineer. You always branch first, implement changes using shell commands, verify with git diff, run tests, then commit and push. You never create a Pull Request unless the issue body explicitly requests one."
tools:
- "execute_command"
- "github_create_branch"
- "github_commit_and_push"
- "github_create_pull_request"
steps:
- name: Analyze Issue
agent: issue_analyst
action: >
Thoroughly analyze the issue and the repository. Follow these steps in order.
First, set up git identity: execute_command 'git config user.name PraisonAI' then
execute_command 'git config user.email [email protected]'. Do NOT pass cwd.
Second, read the issue: execute_command 'cat .github/triage-context.md'. Do NOT pass cwd.
Third, read architecture guidelines: execute_command 'cat src/praisonai-agents/AGENTS.md'.
Fourth, decide if this issue requires code changes:
- If YES: identify the exact files and lines that must change. Output a concrete file-level plan.
- If NO (question, discussion, duplicate, invalid): say so clearly. No code changes needed.
Output: routing decision (Core SDK / Wrapper / Tools / No-change), validation result, and exact files/lines to change.
expected_output: A decision on whether code changes are needed, and if so a concrete file-level plan.
- name: Implement Fix
agent: software_engineer
action: >
You are working inside the repository at /Users/praison/praisonai-package.
Do NOT ask for confirmation. Do NOT use placeholder paths. Act immediately.
Read the analyst's output first. If the analyst said NO code changes are needed, stop here and
output a brief explanation — do NOT create a branch or PR.
If code changes ARE needed, follow these steps:
Step 1 — Create branch: github_create_branch with branch_name=praisonai/issue-{{ISSUE_NUMBER}}.
Step 2 — Implement changes using execute_command (no cwd argument).
To insert a line: python3 -c "lines=open('FILE').readlines(); lines.insert(N,'LINE\n'); open('FILE','w').writelines(lines)"
To replace text: python3 -c "t=open('FILE').read(); open('FILE','w').write(t.replace('OLD','NEW'))"
After each edit verify: execute_command 'git diff'
Step 3 — Run tests: execute_command 'python3 -m pytest src/praisonai-agents/tests/ -x -q --timeout=30 -p no:warnings'
Step 4 — Commit and push: github_commit_and_push with a descriptive commit_message.
Step 5 — Check if the issue body EXPLICITLY contains the phrase "create pull request" or "create PR"
(case-insensitive). ONLY in that case create a PR using github_create_pull_request with
title='fix: resolve issue #{{ISSUE_NUMBER}}',
body='Fixes #{{ISSUE_NUMBER}}.\n\nAutonomously resolved by PraisonAI.',
head_branch=praisonai/issue-{{ISSUE_NUMBER}}, base_branch=main.
Otherwise do NOT create a PR — just confirm the branch was pushed successfully.
expected_output: Either "No code changes needed", or "Branch pushed, no PR requested", or "Branch pushed and PR created at URL".
-116
View File
@@ -1,116 +0,0 @@
framework: "praisonai"
topic: "Pull Request Code Review Analysis"
roles:
security_reviewer:
role: "Security Code Reviewer"
goal: "Identify security vulnerabilities, authentication issues, input validation problems, and unsafe code practices in the PR changes"
backstory: "You are a cybersecurity expert specializing in code analysis. You have extensive experience in identifying common security flaws like injection attacks, authentication bypasses, exposed secrets, and unsafe data handling patterns."
tools:
- "execute_command"
performance_reviewer:
role: "Performance Code Reviewer"
goal: "Analyze code changes for performance implications, identify bottlenecks, inefficient algorithms, and resource usage issues"
backstory: "You are a performance optimization specialist with deep knowledge of algorithm efficiency, memory management, and system performance. You excel at spotting code that could cause performance degradation."
tools:
- "execute_command"
maintainability_reviewer:
role: "Code Quality & Maintainability Reviewer"
goal: "Evaluate code structure, readability, documentation, naming conventions, and adherence to best practices"
backstory: "You are a software engineering expert focused on code quality and maintainability. You ensure code follows established patterns, is well-documented, and will be easy for future developers to understand and modify."
tools:
- "execute_command"
lead_reviewer:
role: "Lead Technical Reviewer"
goal: "Synthesize all review feedback, make final recommendations, and post comprehensive review comments to the GitHub PR"
backstory: "You are a senior technical lead responsible for final review decisions. You coordinate input from security, performance, and maintainability reviewers to provide balanced, actionable feedback to the development team."
tools:
- "execute_command"
steps:
- name: security_analysis
agent: security_reviewer
action: |
Analyze the PR diff for security issues:
1. Extract PR details: `echo "$PR_DATA"`
2. Get changed files: `echo "$CHANGED_FILES"`
3. Review full diff: `gh pr diff ${PR_NUMBER}`
4. Look for:
- Hardcoded secrets, API keys, passwords
- SQL injection vulnerabilities
- XSS vulnerabilities
- Authentication/authorization bypasses
- Unsafe file operations
- Command injection risks
- Missing input validation
- Exposed sensitive data
5. Document findings with file paths and line numbers
expected_output: "Detailed security analysis report with specific vulnerabilities found, severity levels, and remediation suggestions"
- name: performance_analysis
agent: performance_reviewer
action: |
Analyze the PR diff for performance issues:
1. Review algorithm complexity changes
2. Check for inefficient database queries
3. Identify memory leaks or excessive allocations
4. Look for hot-path regressions
5. Check for blocking I/O operations
6. Analyze module-level imports that could slow startup
7. Review caching implementations
8. Check for recursive operations without termination
9. Document performance concerns with impact estimates
expected_output: "Performance analysis report highlighting bottlenecks, efficiency concerns, and optimization recommendations"
- name: maintainability_analysis
agent: maintainability_reviewer
action: |
Analyze the PR diff for code quality and maintainability:
1. Check naming conventions consistency
2. Evaluate code structure and organization
3. Review documentation completeness
4. Check for DRY violations
5. Analyze error handling patterns
6. Review test coverage for changes
7. Check adherence to repository coding standards
8. Evaluate API design consistency
9. Look for technical debt introduction
expected_output: "Code quality analysis with specific recommendations for improving maintainability and readability"
- name: final_review
agent: lead_reviewer
action: |
Synthesize all reviews and post final comprehensive feedback:
1. Collect input from security, performance, and maintainability reviews
2. Prioritize findings by severity (Critical, High, Medium, Low)
3. Create structured review comment using this format:
## 📋 Review Summary
[2-3 sentence overview and assessment]
## 🔍 General Feedback
[Overall patterns and observations]
## 🎯 Specific Feedback
### 🔴 Critical (if any)
[Security vulnerabilities, breaking changes, major bugs]
### 🟡 High (if any)
[Performance issues, design flaws, significant bugs]
### 🟢 Medium (if any)
[Code quality improvements, minor optimizations]
### 🔵 Low (if any)
[Documentation, naming suggestions, minor refactoring]
## ✅ Highlights (if any)
[Positive aspects worth mentioning]
4. Post comprehensive review: `echo "[REVIEW_CONTENT]" | gh pr comment ${PR_NUMBER} -F -`
5. If critical issues found, suggest not merging until resolved
expected_output: "Final review comment posted to GitHub PR with comprehensive analysis from all reviewers"
dependencies: [security_analysis, performance_analysis, maintainability_analysis]
-3
View File
@@ -1,3 +0,0 @@
# GitHub Issue #1350: Add debug log on successful branch checkout
The github_create_branch function in src/praisonai-agents/praisonaiagents/tools/github_tools.py should call logger.debug after a successful checkout.
+14 -397
View File
@@ -1,14 +1,11 @@
name: Auto PR Comment
# Review chain: CodeRabbit/Qodo/Gemini review first → Copilot → Claude (final)
# For human PRs: CodeRabbit/Qodo auto-review → this workflow triggers Copilot → then Claude
# Review chain: CodeRabbit/Qodo/Gemini review first → THEN trigger Copilot
# For human PRs: CodeRabbit/Qodo auto-review → this workflow triggers Copilot
# For bot PRs: CodeRabbit/Qodo skip bots, so we trigger them explicitly first
#
# IMPORTANT: @copilot ignores bot comments. All @copilot mentions must use
# PAT_TOKEN so the comment posts as MervinPraison (a real user).
#
# Claude trigger runs IN-BAND (same workflow) to avoid GitHub Actions
# blocking bot-triggered workflows with action_required approval gates.
on:
issue_comment:
types: [created]
@@ -17,11 +14,6 @@ on:
pull_request:
types: [opened]
env:
MAX_POLL_ATTEMPTS: 20
POLL_DELAY_MS: 30000
CLAUDE_TRIGGER_LOGINS: '["MervinPraison","github-actions[bot]"]'
jobs:
# ================================================================
# HUMAN PRs: Trigger @copilot AFTER CodeRabbit posts its summary
@@ -31,16 +23,12 @@ jobs:
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.comment.user.login == 'coderabbitai[bot]' &&
contains(github.event.comment.body, 'summarize by coderabbit')
contains(github.event.comment.body, 'summary by coderabbit')
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
pr_number: ${{ steps.trigger.outputs.pr_number }}
triggered: ${{ steps.trigger.outputs.triggered }}
steps:
- name: Post Copilot review request (as user via PAT)
id: trigger
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
@@ -57,18 +45,14 @@ jobs:
);
if (alreadyPosted) {
console.log('Copilot already triggered, skipping');
core.setOutput('triggered', 'false');
core.setOutput('pr_number', context.issue.number.toString());
return;
}
await github.rest.issues.createComment({
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused? Any unnecessary code or scope creep?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. **Address reviewer feedback**: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review\n8. Suggest specific improvements with code examples where possible'
});
core.setOutput('triggered', 'true');
core.setOutput('pr_number', context.issue.number.toString());
})
# Fallback: Trigger @copilot after Qodo review (if Coderabbit doesn't fire)
copilot-after-qodo:
@@ -78,12 +62,8 @@ jobs:
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
pr_number: ${{ steps.trigger.outputs.pr_number }}
triggered: ${{ steps.trigger.outputs.triggered }}
steps:
- name: Post Copilot review request (as user via PAT)
id: trigger
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
@@ -99,377 +79,14 @@ jobs:
);
if (alreadyPosted) {
console.log('Copilot already triggered, skipping');
core.setOutput('triggered', 'false');
core.setOutput('pr_number', context.issue.number.toString());
return;
}
await github.rest.issues.createComment({
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above first.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. Suggest specific improvements with code examples where possible'
});
core.setOutput('triggered', 'true');
core.setOutput('pr_number', context.issue.number.toString());
# ================================================================
# FALLBACK: Claude trigger for cases where Copilot chain fails/skips
# Ensures Claude always runs after sufficient review time
# ================================================================
claude-fallback-timeout:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'opened'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
issues: write
pull-requests: write
steps:
- name: Wait for review window (15 min)
run: sleep 900
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@claude') && triggerLogins.includes(c.user.login)
);
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
core.setOutput('comments_count', comments.data.length.toString());
// Collect all bot reviewer comments for context
const botReviews = comments.data.filter(c =>
['coderabbitai[bot]', 'qodo-code-review[bot]', 'gemini-code-assist[bot]',
'copilot-swe-agent', 'Copilot', 'greptile-apps[bot]'].some(bot =>
c.user.login.toLowerCase().includes(bot.toLowerCase())
)
);
core.setOutput('review_count', botReviews.length.toString());
- name: Aggregate reviews and trigger Claude
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const comments = await github.rest.issues.listComments({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, per_page: 100});
const reviews = {coderabbit: [], qodo: [], gemini: [], copilot: [], greptile: [], others: []};
comments.data.forEach(c => {
const login = c.user.login.toLowerCase();
const body = c.body.substring(0, 500);
if (login.includes('coderabbit')) reviews.coderabbit.push(body);
else if (login.includes('qodo')) reviews.qodo.push(body);
else if (login.includes('gemini')) reviews.gemini.push(body);
else if (login.includes('copilot')) reviews.copilot.push(body);
else if (login.includes('greptile')) reviews.greptile.push(body);
else if (c.user.type === 'Bot') reviews.others.push(body);
});
const summaryParts = [];
if (reviews.coderabbit.length) summaryParts.push('CodeRabbit: ' + reviews.coderabbit.length + ' comments');
if (reviews.qodo.length) summaryParts.push('Qodo: ' + reviews.qodo.length + ' comments');
if (reviews.gemini.length) summaryParts.push('Gemini: ' + reviews.gemini.length + ' comments');
if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments');
if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments');
const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected';
const claudeBody = '@claude You are the FINAL architecture reviewer. ' + reviewSummary + '.\n\n' +
'Review Context: ' + comments.data.length + ' total comments, ' +
(reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' +
'Phase 1: Review per AGENTS.md principles (protocol-driven, backward compatible, no perf impact).\n' +
'Phase 2: FIX valid issues found by prior reviewers.\n' +
'Phase 3: Provide final verdict (approve or request changes).';
await github.rest.issues.createComment({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody});
console.log('Claude fallback triggered on PR #' + context.issue.number + ' with ' + reviewSummary);
# ================================================================
# Claude FINAL review — polls for Copilot's response, then triggers
# This runs IN-BAND under the PAT context (MervinPraison), avoiding
# the action_required approval gate that blocks bot-triggered workflows.
# ================================================================
claude-after-copilot:
needs: [copilot-after-coderabbit]
if: |
always() &&
needs.copilot-after-coderabbit.result == 'success' &&
needs.copilot-after-coderabbit.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: write
pull-requests: write
steps:
- name: Wait for Copilot to respond (poll)
id: poll
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }}
POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const owner = context.repo.owner;
const repo = context.repo.repo;
// Poll for Copilot's response — check every 30s for up to 10 minutes
const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10);
const delayMs = parseInt(process.env.POLL_DELAY_MS, 10);
for (let i = 0; i < maxAttempts; i++) {
console.log(`Poll attempt ${i + 1}/${maxAttempts}...`);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner,
repo,
per_page: 50,
sort: 'created',
direction: 'desc'
});
// Look for Copilot's response (copilot-swe-agent or Copilot)
const copilotResponse = comments.data.find(c =>
(c.user.login === 'copilot-swe-agent' ||
c.user.login === 'Copilot' ||
c.user.login.toLowerCase().includes('copilot')) &&
c.user.type === 'Bot'
);
if (copilotResponse) {
console.log(`Copilot responded at ${copilotResponse.created_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
// Also check PR reviews for Copilot
const reviews = await github.rest.pulls.listReviews({
pull_number: pr,
owner,
repo
});
const copilotReview = reviews.data.find(r =>
r.user.login.toLowerCase().includes('copilot')
);
if (copilotReview) {
console.log(`Copilot review submitted at ${copilotReview.submitted_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
if (i < maxAttempts - 1) {
console.log(`Copilot hasn't responded yet, waiting ${delayMs/1000}s...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
console.log('Copilot did not respond within timeout, triggering Claude anyway');
core.setOutput('copilot_responded', 'timeout');
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@claude') && triggerLogins.includes(c.user.login)
);
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Trigger Claude final review
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
});
console.log(`Claude triggered on PR #${pr}`);
# ================================================================
# Claude after Qodo fallback path (same polling approach)
# ================================================================
claude-after-copilot-qodo:
needs: [copilot-after-qodo]
if: |
always() &&
needs.copilot-after-qodo.result == 'success' &&
needs.copilot-after-qodo.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: write
pull-requests: write
steps:
- name: Wait for Copilot to respond (poll)
id: poll
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }}
POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const owner = context.repo.owner;
const repo = context.repo.repo;
const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10);
const delayMs = parseInt(process.env.POLL_DELAY_MS, 10);
for (let i = 0; i < maxAttempts; i++) {
console.log(`Poll attempt ${i + 1}/${maxAttempts}...`);
const comments = await github.rest.issues.listComments({
issue_number: pr, owner, repo,
per_page: 50, sort: 'created', direction: 'desc'
});
const copilotResponse = comments.data.find(c =>
(c.user.login === 'copilot-swe-agent' ||
c.user.login === 'Copilot' ||
c.user.login.toLowerCase().includes('copilot')) &&
c.user.type === 'Bot'
);
if (copilotResponse) {
console.log(`Copilot responded at ${copilotResponse.created_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
const reviews = await github.rest.pulls.listReviews({
pull_number: pr, owner, repo
});
const copilotReview = reviews.data.find(r =>
r.user.login.toLowerCase().includes('copilot')
);
if (copilotReview) {
console.log(`Copilot review submitted at ${copilotReview.submitted_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
if (i < maxAttempts - 1) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
console.log('Copilot did not respond within timeout, triggering Claude anyway');
core.setOutput('copilot_responded', 'timeout');
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@claude') && triggerLogins.includes(c.user.login)
);
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Trigger Claude final review
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
});
console.log(`Claude triggered on PR #${pr}`);
# ================================================================
# Claude after Gemini — restores the trigger lost from the deleted
# chain-claude-after-copilot.yml workflow.
# Fires when Gemini posts a review comment on a PR or issue.
# ================================================================
claude-after-gemini:
if: |
github.event_name == 'issue_comment' &&
(
github.event.comment.user.login == 'gemini-code-assist[bot]' ||
contains(github.event.comment.body, 'Review completed by Gemini CLI') ||
contains(github.event.comment.body, 'The fix is ready for review. Please test')
)
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@claude') && triggerLogins.includes(c.user.login)
);
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Post Claude analysis request (Lead Engineer)
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the Lead Engineer. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
});
})
# ================================================================
# BOT PRs: CodeRabbit/Qodo skip bot-authored PRs by default.
@@ -489,7 +106,7 @@ jobs:
permissions:
pull-requests: write
steps:
- name: Trigger CodeRabbit and Qodo reviews
- name: Trigger CodeRabbit, Qodo, and Gemini reviews
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
@@ -512,12 +129,12 @@ jobs:
});
console.log(`Triggered Qodo for bot PR #${pr}`);
// 3. Trigger Gemini review (commented out)
// await github.rest.issues.createComment({
// issue_number: pr, owner, repo,
// body: '@gemini review this PR'
// });
// console.log(`Triggered Gemini for bot PR #${pr}`);
// 3. Trigger Gemini review
await github.rest.issues.createComment({
issue_number: pr, owner, repo,
body: '@gemini review this PR'
});
console.log(`Triggered Gemini for bot PR #${pr}`);
// NOTE: @copilot is NOT triggered here.
// It will be triggered by copilot-after-coderabbit or copilot-after-qodo
+3 -11
View File
@@ -20,8 +20,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.12
uses: actions/setup-python@v5
@@ -37,8 +35,6 @@ jobs:
- name: Run Import Time Benchmark
id: import_time
env:
FAIL_ON_REGRESSION: ${{ inputs.fail_on_regression }}
run: |
cd src/praisonai-agents
python benchmarks/import_time.py 2>&1 | tee import_time_output.txt
@@ -50,7 +46,7 @@ jobs:
# Check threshold (200ms)
if (( $(echo "$IMPORT_TIME > 200" | bc -l) )); then
echo "::warning::Import time ${IMPORT_TIME}ms exceeds 200ms threshold"
if [ "$FAIL_ON_REGRESSION" == "true" ]; then
if [ "${{ inputs.fail_on_regression }}" == "true" ]; then
echo "import_passed=false" >> $GITHUB_OUTPUT
fi
else
@@ -59,8 +55,6 @@ jobs:
- name: Run Memory Usage Benchmark
id: memory
env:
FAIL_ON_REGRESSION: ${{ inputs.fail_on_regression }}
run: |
cd src/praisonai-agents
python benchmarks/memory_usage.py 2>&1 | tee memory_output.txt
@@ -72,7 +66,7 @@ jobs:
# Check threshold (30MB)
if (( $(echo "$PEAK_MEMORY > 30" | bc -l) )); then
echo "::warning::Memory usage ${PEAK_MEMORY}MB exceeds 30MB threshold"
if [ "$FAIL_ON_REGRESSION" == "true" ]; then
if [ "${{ inputs.fail_on_regression }}" == "true" ]; then
echo "memory_passed=false" >> $GITHUB_OUTPUT
fi
else
@@ -101,11 +95,9 @@ jobs:
- name: Run Parameter Impact Benchmark
id: param_impact
env:
INPUT_ITERATIONS: ${{ inputs.iterations }}
run: |
cd src/praisonai-agents
python benchmarks/param_impact_benchmark.py --no-save --iterations "$INPUT_ITERATIONS" 2>&1 | tee param_impact_output.txt
python benchmarks/param_impact_benchmark.py --no-save --iterations ${{ inputs.iterations }} 2>&1 | tee param_impact_output.txt
- name: Check Lazy Imports
id: lazy_imports
-2
View File
@@ -18,8 +18,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -0,0 +1,58 @@
name: Chain Claude After Reviewers
# Event-driven: triggers when Copilot finishes reviewing a PR
on:
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
jobs:
# Trigger Claude after Copilot finishes on a PR — Claude is the FINAL reviewer
# Claude reads ALL previous reviews (Qodo, Coderabbit, Copilot) and fixes any valid issues
claude-final-review:
if: |
(github.event_name == 'pull_request_review' && contains(github.event.review.user.login, 'copilot')) ||
(github.event_name == 'issue_comment' && contains(github.event.comment.user.login, 'copilot'))
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Post Claude final review + fix request
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
})
# Trigger Claude after Gemini finishes on an Issue or PR
claude-after-gemini:
if: |
github.event_name == 'issue_comment' &&
(
github.event.comment.user.login == 'gemini-code-assist[bot]' ||
contains(github.event.comment.body, 'Review completed by Gemini CLI') ||
contains(github.event.comment.body, 'The fix is ready for review. Please test')
)
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Post Claude analysis request (final reviewer)
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the Lead Engineer. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
})
+3 -6
View File
@@ -15,18 +15,15 @@ jobs:
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Run Claude Issue Triage
uses: ./.github/actions/claude-issue-triage-action
+15 -193
View File
@@ -11,116 +11,10 @@ on:
types: [submitted]
jobs:
# ================================================================
# Job 1: Lightweight triage for ALL new issues (including external users)
# Posts welcome comment + adds triage labels. No Claude code execution.
# ================================================================
issue-triage:
if: |
github.event_name == 'issues' &&
github.event.action == 'opened' &&
!contains(toJSON(github.event.issue.labels), 'claude')
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Triage and acknowledge issue
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const content = title + ' ' + body;
const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(
issue.author_association
);
// --- Label triage ---
const labels = [];
// Kind labels
if (content.includes('feature request') || content.includes('[feature')) {
labels.push('enhancement');
} else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) {
labels.push('bug');
} else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) {
labels.push('question');
}
// Area labels
if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) {
labels.push('security');
}
if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) {
labels.push('performance');
}
if (content.includes('documentation') || content.includes('docs')) {
labels.push('documentation');
}
if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) {
labels.push('javascript');
}
// Apply labels
if (labels.length > 0) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels
});
}
// --- Acknowledgment comment (external users only) ---
if (!isOwner) {
await github.rest.issues.createComment({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: [
`👋 Thanks for opening this issue, @${issue.user.login}!`,
'',
'A maintainer will review this shortly. In the meantime:',
'- Make sure you\'ve included steps to reproduce (for bugs)',
'- Check [existing issues](https://github.com/MervinPraison/PraisonAI/issues) for duplicates',
'- Review the [documentation](https://docs.praison.ai) for related guides',
'',
'_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._'
].join('\n')
});
}
// --- For owner: add 'claude' label to trigger the code-fix job ---
if (isOwner) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['claude']
});
}
# ================================================================
# Job 2: Full Claude code-fix (owner/collaborator only)
# Triggers on: labeled 'claude', @claude comments, assignments
# ================================================================
claude-response:
# Allow: human users, github-actions[bot] (auto-comments/labels)
# Block: dependabot, cursor, renovate, other bots
# Skip 'opened' events — triage job handles those above
if: |
github.event.action != 'opened' &&
(github.event.action != 'labeled' || github.event.label.name == 'claude') &&
(github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) &&
(
@@ -139,130 +33,58 @@ jobs:
actions: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Check for Fork PR
id: check_fork
uses: actions/github-script@v7
with:
script: |
const isPR = context.eventName === 'pull_request' || (context.payload.issue && context.payload.issue.pull_request);
if (isPR) {
const prNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
core.setOutput('pr_branch', pr.data.head.ref);
if (pr.data.head.repo && pr.data.head.repo.full_name !== context.repo.full_name) {
core.setOutput('is_fork', 'true');
core.setOutput('clone_url', pr.data.head.repo.clone_url);
core.setOutput('branch', pr.data.head.ref);
return;
}
}
core.setOutput('is_fork', 'false');
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Fetch PR branch and Setup Remote
if: github.event.issue.pull_request
run: |
# Fetch PR head from base repo and put it into local branch
git fetch origin pull/${{ github.event.issue.number }}/head:${{ steps.check_fork.outputs.pr_branch }}
# If it's a fork, make origin point to local to trick claude-code-action's `git fetch origin <branch>`
if [ "${{ steps.check_fork.outputs.is_fork }}" == "true" ]; then
git remote set-url origin file://$(pwd)
fi
- uses: anthropics/claude-code-action@beta
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
with:
allowed_bots: 'praisonai-triage-agent[bot]'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
trigger_phrase: "@claude"
label_trigger: "claude"
allowed_non_write_users: "*"
direct_prompt: |
${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK. YOU DO NOT HAVE PUSH PERMISSIONS TO THIS REPOSITORY. ONLY READ FILES, VALIDATE CODE, AND PROVIDE YOUR COMMENTS/FEEDBACK DIRECTLY IN THIS PR VIA COMMENTS. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || '' }}
You are working on the PraisonAI SDK. Follow AGENTS.md strictly.
STEP 0 — SETUP GIT IDENTITY & AUTH (GLOBAL — required for all repos):
git config --global user.name "MervinPraison"
git config --global user.email "[email protected]"
gh auth setup-git
STEP 0 — SETUP GIT IDENTITY:
git config user.name "MervinPraison"
git config user.email "[email protected]"
STEP 1 — READ GUIDELINES:
Read AGENTS.md to understand the architecture rules.
STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code):
STEP 2 — ARCHITECTURE VALIDATION (MANDATORY before writing code):
Before implementing anything, answer these questions:
- CORE vs WRAPPER vs TOOLS vs DOCS vs PLUGINS ROUTING:
1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations.
2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies.
3. Tools (PraisonAI-Tools): Capabilities Agents actively call during execution (e.g. SurrealDB tool, Slack tool).
4. Documentation (PraisonAIDocs): Documentation pages, guides, API references.
5. Plugins (PraisonAI-Plugins): Framework lifecycle extensions replacing systemic behavior (e.g. tracing, logging, metrics, hooks, guardrails).
For items routed to an EXTERNAL repository (Tools, Docs, Plugins), follow STEP 3-ALT below.
- Does this add heavy implementations to the core SDK (praisonaiagents)? If YES → move it to the wrapper (praisonai) or make it a plugin.
- Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory).
- Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition.
- Does it add new dependencies? Only optional deps allowed, must be lazy-imported.
- Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead.
- Is there a name collision with existing exports in __init__.py?
If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR.
If ANY of these checks fail, add a comment to the issue explaining why and close it. Do NOT create a PR.
STEP 3 — IMPLEMENT (for changes in THIS repo — PraisonAI):
STEP 3 — IMPLEMENT:
- Create a fix branch and implement a minimal, focused fix
- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper
- Keep changes small and backward-compatible
STEP 3-ALT — IMPLEMENT IN EXTERNAL REPO (for PraisonAI-Tools, PraisonAIDocs, PraisonAI-Plugins):
When work must happen in a different repository, follow these steps EXACTLY. Do NOT attempt to use `cd`, as directory state is not preserved. Use `git -C` for all commands.
a) Clone the repository:
gh repo clone MervinPraison/<REPO_NAME> /tmp/<REPO_NAME>
b) Copy GitHub authentication from the main repository so push works seamlessly:
git -C /tmp/<REPO_NAME> config http."https://github.com/".extraheader "$(git config --get http."https://github.com/".extraheader)"
c) Create a feature branch (NEVER commit to main):
git -C /tmp/<REPO_NAME> checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
d) Make your changes using absolute paths (e.g. edit /tmp/<REPO_NAME>/docs.json)
e) Commit:
git -C /tmp/<REPO_NAME> add -A
git -C /tmp/<REPO_NAME> commit -m "feat: <description> (fixes #$ISSUE_NUMBER)"
f) Push the branch:
git -C /tmp/<REPO_NAME> push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
g) Create PR in the EXTERNAL repo:
gh pr create -R MervinPraison/<REPO_NAME> --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) --title "feat: <title>" --body "Fixes MervinPraison/PraisonAI#$ISSUE_NUMBER
<body>"
STEP 4 — TEST:
- For SDK changes: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
- For docs changes: verify files exist and are valid markdown/MDX
- Run: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
- Ensure no regressions
STEP 5 — CREATE PR:
- Commit with descriptive message, push, and create PR using `gh pr create`
- For external repos, use `gh pr create -R MervinPraison/<REPO_NAME>`
CRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link or say "manual push required".
NOTE: If you worked on an external repo, the wrapper action will report "No commits" for the main repo at the end. This is expected, do not worry about it.
CRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link.
allowed_tools: |
Bash(git:*)
Bash(python:*)
-4
View File
@@ -20,10 +20,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -34,10 +34,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
token: ${{ steps.generate_token.outputs.token }}
@@ -35,10 +35,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
token: ${{ steps.generate_token.outputs.token }}
-4
View File
@@ -46,10 +46,6 @@ jobs:
- name: Checkout PR code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
token: ${{ steps.generate_token.outputs.token }}
ref: ${{ steps.checkout_ref.outputs.ref }}
@@ -1,72 +0,0 @@
name: PraisonAI Issue Triage
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue Number'
required: true
type: string
jobs:
triage:
runs-on: ubuntu-latest
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
contains(github.event.comment.body, '@praisonai') &&
github.actor != 'github-actions[bot]' &&
github.event.comment.user.type != 'Bot' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
fetch-depth: 0
- name: Generate GitHub App Token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.PRAISONAI_APP_ID }}
private_key: ${{ secrets.PRAISONAI_APP_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Set up Git
run: |
git config --local user.email "[email protected]"
git config --local user.name "PraisonAI Bot"
- name: Install PraisonAI
run: |
pip install uv
cd src/praisonai-agents && uv pip install --system -e ".[all]"
cd ../praisonai && uv pip install --system -e ".[all]"
- name: Run PraisonAI Issue Triage
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PRAISONAI_AUTO_APPROVE: "true"
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
export ISSUE_NUMBER="${{ inputs.issue_number }}"
else
export ISSUE_NUMBER="${{ github.event.issue.number }}"
fi
praisonai github triage --issue $ISSUE_NUMBER --agent-file .github/praisonai-issue-triage.yaml
-72
View File
@@ -1,72 +0,0 @@
name: PraisonAI PR Reviewer
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'Pull Request Number'
required: true
type: string
jobs:
review:
runs-on: ubuntu-latest
if: >
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@praisonai') &&
github.actor != 'github-actions[bot]' &&
github.event.comment.user.type != 'Bot' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
steps:
- name: Determine checkout ref
id: dest
run: |
if [ "${{ github.event_name }}" = "issue_comment" ]; then
echo "ref=refs/pull/${{ github.event.issue.number }}/head" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "ref=refs/pull/${{ inputs.pr_number }}/head" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
ref: ${{ steps.dest.outputs.ref }}
fetch-depth: 0
- name: Generate GitHub App Token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.PRAISONAI_APP_ID }}
private_key: ${{ secrets.PRAISONAI_APP_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install PraisonAI
run: pip install "praisonai[all]"
- name: Run PraisonAI PR Review
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
praisonai workflow run .github/praisonai-reviewer.yaml
+1 -2
View File
@@ -15,8 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
-2
View File
@@ -22,8 +22,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
+20 -46
View File
@@ -26,10 +26,6 @@ jobs:
# First checkout the upstream repo at the specified tag
- name: Checkout upstream repo
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
repository: MervinPraison/PraisonAI
ref: ${{ github.event.inputs.tag }}
@@ -38,20 +34,14 @@ jobs:
# Get the commit hash from the tag
- name: Get commit hash
id: get_commit
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
COMMIT_HASH=$(git rev-parse HEAD)
echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
echo "Found commit: $COMMIT_HASH for tag $INPUT_TAG"
echo "Found commit: $COMMIT_HASH for tag ${{ github.event.inputs.tag }}"
# Now checkout the fork at main branch
- name: Checkout fork
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
repository: ${{ github.event.inputs.fork_owner }}/PraisonAI
ref: main
@@ -70,11 +60,8 @@ jobs:
# Fetch from upstream
git fetch upstream --tags --force
- name: Hard reset to tag commit
env:
COMMIT_HASH: ${{ steps.get_commit.outputs.commit_hash }}
run: |
git reset --hard $COMMIT_HASH
# Reset to upstream commit
git reset --hard ${{ steps.get_commit.outputs.commit_hash }}
- name: Setup Python
uses: actions/setup-python@v5
@@ -449,10 +436,6 @@ jobs:
# First checkout the fork's main branch
- name: Checkout fork repo
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
with:
fetch-depth: 0
token: ${{ secrets.PAT_TOKEN }}
@@ -462,64 +445,55 @@ jobs:
# Add upstream and get commit from tag
- name: Get upstream tag commit
id: get_upstream_commit
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
# Add upstream remote
git remote add upstream https://github.com/MervinPraison/PraisonAI.git
# Delete local tag if it exists
git tag -d "$INPUT_TAG" || true
git tag -d ${{ github.event.inputs.tag }} || true
# Fetch from upstream including tags with force
git fetch upstream --tags --force
# Verify tag exists in upstream
if ! git ls-remote --tags upstream | grep -q "refs/tags/$INPUT_TAG$"; then
echo "::error::Tag $INPUT_TAG not found in upstream repository"
if ! git ls-remote --tags upstream | grep -q "refs/tags/${{ github.event.inputs.tag }}$"; then
echo "::error::Tag ${{ github.event.inputs.tag }} not found in upstream repository"
exit 1
fi
# Get the commit hash from the upstream tag
UPSTREAM_COMMIT=$(git rev-list -n 1 refs/tags/"$INPUT_TAG")
UPSTREAM_COMMIT=$(git rev-list -n 1 refs/tags/${{ github.event.inputs.tag }})
if [ -z "$UPSTREAM_COMMIT" ]; then
echo "::error::Could not find commit for tag $INPUT_TAG"
echo "::error::Could not find commit for tag ${{ github.event.inputs.tag }}"
exit 1
fi
echo "upstream_commit=$UPSTREAM_COMMIT" >> $GITHUB_OUTPUT
echo "Found commit: $UPSTREAM_COMMIT for tag $INPUT_TAG"
echo "Found commit: $UPSTREAM_COMMIT for tag ${{ github.event.inputs.tag }}"
# Create tag at the same commit
- name: Create tag at commit
env:
GIT_ASKPASS: /bin/echo
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
git config --global user.name "MervinPraison"
git config --global user.email "[email protected]"
# Remove tag if it exists locally
git tag -d "$INPUT_TAG" || true
git tag -d ${{ github.event.inputs.tag }} || true
# Remove tag if it exists remotely
git push origin :refs/tags/"$INPUT_TAG" || true
git push origin :refs/tags/${{ github.event.inputs.tag }} || true
- name: Fetch tag from upstream
env:
UPSTREAM_COMMIT: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
run: |
git fetch upstream $UPSTREAM_COMMIT --force
# Fetch the specific commit from upstream
git fetch upstream ${{ steps.get_upstream_commit.outputs.upstream_commit }} --force
- name: Reset to upstream commit
env:
UPSTREAM_COMMIT: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
run: |
git reset --hard $UPSTREAM_COMMIT
# Reset to upstream commit
git reset --hard ${{ steps.get_upstream_commit.outputs.upstream_commit }}
# Create and push tag
git tag -a "$INPUT_TAG" -m "Release $INPUT_TAG (synced from upstream MervinPraison/PraisonAI@$INPUT_TAG)"
git push origin "$INPUT_TAG"
git tag -a ${{ github.event.inputs.tag }} -m "Release ${{ github.event.inputs.tag }} (synced from upstream MervinPraison/PraisonAI@${{ github.event.inputs.tag }})"
git push origin ${{ github.event.inputs.tag }}
env:
GIT_ASKPASS: /bin/echo
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
# Create the release
- name: Create Release
+4 -13
View File
@@ -37,10 +37,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
@@ -71,11 +67,9 @@ jobs:
fi
- name: Run Comprehensive Test Suite
env:
INPUT_TEST_TYPE: ${{ github.event.inputs.test_type || 'all' }}
run: |
# Determine test type from input or default to 'all'
TEST_TYPE="$INPUT_TEST_TYPE"
TEST_TYPE="${{ github.event.inputs.test_type || 'all' }}"
echo "🧪 Running comprehensive test suite (type: $TEST_TYPE)"
@@ -108,16 +102,13 @@ jobs:
- name: Generate Comprehensive Test Report
if: always()
env:
INPUT_TEST_TYPE: ${{ github.event.inputs.test_type || 'all' }}
EVENT_NAME: ${{ github.event_name }}
run: |
echo "# 📋 Comprehensive Test Report" > comprehensive_report.md
echo "" >> comprehensive_report.md
echo "**Python Version:** ${{ matrix.python-version }}" >> comprehensive_report.md
echo "**Test Type:** $INPUT_TEST_TYPE" >> comprehensive_report.md
echo "**Trigger:** $EVENT_NAME" >> comprehensive_report.md
echo "**Status:** 🟢 All Tests Passed" >> comprehensive_report.md
echo "**Test Type:** ${{ github.event.inputs.test_type || 'all' }}" >> comprehensive_report.md
echo "**Trigger:** ${{ github.event_name }}" >> comprehensive_report.md
echo "**Date:** $(date -u)" >> comprehensive_report.md
echo "" >> comprehensive_report.md
echo "## 🧪 Test Categories Covered:" >> comprehensive_report.md
+1 -2
View File
@@ -22,8 +22,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
-8
View File
@@ -13,10 +13,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -81,10 +77,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
-4
View File
@@ -33,10 +33,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
+2 -16
View File
@@ -45,8 +45,7 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
@@ -105,8 +104,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@@ -187,8 +185,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
@@ -259,8 +255,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
@@ -318,8 +312,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
@@ -377,8 +369,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
@@ -436,8 +426,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
@@ -495,8 +483,6 @@ jobs:
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
+8 -20
View File
@@ -34,21 +34,14 @@ jobs:
steps:
- name: 🚨 Cost Warning
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
INPUT_CONFIRM_COSTS: ${{ github.event.inputs.confirm_costs }}
run: |
echo "⚠️ WARNING: This workflow will make real API calls!"
echo "💰 This may incur charges on your API accounts"
echo "🎯 Framework: $INPUT_FRAMEWORK"
echo "✅ Cost confirmation: $INPUT_CONFIRM_COSTS"
echo "🎯 Framework: ${{ github.event.inputs.framework }}"
echo "✅ Cost confirmation: ${{ github.event.inputs.confirm_costs }}"
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
@@ -100,23 +93,21 @@ jobs:
- name: Generate Real Test Report
if: always()
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
run: |
echo "# 🔥 Real End-to-End Test Report" > real_test_report.md
echo "" >> real_test_report.md
echo "⚠️ **WARNING: This report represents tests that made real API calls**" >> real_test_report.md
echo "" >> real_test_report.md
echo "**Framework Tested:** $INPUT_FRAMEWORK" >> real_test_report.md
echo "**Framework Tested:** ${{ github.event.inputs.framework }}" >> real_test_report.md
echo "**Python Version:** ${{ matrix.python-version }}" >> real_test_report.md
echo "**Date:** $(date -u)" >> real_test_report.md
echo "**Triggered by:** $GITHUB_ACTOR" >> real_test_report.md
echo "**Triggered by:** ${{ github.actor }}" >> real_test_report.md
echo "" >> real_test_report.md
echo "## 🧪 Test Results" >> real_test_report.md
echo "" >> real_test_report.md
if [ "$INPUT_FRAMEWORK" == "autogen" ] || [ "$INPUT_FRAMEWORK" == "all" ]; then
if [ "${{ github.event.inputs.framework }}" == "autogen" ] || [ "${{ github.event.inputs.framework }}" == "all" ]; then
echo "### AutoGen Real Tests:" >> real_test_report.md
echo "- Environment verification" >> real_test_report.md
echo "- Agent creation with real API calls" >> real_test_report.md
@@ -124,7 +115,7 @@ jobs:
echo "" >> real_test_report.md
fi
if [ "$INPUT_FRAMEWORK" == "crewai" ] || [ "$INPUT_FRAMEWORK" == "all" ]; then
if [ "${{ github.event.inputs.framework }}" == "crewai" ] || [ "${{ github.event.inputs.framework }}" == "all" ]; then
echo "### CrewAI Real Tests:" >> real_test_report.md
echo "- Environment verification" >> real_test_report.md
echo "- Crew creation with real API calls" >> real_test_report.md
@@ -159,14 +150,11 @@ jobs:
steps:
- name: 🛡️ Safety Check Failed
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
INPUT_CONFIRM_COSTS: ${{ github.event.inputs.confirm_costs }}
run: |
echo "🚨 Real tests not executed due to safety checks:"
echo ""
echo "✅ Costs confirmed: $INPUT_CONFIRM_COSTS"
echo "✅ Framework selected: $INPUT_FRAMEWORK"
echo "✅ Costs confirmed: ${{ github.event.inputs.confirm_costs }}"
echo "✅ Framework selected: ${{ github.event.inputs.framework }}"
echo ""
echo "To run real tests:"
echo "1. Select a framework (autogen, crewai, or all)"
+1 -2
View File
@@ -26,8 +26,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
+1 -1
View File
@@ -94,7 +94,7 @@ Cargo.lock
**/*.rs.bk
*.pdb
.windsurf
.agent
src/praisonai/build*.egg-info/
src/praisonai/PraisonAI.egg-info/
jobs/*
+96 -74
View File
@@ -23,13 +23,14 @@
</div>
PraisonAI 🦞 — **Hire a 24/7 AI Workforce.** Stop writing boilerplate and start shipping autonomous agents that research, plan, and execute tasks across your apps. From one agent to an entire organization, deployed in 5 lines of code.
PraisonAI 🦞 — Automate and solve complex challenges with AI agent teams that plan, research, code, and deliver results to Telegram, Discord, and WhatsApp — running 24/7. A low-code, production-ready multi-agent framework with handoffs, guardrails, memory, RAG, and 100+ LLM providers, built around simplicity, customisation, and effective human-agent collaboration.
<div align="center">
<br>
<a href="https://x.com/elonmusk/status/1893870468249141688" target="_blank">
<img src="https://img.shields.io/badge/Highlighted_by_Elon_Musk-000000?style=for-the-badge&logo=x&logoColor=white" alt="Highlighted by Elon Musk" />
</a>
<p><em>"Grok 3 customer support" — <a href="https://x.com/elonmusk/status/1893870468249141688">Elon Musk quoting PraisonAI's tutorial</a></em></p>
<br>
</div>
@@ -68,6 +69,26 @@ PraisonAI 🦞 — **Hire a 24/7 AI Workforce.** Stop writing boilerplate and st
---
> **Quick Paths:**
> - 🆕 **New here?** → [Quick Start](#-quick-start) *(1 minute to first agent)*
> - 📦 **Installing?** → [Installation](#-installation)
> - 🐍 **Python SDK?** → [Python Examples](#-using-python-code)
> - 📄 **YAML/No-Code?** → [YAML Examples](#-using-yaml-no-code)
> - 🎯 **CLI user?** → [CLI Quick Reference](#-cli-quick-reference)
> - 🤝 **Contributing?** → [Contributing](#-contributing)
---
## ⚡ Performance
PraisonAI is built for speed, with agent instantiation in under 4μs. This reduces overhead, improves responsiveness, and helps multi-agent systems scale efficiently in real-world production workloads.
| Performance Metric | PraisonAI |
|--------------------|-----------|
| Avg Instantiation Time | **3.77 μs** |
---
## 🎯 Use Cases
AI agents solving real-world problems across industries:
@@ -83,44 +104,9 @@ AI agents solving real-world problems across industries:
---
## 🚀 Meet your first Agent (Under 1 Minute)
## Supported Providers
1. Install the lightweight core SDK:
```bash
pip install praisonaiagents
export OPENAI_API_KEY="your-api-key"
```
2. Run your first autonomous agent:
```python
from praisonaiagents import Agent
# Give your agent a goal, and watch it work.
agent = Agent(instructions="You are a senior data analyst.")
agent.start("Analyze the top 3 tech trends of 2026 and format as a markdown table.")
```
---
## 🌌 The PraisonAI Ecosystem
Start simple with the core SDK, or expand to full visual builders and dashboards when you're ready.
* **Core SDK (`praisonaiagents`)**: For pure Python development. `pip install praisonaiagents`
* 💻 **PraisonAI CLI (`praisonai`)**: For terminal-based developers. `pip install praisonai`
* 🦞 **Claw Dashboard**: Connect agents directly to Telegram, Slack, or Discord. `pip install "praisonai[claw]"`
* 🔗 **Flow Visual Builder**: Drag-and-drop workflow creation. `pip install "praisonai[flow]"`
* 🤖 **PraisonAI UI**: Clean chat interface. `pip install "praisonai[ui]"`
### JavaScript SDK
```bash
npm install praisonai
```
## 🧠 Supported Providers & Features
Powered by 100+ LLMs (OpenAI, Anthropic, Gemini & local models).
PraisonAI supports 100+ LLM providers through seamless integration:
<p align="center">
<img src="https://img.shields.io/badge/OpenAI-412991?style=flat&logo=openai&logoColor=white" alt="OpenAI" />
@@ -178,14 +164,6 @@ Powered by 100+ LLMs (OpenAI, Anthropic, Gemini & local models).
</details>
<div align="center">
<a href="https://x.com/elonmusk/status/1893870468249141688" target="_blank">
<img src=".github/images/elon_musk_praisonai.png" alt="Highlighted by Elon Musk" width="600" />
</a>
<p><em>"Grok 3 customer support" — <a href="https://x.com/elonmusk/status/1893870468249141688">Elon Musk quoting PraisonAI's tutorial</a></em></p>
</div>
<br>
---
## 🌟 Why PraisonAI?
@@ -227,6 +205,78 @@ Powered by 100+ LLMs (OpenAI, Anthropic, Gemini & local models).
</details>
<div align="center">
<a href="https://x.com/elonmusk/status/1893870468249141688" target="_blank">
<img src=".github/images/elon_musk_praisonai.png" alt="Highlighted by Elon Musk" width="600" />
</a>
<p><em>"Grok 3 customer support" — <a href="https://x.com/elonmusk/status/1893870468249141688">Elon Musk quoting PraisonAI's tutorial</a></em></p>
</div>
<br>
---
## 🚀 Quick Start
Get started with PraisonAI in under 1 minute:
```bash
# Install
pip install praisonaiagents
# Set API key
export OPENAI_API_KEY=your_key_here
# Create a simple agent
python -c "from praisonaiagents import Agent; Agent(instructions='You are a helpful AI assistant').start('Write a haiku about AI')"
```
> **Next Steps:** [Single Agent Example](#1-single-agent) | [Multi Agents](#2-multi-agents) | [Full Docs](https://docs.praison.ai)
---
## 📦 Installation
### Python SDK
Lightweight package dedicated for coding:
```bash
pip install praisonaiagents
```
For the full framework with CLI support:
```bash
pip install praisonai
```
🦞 **PraisonAI Claw** — full UI with bots, memory, knowledge, and gateway:
```bash
pip install "praisonai[claw]"
praisonai claw
```
🔗 **PraisonAI Flow** — Langflow Visual Flow Builder:
```bash
pip install "praisonai[flow]"
praisonai flow
```
🤖 **PraisonAI UI** — Clean chat interface:
```bash
pip install "praisonai[ui]"
praisonai ui
```
### JavaScript SDK
```bash
npm install praisonai
```
---
@@ -650,7 +700,6 @@ praisonai agents.yaml
| Auto-Save Sessions | [Docs](https://docs.praison.ai/docs/cli/session) | [📖](https://docs.praison.ai/docs/cli/session) |
| History in Context | [Docs](https://docs.praison.ai/docs/cli/session) | [📖](https://docs.praison.ai/docs/cli/session) |
| Telemetry | [Example](examples/python/telemetry/production-telemetry-example.py) | [📖](https://docs.praison.ai/docs/features/telemetry) |
| Langfuse Tracing | [Docs](https://docs.praison.ai/docs/observability/langfuse) | [📖](https://docs.praison.ai/docs/observability/langfuse) |
| Project Docs (.praison/docs/) | [Docs](https://docs.praison.ai/docs/cli/docs) | [📖](https://docs.praison.ai/docs/cli/docs) |
| AI Commit Messages | [Docs](https://docs.praison.ai/docs/cli/commit) | [📖](https://docs.praison.ai/docs/cli/commit) |
| @Mentions in Prompts | [Docs](https://docs.praison.ai/docs/cli/mentions) | [📖](https://docs.praison.ai/docs/cli/mentions) |
@@ -725,20 +774,6 @@ const agent = new Agent({ instructions: 'You are a helpful AI assistant' });
agent.start('Write a movie script about a robot in Mars');
```
---
## ⚡ Performance
PraisonAI is built for speed, with agent instantiation in under 4μs. This reduces overhead, improves responsiveness, and helps multi-agent systems scale efficiently in real-world production workloads.
| Performance Metric | PraisonAI |
|--------------------|-----------|
| Avg Instantiation Time | **3.77 μs** |
---
---
## ⭐ Star History
@@ -747,19 +782,6 @@ PraisonAI is built for speed, with agent instantiation in under 4μs. This reduc
---
## 🔍 Langfuse Tracing
```bash
pip install "praisonai[langfuse]"
praisonai langfuse
```
<p align="center">
<img src=".github/images/langfuse.png" alt="PraisonAI Langfuse Tracing" width="800" />
</p>
---
## 🎓 Video Tutorials
Learn PraisonAI through our comprehensive video series:
+2 -36
View File
@@ -28,19 +28,15 @@ from praisonaiagents import Agent, AutoAgents, AutoRagAgent, ContextAgent, DeepR
Methods:
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">aclose</a>() -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">agent_id</a>() -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">analyze_prompt</a>(prompt: str) -> set</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">auto_memory</a>() -> Optional[bool]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">auto_memory</a>(value: Optional[bool]) -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">background</a>() -> Optional[bool]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">background</a>(value: Optional[bool]) -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat_history</a>()</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat_history</a>(value)</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat_with_context</a>(message: str, context: 'ContextPack', **kwargs) -> str</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>() -> Optional[bool]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>(value: Optional[bool]) -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">close</a>() -> None</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">console</a>() -> Optional[Any]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>() -> Optional[Any]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>(value)</code>
@@ -56,7 +52,6 @@ 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">is_closed</a>() -> bool</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">llm_model</a>() -> Optional[str]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>() -> Optional[str]</code>
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>(value: Optional[str]) -> None</code>
@@ -364,9 +359,6 @@ Methods:
* <code title="class GuardrailConfig">GuardrailConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">__call__</a>(task_output) -> Tuple[bool, Union[str, 'TaskOutput']]</code>
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">validate_input</a>(content: str, **kwargs) -> Tuple[bool, str]</code>
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">validate_output</a>(content: str, **kwargs) -> Tuple[bool, str]</code>
* <code title="class LLMGuardrail">LLMGuardrail.<a href="./src/praisonai-agents/praisonaiagents/guardrails/llm_guardrail.py">validate_tool_call</a>(tool_name: str, arguments: Dict[str, Any], **kwargs) -> Tuple[bool, Dict[str, Any]]</code>
# Planning
@@ -555,7 +547,7 @@ Methods:
Types:
```python
from praisonaiagents import FlowDisplay, track_workflow
from praisonaiagents import FlowDisplay, obs, track_workflow
```
Methods:
@@ -741,7 +733,7 @@ Methods:
Types:
```python
from praisonai import AgentApp, AgentOS, AnthropicManagedAgent, CloudProvider, DB, Deploy, DeployConfig, DeployType, LocalManagedAgent, LocalManagedConfig, ManagedAgent, ManagedConfig, PraisonAI, __version__
from praisonai import AgentApp, AgentOS, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
```
# CLI
@@ -863,7 +855,6 @@ Methods:
* <code title="cli">praisonai gateway send <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway start <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway status <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai github triage <a href="./src/praisonai/praisonai/cli/commands/github.py">--help</a></code>
* <code title="cli">praisonai hooks add <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
* <code title="cli">praisonai hooks list <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
* <code title="cli">praisonai hooks remove <a href="./src/praisonai/praisonai/cli/commands/hooks.py">--help</a></code>
@@ -871,17 +862,6 @@ Methods:
* <code title="cli">praisonai knowledge index <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
* <code title="cli">praisonai knowledge list <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
* <code title="cli">praisonai knowledge search <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
* <code title="cli">praisonai langfuse config <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse connect <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse init <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse langfuse-start <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse sessions <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse show <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse status <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse stop <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse test <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse traces <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai langfuse version <a href="./src/praisonai/praisonai/cli/commands/langfuse.py">--help</a></code>
* <code title="cli">praisonai loop help <a href="./src/praisonai/praisonai/cli/commands/loop.py">--help</a></code>
* <code title="cli">praisonai loop loop-main <a href="./src/praisonai/praisonai/cli/commands/loop.py">--help</a></code>
* <code title="cli">praisonai lsp logs <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
@@ -889,16 +869,6 @@ Methods:
* <code title="cli">praisonai lsp start <a href="./src/praisonai/praisonai/cli/commands/lsp.py">--help</a></code>
* <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 managed get <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed list <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed managed-callback <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed multi <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed restore <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed resume <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed run <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed save <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed show <a href="./src/praisonai/praisonai/cli/commands/managed.py">--help</a></code>
* <code title="cli">praisonai managed update <a href="./src/praisonai/praisonai/cli/commands/managed.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 auth <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>
@@ -931,10 +901,6 @@ Methods:
* <code title="cli">praisonai plugins list <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins plugins-callback <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins remove <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai port check <a href="./src/praisonai/praisonai/cli/commands/port.py">--help</a></code>
* <code title="cli">praisonai port kill <a href="./src/praisonai/praisonai/cli/commands/port.py">--help</a></code>
* <code title="cli">praisonai port list <a href="./src/praisonai/praisonai/cli/commands/port.py">--help</a></code>
* <code title="cli">praisonai port port-callback <a href="./src/praisonai/praisonai/cli/commands/port.py">--help</a></code>
* <code title="cli">praisonai profile imports <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile optimize <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
* <code title="cli">praisonai profile profile-callback <a href="./src/praisonai/praisonai/cli/commands/profile.py">--help</a></code>
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.2" \
"praisonai>=4.5.112" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.2" \
"praisonai>=4.5.112" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.2" \
"praisonai>=4.5.112" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -1,115 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + ClickHouse Persistence — Real conversation with session resume.
ClickHouse stores analytics/state; SQLite stores conversation history.
Prerequisites:
pip install praisonai praisonaiagents clickhouse-connect
export OPENAI_API_KEY="sk-..."
# ClickHouse running on localhost:8123
"""
import json
import os
import sys
import tempfile
import clickhouse_connect
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
CH_HOST = os.getenv("CH_HOST", "localhost")
CH_PORT = int(os.getenv("CH_PORT", "8123"))
CH_USER = os.getenv("CH_USER", "clickhouse")
CH_PASSWORD = os.getenv("CH_PASSWORD", "clickhouse")
SQLITE_PATH = os.path.join(tempfile.gettempdir(), "managed_ch_conv.db")
print(f"ManagedAgent + ClickHouse Persistence\nCH: {CH_HOST}:{CH_PORT}\nSQLite: {SQLITE_PATH}\n")
# Setup ClickHouse table
client = clickhouse_connect.get_client(host=CH_HOST, port=CH_PORT, username=CH_USER, password=CH_PASSWORD)
TABLE = "managed_agent_sessions"
client.command(f"""
CREATE TABLE IF NOT EXISTS {TABLE} (
session_id String, agent_id String,
input_tokens UInt64, output_tokens UInt64,
state_json String, created_at DateTime DEFAULT now()
) ENGINE = MergeTree() ORDER BY (session_id, created_at)
""")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=SQLITE_PATH)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="ClickHouse Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My favourite movie is Interstellar by Christopher Nolan, 2014. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: The Hans Zimmer soundtrack is my favourite film score. Confirm.")
print(f"Agent: {result2[:200]}...")
# Store to ClickHouse
session_id = managed.session_id
client.insert(TABLE, [[
session_id, managed.agent_id or "",
managed.total_input_tokens, managed.total_output_tokens,
json.dumps({"environment_id": managed.environment_id}),
]], column_names=["session_id", "agent_id", "input_tokens", "output_tokens", "state_json"])
# ── Phase 2: ClickHouse Verification ──
print("\n=== Phase 2: ClickHouse Verification ===")
rows = client.query(f"SELECT session_id, agent_id FROM {TABLE} WHERE session_id=%(sid)s LIMIT 1", parameters={"sid": session_id})
for row in rows.result_rows:
print(f" session_id: {row[0]}, agent_id: {row[1]}")
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=SQLITE_PATH)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(session_id)
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my favourite movie, director, year, and soundtrack composer?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Interstellar'": "interstellar" in r,
"Remembers 'Nolan'": "nolan" in r,
"Remembers '2014'": "2014" in r,
"Remembers 'Zimmer'": "zimmer" in r,
"Session ID continuity": managed2.session_id == session_id,
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
client.command(f"ALTER TABLE {TABLE} DELETE WHERE session_id = '{session_id}'")
client.close()
os.unlink(SQLITE_PATH)
print(f"\n{'PASS' if all_passed else 'FAIL'}: ClickHouse ManagedAgent persistence test")
@@ -1,25 +0,0 @@
"""
CockroachDB Serverless Persistence Example.
CockroachDB Serverless provides distributed SQL with scale-to-zero.
Auto-handles serialization retries (error 40001) and SSL.
Setup:
1. Create free CockroachDB Serverless cluster at https://cockroachlabs.cloud
2. Copy connection string from dashboard
3. Set env var: export COCKROACHDB_URL="postgresql://user:[email protected]:26257/mydb?sslmode=verify-full"
4. pip install praisonai[cockroachdb]
"""
import os
from praisonaiagents import Agent
agent = Agent(
name="CockroachDB Agent",
instructions="You are a helpful assistant.",
db={"database_url": os.getenv("COCKROACHDB_URL")},
session_id="crdb-demo-session",
)
result = agent.start("What is distributed SQL? Explain in 2 sentences.")
print(result)
@@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + JSON File Persistence — Real conversation with session resume.
Uses DefaultSessionStore (JSON files on disk). No external database required.
Prerequisites:
pip install praisonai praisonaiagents
export OPENAI_API_KEY="sk-..."
"""
import json
import os
import shutil
import sys
import tempfile
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig
from praisonaiagents import Agent
from praisonaiagents.session.store import DefaultSessionStore
SESSION_DIR = os.path.join(tempfile.gettempdir(), "managed_json_sessions")
print(f"ManagedAgent + JSON File Persistence\nSession dir: {SESSION_DIR}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
store = DefaultSessionStore(session_dir=SESSION_DIR)
managed = ManagedAgent(
provider="local", session_store=store,
config=LocalManagedConfig(
model="gpt-4o-mini", name="JSON Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My favourite city is Barcelona, Spain. I love the Sagrada Familia. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: I always stay at Hotel Arts in Barcelona. Confirm.")
print(f"Agent: {result2[:200]}...")
# ── Phase 2: Verify JSON files ──
print("\n=== Phase 2: JSON File Verification ===")
json_files = [f for f in os.listdir(SESSION_DIR) if f.endswith(".json")]
print(f"Files: {json_files}")
for fname in json_files:
with open(os.path.join(SESSION_DIR, fname), "r") as f:
data = json.load(f)
print(f" {fname}: {len(data.get('messages', []))} messages")
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
session_id = saved_ids["session_id"]
print(f"Saved IDs: {saved_ids}")
del agent, managed, store
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
store2 = DefaultSessionStore(session_dir=SESSION_DIR)
managed2 = ManagedAgent(provider="local", session_store=store2)
managed2.resume_session(session_id)
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my favourite city, landmark, and hotel?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Barcelona'": "barcelona" in r,
"Remembers 'Sagrada Familia'": "sagrada" in r,
"Remembers 'Hotel Arts'": "hotel arts" in r or "arts" in r,
"Session ID continuity": managed2.session_id == session_id,
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
shutil.rmtree(SESSION_DIR)
print(f"\n{'PASS' if all_passed else 'FAIL'}: JSON File ManagedAgent persistence test")
@@ -1,102 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + MongoDB Persistence — Real conversation with session resume.
MongoDB stores state metadata; SQLite stores conversation history.
Prerequisites:
pip install praisonai praisonaiagents pymongo
export OPENAI_API_KEY="sk-..."
# MongoDB running on localhost:27017
"""
import os
import sys
import tempfile
import pymongo
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
MONGO_URL = os.getenv("MONGO_URL", "mongodb://localhost:27017")
SQLITE_PATH = os.path.join(tempfile.gettempdir(), "managed_mongo_conv.db")
print(f"ManagedAgent + MongoDB Persistence\nMongoDB: {MONGO_URL}\nSQLite: {SQLITE_PATH}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=SQLITE_PATH)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="MongoDB Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My favourite book is 'Dune' by Frank Herbert, published 1965. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: 'Dune Messiah' is my second favourite. Confirm.")
print(f"Agent: {result2[:200]}...")
# Store session metadata into MongoDB for verification
mongo_client = pymongo.MongoClient(MONGO_URL)
mongo_col = mongo_client["praisonai_managed"]["sessions"]
state_doc = {
"_id": managed.session_id,
"agent_id": managed.agent_id,
"total_input_tokens": managed.total_input_tokens,
}
mongo_col.replace_one({"_id": managed.session_id}, state_doc, upsert=True)
# ── Phase 2: MongoDB Verification ──
print("\n=== Phase 2: MongoDB Verification ===")
doc = mongo_col.find_one({"_id": managed.session_id})
print(f" Session in MongoDB: {doc['_id']}" if doc else " WARNING: not found")
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
session_id = saved_ids["session_id"]
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=SQLITE_PATH)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(session_id)
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my favourite book, author, year, and second favourite?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Dune'": "dune" in r,
"Remembers 'Herbert'": "herbert" in r,
"Remembers '1965'": "1965" in r,
"Remembers 'Messiah'": "messiah" in r,
"Session ID continuity": managed2.session_id == session_id,
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
mongo_col.delete_one({"_id": session_id})
mongo_client.close()
os.unlink(SQLITE_PATH)
print(f"\n{'PASS' if all_passed else 'FAIL'}: MongoDB ManagedAgent persistence test")
@@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + MySQL Persistence — Real conversation with session resume.
Prerequisites:
pip install praisonai praisonaiagents mysql-connector-python
export OPENAI_API_KEY="sk-..."
# MySQL running on localhost:3307
"""
import os
import sys
import mysql.connector
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
MYSQL_URL = os.getenv("MYSQL_URL", "mysql://root:praisontest@localhost:3307/praisonai")
print(f"ManagedAgent + MySQL Persistence\nDB: {MYSQL_URL}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=MYSQL_URL)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="MySQL Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: I live in Tokyo, Japan and love ramen from Ichiran. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: I work as a machine learning engineer at a robotics company. Confirm.")
print(f"Agent: {result2[:200]}...")
# ── Phase 2: Direct MySQL Verification ──
print("\n=== Phase 2: MySQL Verification ===")
conn = mysql.connector.connect(host="localhost", port=3307, user="root", password="praisontest", database="praisonai")
cursor = conn.cursor()
cursor.execute("SELECT table_name FROM information_schema.tables WHERE table_schema='praisonai' AND table_name LIKE '%message%'")
msg_tables = [r[0] for r in cursor.fetchall()]
print(f"Message tables: {msg_tables}")
for t in msg_tables:
cursor.execute(f"SELECT COUNT(*) FROM `{t}`")
print(f" {t}: {cursor.fetchone()[0]} rows")
conn.close()
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=MYSQL_URL)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(saved_ids["session_id"])
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("Where do I live, what's my favourite food, and what do I do for work?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Tokyo'": "tokyo" in r,
"Remembers 'ramen'": "ramen" in r,
"Remembers 'Ichiran'": "ichiran" in r,
"Remembers 'machine learning'": "machine learning" in r or "ml" in r,
"Remembers 'robotics'": "robotic" in r,
"Session ID continuity": managed2.session_id == saved_ids["session_id"],
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
print(f"\n{'PASS' if all_passed else 'FAIL'}: MySQL ManagedAgent persistence test")
@@ -1,34 +0,0 @@
"""
Neon Serverless PostgreSQL Persistence Example.
Neon auto-idles after inactivity — you only pay when active.
The PraisonAI adapter auto-handles cold-start retries and SSL.
Setup:
1. Create free Neon project at https://neon.tech
2. Copy connection string from dashboard
3. Set env var: export NEON_DATABASE_URL="postgresql://user:[email protected]/dbname?sslmode=require"
4. pip install praisonai[neon]
"""
import os
from praisonaiagents import Agent
# Option 1: Use NEON_DATABASE_URL env var (recommended)
agent = Agent(
name="Neon Agent",
instructions="You are a helpful assistant.",
db={"database_url": os.getenv("NEON_DATABASE_URL")},
session_id="neon-demo-session",
)
# Option 2: Direct URL
# agent = Agent(
# name="Neon Agent",
# instructions="You are a helpful assistant.",
# db={"database_url": "postgresql://user:[email protected]/dbname?sslmode=require"},
# session_id="neon-demo-session",
# )
result = agent.start("What is serverless PostgreSQL? Explain in 2 sentences.")
print(result)
@@ -1,94 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + PostgreSQL Persistence — Real conversation with session resume.
Prerequisites:
pip install praisonai praisonaiagents psycopg2-binary
export OPENAI_API_KEY="sk-..."
# PostgreSQL running on localhost:5432
"""
import os
import sys
import psycopg2
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
PG_URL = os.getenv("PG_URL", "postgresql://postgres:postgres@localhost:5432/postgres")
print(f"ManagedAgent + PostgreSQL Persistence\nDB: {PG_URL}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=PG_URL)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="PG Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My favourite language is Rust, 3 years experience. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: I'm building a web crawler with the reqwest crate. Confirm.")
print(f"Agent: {result2[:200]}...")
# ── Phase 2: Direct PostgreSQL Verification ──
print("\n=== Phase 2: PostgreSQL Verification ===")
conn = psycopg2.connect(PG_URL)
cursor = conn.cursor()
cursor.execute("""
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_name LIKE '%message%'
""")
msg_tables = [r[0] for r in cursor.fetchall()]
print(f"Message tables: {msg_tables}")
for t in msg_tables:
cursor.execute(f"SELECT COUNT(*) FROM {t}")
print(f" {t}: {cursor.fetchone()[0]} rows")
conn.close()
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=PG_URL)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(saved_ids["session_id"])
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my favourite language, experience, and what am I building?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Rust'": "rust" in r,
"Remembers '3 years'": "3" in r and "year" in r,
"Remembers 'crawler'": "crawler" in r or "crawl" in r,
"Remembers 'reqwest'": "reqwest" in r,
"Session ID continuity": managed2.session_id == saved_ids["session_id"],
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
print(f"\n{'PASS' if all_passed else 'FAIL'}: PostgreSQL ManagedAgent persistence test")
@@ -1,90 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + Redis Persistence — Real conversation with session resume.
Redis stores state/metadata; SQLite stores conversation history.
Prerequisites:
pip install praisonai praisonaiagents redis
export OPENAI_API_KEY="sk-..."
# Redis running on localhost:6379
"""
import os
import sys
import tempfile
import redis as redis_lib
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
REDIS_URL = os.getenv("REDIS_URL", "redis://:myredissecret@localhost:6379/0")
SQLITE_PATH = os.path.join(tempfile.gettempdir(), "managed_redis_conv.db")
print(f"ManagedAgent + Redis Persistence\nRedis: {REDIS_URL}\nSQLite: {SQLITE_PATH}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=SQLITE_PATH, state_url=REDIS_URL)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="Redis Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My cat Luna is a Siamese who loves tuna treats. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: Luna was adopted from a shelter in Portland, Oregon. Confirm.")
print(f"Agent: {result2[:200]}...")
# ── Phase 2: Direct Redis Verification ──
print("\n=== Phase 2: Redis Verification ===")
rc = redis_lib.Redis(host="localhost", port=6379, password="myredissecret", decode_responses=True)
all_keys = rc.keys("*")
print(f"Total Redis keys: {len(all_keys)}")
# ── Phase 3: Destroy instance ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=SQLITE_PATH, state_url=REDIS_URL)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(saved_ids["session_id"])
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my cat's name, breed, favourite treat, and adoption city?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Luna'": "luna" in r,
"Remembers 'Siamese'": "siamese" in r,
"Remembers 'tuna'": "tuna" in r,
"Remembers 'Portland'": "portland" in r,
"Session ID continuity": managed2.session_id == saved_ids["session_id"],
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
os.unlink(SQLITE_PATH)
print(f"\n{'PASS' if all_passed else 'FAIL'}: Redis ManagedAgent persistence test")
@@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""
ManagedAgent + SQLite Persistence — Real conversation with session resume.
Prerequisites:
pip install praisonai praisonaiagents
export OPENAI_API_KEY="sk-..."
"""
import os
import sqlite3
import sys
import tempfile
if not os.getenv("OPENAI_API_KEY"):
sys.exit("ERROR: OPENAI_API_KEY not set.")
from praisonai import ManagedAgent, LocalManagedConfig, DB
from praisonaiagents import Agent
DB_PATH = os.path.join(tempfile.gettempdir(), "managed_sqlite_test.db")
print(f"ManagedAgent + SQLite Persistence\nDB: {DB_PATH}\n")
# ── Phase 1: Create agent, teach facts ──
print("=== Phase 1: First Session ===")
db = DB(database_url=DB_PATH)
managed = ManagedAgent(
provider="local", db=db,
config=LocalManagedConfig(
model="gpt-4o-mini", name="SQLite Memory Agent",
system="You are a helpful assistant. Remember all facts the user tells you.",
),
)
agent = Agent(name="User", backend=managed)
result1 = agent.run("Remember: My dog's name is Biscuit, a golden retriever. Confirm.")
print(f"Agent: {result1[:200]}...")
print(f"Session ID: {managed.session_id}")
result2 = agent.run("Also remember: Biscuit's favourite toy is a blue squeaky ball. Confirm.")
print(f"Agent: {result2[:200]}...")
# ── Phase 2: Verify SQLite directly ──
print("\n=== Phase 2: SQLite Verification ===")
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = [r[0] for r in cursor.fetchall()]
print(f"Tables: {tables}")
for t in tables:
if "message" in t.lower():
cursor.execute(f"SELECT COUNT(*) FROM [{t}]")
print(f" {t}: {cursor.fetchone()[0]} rows")
conn.close()
# ── Phase 3: Destroy instance (simulate idle) ──
print("\n=== Phase 3: Instance Goes Idle ===")
saved_ids = managed.save_ids()
print(f"Saved IDs: {saved_ids}")
del agent, managed, db
print("Agent destroyed.\n")
# ── Phase 4: Resume — no config needed, restored from DB ──
print("=== Phase 4: Resume Session ===")
db2 = DB(database_url=DB_PATH)
managed2 = ManagedAgent(provider="local", db=db2)
managed2.resume_session(saved_ids["session_id"])
print(f"Resumed session: {managed2.session_id}")
agent2 = Agent(name="User", backend=managed2)
result3 = agent2.run("What is my dog's name, breed, and favourite toy?")
print(f"Agent: {result3[:300]}...")
# ── Phase 5: Validate ──
print("\n=== Phase 5: Validation ===")
r = result3.lower()
checks = {
"Remembers 'Biscuit'": "biscuit" in r,
"Remembers 'golden retriever'": "golden retriever" in r,
"Remembers toy": "blue" in r or "squeaky" in r,
"Session ID continuity": managed2.session_id == saved_ids["session_id"],
}
all_passed = True
for check, ok in checks.items():
print(f" [{'PASS' if ok else 'FAIL'}] {check}")
if not ok:
all_passed = False
os.unlink(DB_PATH)
print(f"\n{'PASS' if all_passed else 'FAIL'}: SQLite ManagedAgent persistence test")
@@ -1,28 +0,0 @@
"""
Supabase Direct PostgreSQL Persistence Example.
Supabase offers two connection modes:
1. REST API (via supabase-py) — set SUPABASE_URL + SUPABASE_KEY
2. Direct PostgreSQL (via psycopg2) — use Postgres connection string
This example uses direct PostgreSQL mode with auto-retry for paused projects.
Setup:
1. Create free Supabase project at https://supabase.com
2. Go to Settings > Database > Connection string (URI)
3. Set env var: export SUPABASE_DATABASE_URL="postgresql://postgres.xxx:[pass]@xxx.pooler.supabase.com:6543/postgres"
4. pip install praisonai[postgresql]
"""
import os
from praisonaiagents import Agent
agent = Agent(
name="Supabase Agent",
instructions="You are a helpful assistant.",
db={"database_url": os.getenv("SUPABASE_DATABASE_URL")},
session_id="supabase-demo-session",
)
result = agent.start("What is Supabase? Explain in 2 sentences.")
print(result)
@@ -1,29 +0,0 @@
"""
Turso/libSQL Persistence Example.
Turso provides SQLite-at-the-edge with automatic sync and scale-to-zero.
Setup:
1. Create free Turso database at https://turso.tech
2. Run: turso db tokens create <db-name>
3. Set env vars:
export TURSO_DATABASE_URL="libsql://mydb-user.turso.io"
export TURSO_AUTH_TOKEN="eyJ..."
4. pip install praisonai[turso]
"""
import os
from praisonaiagents import Agent
agent = Agent(
name="Turso Agent",
instructions="You are a helpful assistant.",
db={
"database_url": os.getenv("TURSO_DATABASE_URL"),
"auth_token": os.getenv("TURSO_AUTH_TOKEN"),
},
session_id="turso-demo-session",
)
result = agent.start("What is edge computing? Explain in 2 sentences.")
print(result)
@@ -1,156 +0,0 @@
"""
ClickHouse — Persistence Example
Demonstrates:
- Connecting to ClickHouse
- Creating tables for agent state
- Writing and reading agent metadata
- JSON state storage and retrieval
- Session resume after simulated restart
Requirements:
pip install clickhouse-connect
Docker: ClickHouse on localhost:8123 (user=clickhouse, password=clickhouse)
Run:
python clickhouse_persistence.py
"""
import json
import uuid
import clickhouse_connect
CH_HOST = "localhost"
CH_PORT = 8123
CH_USER = "clickhouse"
CH_PASSWORD = "clickhouse"
print(f"ClickHouse: {CH_HOST}:{CH_PORT}\n")
client = clickhouse_connect.get_client(
host=CH_HOST, port=CH_PORT, username=CH_USER, password=CH_PASSWORD,
)
TABLE_SESSIONS = f"praison_sessions_{uuid.uuid4().hex[:8]}"
TABLE_STATE = f"praison_state_{uuid.uuid4().hex[:8]}"
# --- Phase 1: Create tables and insert session data ---
print("=== Phase 1: Create Tables & Insert Sessions ===")
client.command(f"""
CREATE TABLE IF NOT EXISTS {TABLE_SESSIONS} (
session_id String,
agent_id String,
agent_version UInt32,
total_input_tokens UInt64,
total_output_tokens UInt64,
model String,
created_at DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY session_id
""")
sessions = [
["session_ch_001", "agent_demo_001", 3, 500, 200, "gpt-4o"],
["session_ch_002", "agent_demo_002", 1, 1000, 400, "gpt-4o-mini"],
["session_ch_003", "agent_demo_001", 3, 750, 300, "gpt-4o"],
]
client.insert(
TABLE_SESSIONS, sessions,
column_names=["session_id", "agent_id", "agent_version", "total_input_tokens", "total_output_tokens", "model"],
)
for s in sessions:
print(f" Inserted: {s[0]} (agent={s[1]}, tokens_in={s[3]})")
print()
# --- Phase 2: Query and verify ---
print("=== Phase 2: Query & Verify ===")
result = client.query(f"SELECT * FROM {TABLE_SESSIONS} ORDER BY session_id")
print(f" Total sessions: {len(result.result_rows)}")
for row in result.result_rows:
print(f" {row[0]}: agent={row[1]}, v{row[2]}, tokens_in={row[3]}, model={row[5]}")
assert len(result.result_rows) == 3
assert result.result_rows[0][0] == "session_ch_001"
# Aggregate query
agg = client.query(f"SELECT agent_id, SUM(total_input_tokens) as total_in FROM {TABLE_SESSIONS} GROUP BY agent_id ORDER BY agent_id")
print("\n Tokens by agent:")
for row in agg.result_rows:
print(f" {row[0]}: {row[1]} input tokens")
print()
# --- Phase 3: JSON state storage ---
print("=== Phase 3: JSON State Storage ===")
client.command(f"""
CREATE TABLE IF NOT EXISTS {TABLE_STATE} (
session_id String,
state_json String,
updated_at DateTime DEFAULT now()
) ENGINE = ReplacingMergeTree(updated_at)
ORDER BY session_id
""")
state = {
"agent_id": "agent_demo_001",
"agent_version": 3,
"environment_id": "env_ch_001",
"total_input_tokens": 2000,
"total_output_tokens": 800,
"compute_instance_id": "flyio_ch_001",
"session_history": [
{"id": "session_ch_001", "status": "completed"},
{"id": "session_ch_003", "status": "idle"},
],
}
client.insert(
TABLE_STATE,
[["session_ch_001", json.dumps(state)]],
column_names=["session_id", "state_json"],
)
print(" Stored JSON state for session_ch_001")
# Retrieve
result = client.query(f"SELECT state_json FROM {TABLE_STATE} WHERE session_id = 'session_ch_001'")
recovered = json.loads(result.result_rows[0][0])
print(f" Recovered agent_id: {recovered['agent_id']}")
print(f" Recovered tokens: in={recovered['total_input_tokens']}, out={recovered['total_output_tokens']}")
print(f" Recovered compute: {recovered['compute_instance_id']}")
assert recovered["total_input_tokens"] == 2000
print()
# --- Phase 4: Session Resume ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
client.close()
client2 = clickhouse_connect.get_client(
host=CH_HOST, port=CH_PORT, username=CH_USER, password=CH_PASSWORD,
)
result2 = client2.query(f"SELECT state_json FROM {TABLE_STATE} WHERE session_id = 'session_ch_001'")
assert len(result2.result_rows) > 0, "State not found after restart!"
recovered2 = json.loads(result2.result_rows[0][0])
print(f" Recovered after restart: agent={recovered2['agent_id']}, tokens_in={recovered2['total_input_tokens']}")
# Update state
recovered2["total_input_tokens"] += 500
client2.insert(
TABLE_STATE,
[["session_ch_001", json.dumps(recovered2)]],
column_names=["session_id", "state_json"],
)
print(f" Updated tokens_in: {recovered2['total_input_tokens']}") # noqa: F541
# --- Cleanup ---
print("\n=== Cleanup ===")
client2.command(f"DROP TABLE IF EXISTS {TABLE_SESSIONS}")
client2.command(f"DROP TABLE IF EXISTS {TABLE_STATE}")
client2.close()
print(" Tables dropped.")
print("\n✅ ClickHouse Persistence — All tests passed!")
@@ -1,128 +0,0 @@
"""
JSON File DefaultSessionStore — Full Persistence Example
Demonstrates:
- Creating a DefaultSessionStore (JSON file-backed)
- Adding messages and metadata
- Verifying data on disk
- Session resume after simulated restart
- Full managed agent state roundtrip (7 data categories)
Requirements:
pip install praisonaiagents
Run:
python json_file_session_store.py
"""
import json
import os
import shutil
import tempfile
import uuid
from praisonaiagents.session.store import DefaultSessionStore
# --- Setup ---
session_dir = os.path.join(tempfile.gettempdir(), f"praison_json_example_{uuid.uuid4().hex[:6]}")
os.makedirs(session_dir, exist_ok=True)
print(f"Session directory: {session_dir}\n")
store = DefaultSessionStore(session_dir=session_dir)
SESSION_ID = f"json-demo-{uuid.uuid4().hex[:8]}"
# --- Phase 1: Add messages ---
print("=== Phase 1: Add Messages ===")
messages = [
("user", "What is machine learning?"),
("assistant", "Machine learning is a subset of AI that enables systems to learn from data."),
("user", "Give me an example."),
("assistant", "Spam filters use ML to classify emails as spam or not spam."),
]
for role, content in messages:
store.add_message(SESSION_ID, role, content)
print(f" Added [{role}]: {content[:60]}")
print()
# --- Phase 2: Store metadata (simulating managed agent state) ---
print("=== Phase 2: Store Metadata ===")
session = store.get_session(SESSION_ID)
session.metadata = {
"agent_id": "json_demo_agent_001",
"agent_version": 2,
"environment_id": "env_local_json",
"total_input_tokens": 450,
"total_output_tokens": 180,
"compute_instance_id": "local_json_demo",
"session_history": [
{"id": SESSION_ID, "status": "idle", "title": "ML chat"},
],
}
store._save_session(session)
print(f" agent_id: {session.metadata['agent_id']}")
print(f" tokens: in={session.metadata['total_input_tokens']}, out={session.metadata['total_output_tokens']}")
print(f" compute: {session.metadata['compute_instance_id']}")
print()
# --- Phase 3: Verify on disk ---
print("=== Phase 3: Verify JSON File on Disk ===")
json_file = os.path.join(session_dir, f"{SESSION_ID}.json")
assert os.path.exists(json_file), f"JSON file not found: {json_file}"
with open(json_file) as f:
data = json.load(f)
print(f" File: {json_file}")
print(f" Session ID: {data['session_id']}")
print(f" Messages: {len(data.get('messages', []))}")
print(f" Metadata agent_id: {data['metadata']['agent_id']}")
print(f" Metadata tokens_in: {data['metadata']['total_input_tokens']}")
assert data["metadata"]["agent_id"] == "json_demo_agent_001"
assert data["metadata"]["total_input_tokens"] == 450
assert len(data["messages"]) == 4
print()
# --- Phase 4: Session Resume ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
del store
store2 = DefaultSessionStore(session_dir=session_dir)
# Verify session exists
assert store2.session_exists(SESSION_ID), "Session not found after restart!"
# Recover history
history = store2.get_chat_history(SESSION_ID)
print(f" Recovered messages: {len(history)}")
assert len(history) == 4
# Recover metadata
session2 = store2.get_session(SESSION_ID)
meta = session2.metadata
print(f" Recovered agent_id: {meta['agent_id']}")
print(f" Recovered tokens: in={meta['total_input_tokens']}, out={meta['total_output_tokens']}")
print(f" Recovered compute: {meta['compute_instance_id']}")
print(f" Recovered session_history: {len(meta['session_history'])} entries")
assert meta["agent_id"] == "json_demo_agent_001"
assert meta["total_input_tokens"] == 450
assert meta["compute_instance_id"] == "local_json_demo"
# Continue conversation
store2.add_message(SESSION_ID, "user", "What about deep learning?")
store2.add_message(SESSION_ID, "assistant", "Deep learning uses neural networks with many layers.")
final_history = store2.get_chat_history(SESSION_ID)
print(f" Messages after resume: {len(final_history)}")
assert len(final_history) == 6
# --- Cleanup ---
print("\n=== Cleanup ===")
shutil.rmtree(session_dir)
print(" Session directory removed.")
print("\n✅ JSON File DefaultSessionStore — All tests passed!")
-123
View File
@@ -1,123 +0,0 @@
"""
MongoDB StateStore — Full Persistence Example
Demonstrates:
- Creating a MongoDB StateStore
- Key-value operations (get/set/delete/exists)
- Complex state persistence (agent metadata, usage tokens)
- Direct MongoDB verification
- Session resume after simulated restart
Requirements:
pip install praisonai pymongo
Docker: MongoDB on localhost:27017 (no auth)
Run:
python mongodb_state_store.py
"""
import uuid
import pymongo
from praisonai.persistence.state.mongodb import MongoDBStateStore
MONGO_URL = "mongodb://localhost:27017"
DATABASE = "praisonai_example"
COLLECTION = f"state_{uuid.uuid4().hex[:8]}"
print(f"MongoDB: {MONGO_URL}")
print(f"Database: {DATABASE}, Collection: {COLLECTION}\n")
store = MongoDBStateStore(url=MONGO_URL, database=DATABASE, collection=COLLECTION)
# --- Phase 1: Basic key-value operations ---
print("=== Phase 1: Basic Key-Value Operations ===")
store.set("agent_id", "mongo_demo_agent_001")
store.set("model", "gpt-4o-mini")
store.set("counter", 42)
print(f" agent_id: {store.get('agent_id')}")
print(f" model: {store.get('model')}")
print(f" counter: {store.get('counter')}")
print(f" exists('agent_id'): {store.exists('agent_id')}")
print(f" exists('missing'): {store.exists('missing')}")
assert store.get("agent_id") == "mongo_demo_agent_001"
assert store.get("counter") == 42
print()
# --- Phase 2: Complex state persistence ---
print("=== Phase 2: Complex State Persistence ===")
managed_state = {
"agent_id": "mongo_demo_agent_001",
"agent_version": 4,
"environment_id": "env_mongo_001",
"total_input_tokens": 800,
"total_output_tokens": 300,
"compute_instance_id": "e2b_mongo_demo",
"session_history": [
{"id": "session_m1", "status": "completed"},
{"id": "session_m2", "status": "idle"},
],
}
store.set("managed_state", managed_state)
recovered = store.get("managed_state")
print(f" agent_id: {recovered['agent_id']}")
print(f" agent_version: {recovered['agent_version']}")
print(f" total_input_tokens: {recovered['total_input_tokens']}")
print(f" compute_instance_id: {recovered['compute_instance_id']}")
print(f" session_history count: {len(recovered['session_history'])}")
assert recovered["agent_id"] == "mongo_demo_agent_001"
assert recovered["total_input_tokens"] == 800
print()
# --- Phase 3: Direct MongoDB Verification ---
print("=== Phase 3: Direct MongoDB Verification ===")
client = pymongo.MongoClient(MONGO_URL)
doc = client[DATABASE][COLLECTION].find_one({"_id": "managed_state"})
print(f" Raw doc _id: {doc['_id']}")
print(f" Raw doc value.agent_id: {doc['value']['agent_id']}")
print(f" Raw doc value.total_input_tokens: {doc['value']['total_input_tokens']}")
assert doc["value"]["compute_instance_id"] == "e2b_mongo_demo"
client.close()
print()
# --- Phase 4: Session Resume ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
store.close()
store2 = MongoDBStateStore(url=MONGO_URL, database=DATABASE, collection=COLLECTION)
recovered2 = store2.get("managed_state")
assert recovered2 is not None, "State not found after restart!"
print(f" Recovered agent_id: {recovered2['agent_id']}")
print(f" Recovered tokens: in={recovered2['total_input_tokens']}, out={recovered2['total_output_tokens']}")
print(f" Recovered compute: {recovered2['compute_instance_id']}")
# Update
recovered2["total_input_tokens"] += 400
store2.set("managed_state", recovered2)
final = store2.get("managed_state")
print(f" Updated input_tokens: {final['total_input_tokens']}")
assert final["total_input_tokens"] == 1200
# Delete test
store2.delete("counter")
assert not store2.exists("counter")
print(" Delete verified: counter removed")
store2.close()
# --- Cleanup ---
print("\n=== Cleanup ===")
client = pymongo.MongoClient(MONGO_URL)
client[DATABASE].drop_collection(COLLECTION)
client.close()
print(" Collection dropped.")
print("\n✅ MongoDB StateStore — All tests passed!")
@@ -1,136 +0,0 @@
"""
MySQL ConversationStore — Full Persistence Example
Demonstrates:
- Creating a MySQL ConversationStore
- Creating sessions, adding messages
- Direct SQL verification that data is in MySQL
- Session resume after simulated restart
Requirements:
pip install praisonai mysql-connector-python
Docker: MySQL on localhost:3307 (user=root, password=praisontest, database=praisonai)
Run:
python mysql_conversation_store.py
"""
import uuid
import mysql.connector
from praisonai.persistence.conversation.mysql import MySQLConversationStore
from praisonai.persistence.conversation.base import ConversationSession, ConversationMessage
MYSQL_HOST = "localhost"
MYSQL_PORT = 3307
MYSQL_USER = "root"
MYSQL_PASSWORD = "praisontest"
MYSQL_DB = "praisonai"
TABLE_PREFIX = f"example_{uuid.uuid4().hex[:6]}_"
print(f"MySQL: {MYSQL_USER}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}")
print(f"Table prefix: {TABLE_PREFIX}\n")
store = MySQLConversationStore(
host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PASSWORD, database=MYSQL_DB, table_prefix=TABLE_PREFIX,
)
SESSION_ID = f"mysql-demo-{uuid.uuid4().hex[:8]}"
# --- Phase 1: Create session and add messages ---
print("=== Phase 1: Create Session & Add Messages ===")
session = ConversationSession(
session_id=SESSION_ID,
agent_id="mysql_demo_agent",
name="MySQL Demo",
metadata={"agent_version": 7, "model": "gpt-4o-mini"},
)
store.create_session(session)
messages = [
("user", "What makes MySQL popular?"),
("assistant", "MySQL is popular for its speed, reliability, and ease of use in web applications."),
("user", "What about scalability?"),
("assistant", "MySQL supports replication, sharding, and clustering for horizontal scalability."),
]
for role, content in messages:
msg = ConversationMessage(session_id=SESSION_ID, role=role, content=content)
store.add_message(SESSION_ID, msg)
print(f" Added [{role}]: {content[:60]}")
print()
# --- Phase 2: Verify data ---
print("=== Phase 2: Verify Data in MySQL ===")
retrieved = store.get_session(SESSION_ID)
print(f" Session: {retrieved.session_id}")
print(f" Agent ID: {retrieved.agent_id}")
print(f" Metadata: {retrieved.metadata}")
stored = store.get_messages(SESSION_ID)
print(f" Messages: {len(stored)}")
for msg in stored:
print(f" [{msg.role}] {msg.content[:70]}")
print()
# --- Phase 3: Direct SQL Verification ---
print("=== Phase 3: Direct SQL Verification ===")
conn = mysql.connector.connect(
host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PASSWORD, database=MYSQL_DB,
)
cur = conn.cursor()
cur.execute(f"SELECT COUNT(*) FROM {TABLE_PREFIX}messages WHERE session_id = %s", (SESSION_ID,))
count = cur.fetchone()[0]
print(f" Raw SQL message count: {count}")
assert count == 4, f"Expected 4, got {count}"
cur.execute(f"SELECT agent_id FROM {TABLE_PREFIX}sessions WHERE session_id = %s", (SESSION_ID,))
agent_id = cur.fetchone()[0]
print(f" Raw SQL agent_id: {agent_id}")
assert agent_id == "mysql_demo_agent"
conn.close()
print()
# --- Phase 4: Session Resume ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
store.close()
store2 = MySQLConversationStore(
host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PASSWORD, database=MYSQL_DB, table_prefix=TABLE_PREFIX,
)
resumed = store2.get_session(SESSION_ID)
assert resumed is not None, "Session not found after restart!"
print(f" Resumed session: {resumed.session_id}")
print(f" Metadata preserved: {resumed.metadata}")
msgs = store2.get_messages(SESSION_ID)
print(f" Messages recovered: {len(msgs)}")
assert len(msgs) == 4
# Continue
store2.add_message(SESSION_ID, ConversationMessage(session_id=SESSION_ID, role="user", content="Thanks!"))
store2.add_message(SESSION_ID, ConversationMessage(session_id=SESSION_ID, role="assistant", content="You're welcome!"))
final = store2.get_messages(SESSION_ID)
print(f" Messages after resume: {len(final)}")
assert len(final) == 6
store2.close()
# --- Cleanup ---
print("\n=== Cleanup ===")
conn = mysql.connector.connect(
host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PASSWORD, database=MYSQL_DB,
)
cur = conn.cursor()
cur.execute(f"DROP TABLE IF EXISTS {TABLE_PREFIX}messages")
cur.execute(f"DROP TABLE IF EXISTS {TABLE_PREFIX}sessions")
conn.commit()
conn.close()
print(" Tables dropped.")
print("\n✅ MySQL ConversationStore — All tests passed!")
@@ -1,120 +0,0 @@
"""
PostgreSQL ConversationStore — Full Persistence Example
Demonstrates:
- Creating a PostgreSQL ConversationStore
- Creating sessions, adding messages, storing metadata
- Direct SQL verification that data is in PostgreSQL
- Session resume after simulated restart
Requirements:
pip install praisonai psycopg2-binary
Docker: PostgreSQL on localhost:5432 (user=postgres, password=postgres)
Run:
python postgres_conversation_store.py
"""
import uuid
import psycopg2
from praisonai.persistence.conversation.postgres import PostgresConversationStore
from praisonai.persistence.conversation.base import ConversationSession, ConversationMessage
PG_URL = "postgresql://postgres:postgres@localhost:5432/postgres"
TABLE_PREFIX = f"example_{uuid.uuid4().hex[:6]}_"
print(f"PostgreSQL URL: {PG_URL}")
print(f"Table prefix: {TABLE_PREFIX}\n")
store = PostgresConversationStore(url=PG_URL, table_prefix=TABLE_PREFIX)
SESSION_ID = f"pg-demo-{uuid.uuid4().hex[:8]}"
# --- Phase 1: Create session and add messages ---
print("=== Phase 1: Create Session & Add Messages ===")
session = ConversationSession(
session_id=SESSION_ID,
agent_id="pg_demo_agent",
name="PostgreSQL Demo",
metadata={"agent_version": 5, "model": "gpt-4o"},
)
store.create_session(session)
messages = [
("user", "What are the benefits of PostgreSQL?"),
("assistant", "PostgreSQL offers ACID compliance, extensibility, JSON support, and excellent performance."),
("user", "How does it compare to MySQL?"),
("assistant", "PostgreSQL has better standards compliance and advanced features, while MySQL is simpler for basic use."),
]
for role, content in messages:
msg = ConversationMessage(session_id=SESSION_ID, role=role, content=content)
store.add_message(SESSION_ID, msg)
print(f" Added [{role}]: {content[:60]}")
print()
# --- Phase 2: Verify data in PostgreSQL ---
print("=== Phase 2: Verify Data in PostgreSQL ===")
retrieved = store.get_session(SESSION_ID)
print(f" Session: {retrieved.session_id}")
print(f" Agent ID: {retrieved.agent_id}")
print(f" Metadata: {retrieved.metadata}")
stored = store.get_messages(SESSION_ID)
print(f" Messages: {len(stored)}")
for msg in stored:
print(f" [{msg.role}] {msg.content[:70]}")
print()
# --- Phase 3: Direct SQL Verification ---
print("=== Phase 3: Direct SQL Verification ===")
conn = psycopg2.connect(PG_URL)
cur = conn.cursor()
cur.execute(f"SELECT COUNT(*) FROM public.{TABLE_PREFIX}messages WHERE session_id = %s", (SESSION_ID,))
count = cur.fetchone()[0]
print(f" Raw SQL message count: {count}")
assert count == 4, f"Expected 4, got {count}"
cur.execute(f"SELECT agent_id FROM public.{TABLE_PREFIX}sessions WHERE session_id = %s", (SESSION_ID,))
agent_id = cur.fetchone()[0]
print(f" Raw SQL agent_id: {agent_id}")
assert agent_id == "pg_demo_agent"
conn.close()
print()
# --- Phase 4: Session Resume ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
store.close()
store2 = PostgresConversationStore(url=PG_URL, table_prefix=TABLE_PREFIX)
resumed = store2.get_session(SESSION_ID)
assert resumed is not None, "Session not found after restart!"
print(f" Resumed session: {resumed.session_id}")
print(f" Metadata preserved: {resumed.metadata}")
msgs = store2.get_messages(SESSION_ID)
print(f" Messages recovered: {len(msgs)}")
assert len(msgs) == 4
# Continue
store2.add_message(SESSION_ID, ConversationMessage(session_id=SESSION_ID, role="user", content="Thanks!"))
store2.add_message(SESSION_ID, ConversationMessage(session_id=SESSION_ID, role="assistant", content="You're welcome!"))
final = store2.get_messages(SESSION_ID)
print(f" Messages after resume: {len(final)}")
assert len(final) == 6
store2.close()
# --- Cleanup ---
print("\n=== Cleanup ===")
conn = psycopg2.connect(PG_URL)
conn.autocommit = True
cur = conn.cursor()
cur.execute(f"DROP TABLE IF EXISTS public.{TABLE_PREFIX}messages CASCADE")
cur.execute(f"DROP TABLE IF EXISTS public.{TABLE_PREFIX}sessions CASCADE")
conn.close()
print(" Tables dropped.")
print("\n✅ PostgreSQL ConversationStore — All tests passed!")
-129
View File
@@ -1,129 +0,0 @@
"""
Redis StateStore — Full Persistence Example
Demonstrates:
- Creating a Redis StateStore
- Key-value operations (get/set/delete/exists)
- JSON state persistence (agent metadata, usage tokens)
- Hash operations for structured data
- Session resume: simulate restart, recover state
Requirements:
pip install praisonai redis
Docker: Redis on localhost:6379 (password=myredissecret)
Run:
python redis_state_store.py
"""
import uuid
import redis as redis_lib
from praisonai.persistence.state.redis import RedisStateStore
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_PASSWORD = "myredissecret"
PREFIX = f"example_{uuid.uuid4().hex[:6]}:"
print(f"Redis: {REDIS_HOST}:{REDIS_PORT}")
print(f"Key prefix: {PREFIX}\n")
store = RedisStateStore(
host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, prefix=PREFIX,
)
# --- Phase 1: Basic key-value operations ---
print("=== Phase 1: Basic Key-Value Operations ===")
store.set("agent_id", "redis_demo_agent_001")
store.set("model", "gpt-4o-mini")
store.set("counter", "42")
print(f" agent_id: {store.get('agent_id')}")
print(f" model: {store.get('model')}")
print(f" counter: {store.get('counter')}")
print(f" exists('agent_id'): {store.exists('agent_id')}")
print(f" exists('missing'): {store.exists('missing')}")
print()
# --- Phase 2: JSON state persistence ---
print("=== Phase 2: JSON State Persistence ===")
managed_state = {
"agent_id": "redis_demo_agent_001",
"agent_version": 3,
"environment_id": "env_local_001",
"total_input_tokens": 1500,
"total_output_tokens": 600,
"compute_instance_id": "docker_redis_demo",
"session_history": [
{"id": "session_001", "status": "completed", "title": "Weather chat"},
{"id": "session_002", "status": "idle", "title": "Code review"},
],
}
store.set_json("managed_state", managed_state)
recovered = store.get_json("managed_state")
print(f" agent_id: {recovered['agent_id']}")
print(f" agent_version: {recovered['agent_version']}")
print(f" total_input_tokens: {recovered['total_input_tokens']}")
print(f" total_output_tokens: {recovered['total_output_tokens']}")
print(f" compute_instance_id: {recovered['compute_instance_id']}")
print(f" session_history count: {len(recovered['session_history'])}")
assert recovered["agent_id"] == "redis_demo_agent_001"
assert recovered["total_input_tokens"] == 1500
assert recovered["compute_instance_id"] == "docker_redis_demo"
print()
# --- Phase 3: Hash operations ---
print("=== Phase 3: Hash Operations ===")
store.hset("agent_meta", "version", "5")
store.hset("agent_meta", "env_id", "env_abc")
store.hset("agent_meta", "model", "gpt-4o")
print(f" version: {store.hget('agent_meta', 'version')}")
all_meta = store.hgetall("agent_meta")
print(f" all fields: {all_meta}")
print()
# --- Phase 4: Session Resume (Simulating Restart) ---
print("=== Phase 4: Session Resume (Simulating Restart) ===")
store.close()
# New store instance (simulating process restart)
store2 = RedisStateStore(
host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, prefix=PREFIX,
)
# Recover state
recovered2 = store2.get_json("managed_state")
assert recovered2 is not None, "State not found after restart!"
print(f" Recovered agent_id: {recovered2['agent_id']}")
print(f" Recovered tokens: in={recovered2['total_input_tokens']}, out={recovered2['total_output_tokens']}")
print(f" Recovered compute: {recovered2['compute_instance_id']}")
print(f" Recovered sessions: {len(recovered2['session_history'])}")
# Update state (simulate more work)
recovered2["total_input_tokens"] += 500
recovered2["total_output_tokens"] += 200
store2.set_json("managed_state", recovered2)
final = store2.get_json("managed_state")
print(f" Updated tokens: in={final['total_input_tokens']}, out={final['total_output_tokens']}")
assert final["total_input_tokens"] == 2000
assert final["total_output_tokens"] == 800
store2.close()
# --- Cleanup ---
print("\n=== Cleanup ===")
r = redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD)
for key in r.keys(f"{PREFIX}*"):
r.delete(key)
r.close()
print(" Keys deleted.")
print("\n✅ Redis StateStore — All tests passed!")
@@ -1,112 +0,0 @@
"""
SQLite ConversationStore — Full Persistence Example
Demonstrates:
- Creating a SQLite ConversationStore
- Creating sessions and adding messages
- Verifying data is stored in the SQLite file
- Session resume: destroy agent, restore from DB, verify state
Requirements:
pip install praisonai
Run:
python sqlite_conversation_store.py
"""
import os
import sqlite3
import tempfile
from praisonai.persistence.conversation.sqlite import SQLiteConversationStore
from praisonai.persistence.conversation.base import ConversationSession, ConversationMessage
# --- Setup ---
db_path = os.path.join(tempfile.gettempdir(), "praison_sqlite_example.db")
print(f"SQLite DB path: {db_path}\n")
store = SQLiteConversationStore(path=db_path)
SESSION_ID = "sqlite-demo-session-001"
# --- Phase 1: Create session and add messages ---
print("=== Phase 1: Create Session & Add Messages ===")
session = ConversationSession(
session_id=SESSION_ID,
agent_id="sqlite_demo_agent",
name="SQLite Demo Session",
metadata={"agent_version": 3, "environment": "local"},
)
store.create_session(session)
messages = [
("user", "Hello! What is the capital of France?"),
("assistant", "The capital of France is Paris."),
("user", "What about Germany?"),
("assistant", "The capital of Germany is Berlin."),
]
for role, content in messages:
msg = ConversationMessage(session_id=SESSION_ID, role=role, content=content)
store.add_message(SESSION_ID, msg)
print(f" Added [{role}]: {content[:50]}")
print()
# --- Phase 2: Verify data in DB ---
print("=== Phase 2: Verify Data in SQLite ===")
retrieved_session = store.get_session(SESSION_ID)
print(f" Session ID: {retrieved_session.session_id}")
print(f" Agent ID: {retrieved_session.agent_id}")
print(f" Metadata: {retrieved_session.metadata}")
stored_messages = store.get_messages(SESSION_ID)
print(f" Messages stored: {len(stored_messages)}")
for msg in stored_messages:
print(f" [{msg.role}] {msg.content[:60]}")
print()
# --- Phase 3: Session Resume ---
print("=== Phase 3: Session Resume (Simulating Restart) ===")
store.close()
# Reopen store from same file (simulating process restart)
store2 = SQLiteConversationStore(path=db_path)
# Verify session still exists
resumed_session = store2.get_session(SESSION_ID)
assert resumed_session is not None, "Session not found after restart!"
print(f" Resumed session: {resumed_session.session_id}")
print(f" Agent ID: {resumed_session.agent_id}")
print(f" Metadata preserved: {resumed_session.metadata}")
# Verify messages
resumed_messages = store2.get_messages(SESSION_ID)
print(f" Messages recovered: {len(resumed_messages)}")
assert len(resumed_messages) == 4, f"Expected 4 messages, got {len(resumed_messages)}"
# Continue the conversation
new_msg = ConversationMessage(session_id=SESSION_ID, role="user", content="What about Spain?")
store2.add_message(SESSION_ID, new_msg)
new_msg2 = ConversationMessage(session_id=SESSION_ID, role="assistant", content="The capital of Spain is Madrid.")
store2.add_message(SESSION_ID, new_msg2)
final_messages = store2.get_messages(SESSION_ID)
print(f" Messages after resume: {len(final_messages)}")
assert len(final_messages) == 6
store2.close()
# --- Phase 4: Direct SQLite verification ---
print("\n=== Phase 4: Direct SQLite Verification ===")
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM praison_messages WHERE session_id = ?", (SESSION_ID,))
count = cur.fetchone()[0]
print(f" Raw SQL message count: {count}")
assert count == 6
conn.close()
# Cleanup
os.remove(db_path)
print("\n✅ SQLite ConversationStore — All tests passed!")
@@ -43,34 +43,14 @@ app = FastAPI(
redoc_url="/redoc"
)
# Add CORS middleware with secure configuration
cors_origins = os.getenv("API_CORS_ORIGINS", "").split(",")
cors_origins = [origin.strip() for origin in cors_origins if origin.strip() and origin.strip() != "*"]
# Default secure origins if none specified
if not cors_origins:
# Secure defaults for different environments
if os.getenv("ENVIRONMENT") == "production":
# In production, require explicit configuration
cors_origins = []
else:
# Development defaults - restrict to local origins
cors_origins = [
"http://localhost:3000", # Development frontend
"http://localhost:8000", # Local development
"http://127.0.0.1:3000", # Local development
"http://127.0.0.1:8000", # Local development
]
# Only add CORS middleware if origins are specified
if cors_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type", "Origin", "Accept"],
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Create directories for storing reports
REPORTS_DIR = Path("generated_reports")
@@ -14,17 +14,14 @@ import subprocess
import sys
import os
import json
import shlex
from pathlib import Path
def run_cli(cmd: str, cwd: str = None) -> tuple[int, str, str]:
"""Run a CLI command and return (exit_code, stdout, stderr)."""
# Use shell=False with shlex.split for safer execution
args = shlex.split(cmd)
result = subprocess.run(
args,
shell=False, # Use shell=False for security
cmd,
shell=True,
capture_output=True,
text=True,
cwd=cwd,
@@ -1,9 +0,0 @@
from praisonai import Agent, ManagedAgent
# Zero config — defaults: name="Agent", model="claude-haiku-4-5"
managed = ManagedAgent()
agent = Agent(name="coder", backend=managed)
result = agent.start("Say hello")
print(f"Agent ID: {managed.agent_id}")
print(f"Version: {managed.agent_version}")
@@ -1,17 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Environment is created automatically — just specify packages/networking in config
managed = ManagedAgent(
config=ManagedConfig(
name="Coding Assistant",
model="claude-haiku-4-5",
system="You are a helpful coding assistant. Write clean, well-documented code.",
networking={"type": "unrestricted"},
),
)
agent = Agent(name="coder", backend=managed)
result = agent.start("Say hello")
print(f"Agent ID: {managed.agent_id}")
print(f"Environment ID: {managed.environment_id}")
@@ -1,18 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Agent, environment, and session are all created automatically on first use
managed = ManagedAgent(
config=ManagedConfig(
name="Coding Assistant",
model="claude-haiku-4-5",
system="You are a helpful coding assistant. Write clean, well-documented code.",
session_title="Quickstart session",
),
)
agent = Agent(name="coder", backend=managed)
result = agent.start("Say hello")
print(f"Agent ID: {managed.agent_id}")
print(f"Environment ID: {managed.environment_id}")
print(f"Session ID: {managed.session_id}")
@@ -1,18 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Coding Assistant",
model="claude-haiku-4-5",
system="You are a helpful coding assistant. Write clean, well-documented code.",
),
)
agent = Agent(name="coder", backend=managed)
result = agent.start(
"Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
stream=True,
)
print("\nAgent finished.")
@@ -1,27 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Select specific tools: only bash, read, write — everything else disabled
managed = ManagedAgent(
config=ManagedConfig(
name="Bash Only Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant that can only use bash commands.",
tools=[
{
"type": "agent_toolset_20260401",
"default_config": {"enabled": False},
"configs": [
{"name": "bash", "enabled": True},
{"name": "read", "enabled": True},
{"name": "write", "enabled": True},
],
},
],
),
)
agent = Agent(name="bash-agent", backend=managed)
result = agent.start("List the current directory contents and show system info using uname -a")
print(result)
print("\nAgent finished.")
@@ -1,25 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Disable specific tools: web access disabled, everything else stays on
managed = ManagedAgent(
config=ManagedConfig(
name="No Web Agent",
model="claude-haiku-4-5",
system="You are a coding assistant. You cannot access the web.",
tools=[
{
"type": "agent_toolset_20260401",
"configs": [
{"name": "web_fetch", "enabled": False},
{"name": "web_search", "enabled": False},
],
},
],
),
)
agent = Agent(name="no-web-agent", backend=managed)
result = agent.start("Write a hello world Python script and run it")
print(result)
print("\nAgent finished.")
@@ -1,41 +0,0 @@
import json
from praisonai import Agent, ManagedAgent, ManagedConfig
def handle_weather(tool_name, tool_input):
"""Custom tool callback — PraisonAI calls this when the agent uses get_weather."""
location = tool_input.get("location", "Unknown")
print(f"\n[Custom tool call: {tool_name}]")
print(f"[Input: {json.dumps(tool_input)}]")
return f"Weather in {location}: 15°C, partly cloudy, humidity 72%"
managed = ManagedAgent(
config=ManagedConfig(
name="Weather Agent",
model="claude-haiku-4-5",
system="You are a weather assistant. Use the get_weather tool to check weather.",
tools=[
{"type": "agent_toolset_20260401"},
{
"type": "custom",
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
},
"required": ["location"],
},
},
],
),
on_custom_tool=handle_weather,
)
agent = Agent(name="weather-agent", backend=managed)
result = agent.start("What is the weather in London?")
print(result)
print("\nAgent finished.")
@@ -1,22 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="My Agent v1",
model="claude-haiku-4-5",
system="You are a helpful assistant.",
),
)
agent = Agent(name="updatable", backend=managed)
# First call creates the agent
result = agent.start("Say hello briefly")
print(f"Created: {managed.agent_id}, version: {managed.agent_version}")
# Update the agent's system prompt (no need to recreate)
managed.update_agent(
name="My Agent v2",
system="You are a senior Python developer. Write production-quality code.",
)
print(f"Updated: {managed.agent_id}, version: {managed.agent_version}")
@@ -1,23 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Session Demo Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant.",
),
)
agent = Agent(name="session-demo", backend=managed)
# Run a task to create agent + session
agent.start("Say hello briefly")
# List sessions for this agent
sessions = managed.list_sessions()
print(f"Total sessions: {len(sessions)}")
for s in sessions[:5]:
print(f" ID: {s['id']}")
print(f" Status: {s['status']}")
print(f" Title: {s['title']}")
print()
@@ -1,17 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Research Agent",
model="claude-haiku-4-5",
system="You are a research assistant. Search the web for information.",
),
)
agent = Agent(name="researcher", backend=managed)
result = agent.start(
"Search the web for the latest Python 3.13 features and summarize them in 3 bullet points"
)
print(result)
print("\nAgent finished.")
@@ -1,25 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Multi Turn Agent",
model="claude-haiku-4-5",
system="You are a helpful coding assistant.",
),
)
agent = Agent(name="multi-turn", backend=managed)
print(f"Session ID: {managed.session_id}")
# Multi-turn: each call reuses the same session — agent remembers context
messages = [
"Create a file called greeting.py that prints 'Hello World'",
"Now modify greeting.py to accept a name as a command line argument",
"Run greeting.py with the argument 'Claude'",
]
for i, message in enumerate(messages):
print(f"\n--- Turn {i + 1}: {message} ---\n")
result = agent.start(message)
print(result)
print("\nAgent finished turn.")
@@ -1,22 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Just add packages to the config — environment is created automatically
managed = ManagedAgent(
config=ManagedConfig(
name="Data Science Agent",
model="claude-haiku-4-5",
system="You are a data science assistant.",
packages={"pip": ["pandas", "numpy"]},
),
)
agent = Agent(name="data-scientist", backend=managed)
print(f"Environment ID: {managed.environment_id}")
result = agent.start(
"Create a Python script that generates random sales data with pandas and saves a summary to sales_summary.txt"
)
print(result)
print("\nAgent finished.")
@@ -1,20 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Interruptable Agent",
model="claude-haiku-4-5",
system="You are a helpful coding assistant.",
),
)
agent = Agent(name="interruptable", backend=managed)
# Start a task
result = agent.start("Write a Python script that prints numbers 1 to 5")
print(result)
# Interrupt the agent mid-work
print("\n[Sending interrupt...]")
managed.interrupt()
print("Agent stopped.")
@@ -1,28 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Usage Tracker Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant.",
),
)
agent = Agent(name="tracker", backend=managed)
result = agent.start("Write a one-line Python script that prints the current date and run it")
print(result)
print("\nAgent finished.")
# Usage tracked automatically
print("\n--- Usage Report ---")
print(f"Input tokens: {managed.total_input_tokens}")
print(f"Output tokens: {managed.total_output_tokens}")
# Or retrieve detailed session info from the API
info = managed.retrieve_session()
print(f"\nSession ID: {info.get('id')}")
print(f"Status: {info.get('status')}")
if "usage" in info:
print(f"API Input tokens: {info['usage']['input_tokens']}")
print(f"API Output tokens: {info['usage']['output_tokens']}")
@@ -1,35 +0,0 @@
import json
import pathlib
from praisonai import Agent, ManagedAgent, ManagedConfig
IDS_FILE = pathlib.Path("managed_ids.json")
if IDS_FILE.exists():
saved = json.loads(IDS_FILE.read_text())
print(f"Resuming session: {saved['session_id']}\n")
managed = ManagedAgent()
managed.resume_session(saved["session_id"])
agent = Agent(name="coder", backend=managed)
result = agent.start("What is my favourite number?", stream=True)
else:
managed = ManagedAgent(
config=ManagedConfig(
name="Persistent Coder",
model="claude-haiku-4-5",
system="You are a helpful coding assistant.",
),
)
agent = Agent(name="coder", backend=managed)
result = agent.start("Remember this: my favourite number is 42.", stream=True)
ids = managed.save_ids()
IDS_FILE.write_text(json.dumps(ids, indent=2))
print(f"\nSaved IDs to {IDS_FILE}")
print(f" agent_id : {managed.agent_id}")
print(f" environment_id: {managed.environment_id}")
print(f" session_id : {managed.session_id}")
print("\nRun this script again to resume the session.")
@@ -1,21 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
name="Session Demo",
model="claude-haiku-4-5",
system="You are a helpful assistant.",
),
)
agent = Agent(name="demo", backend=managed)
agent.start("Say hello and confirm you are ready.", stream=True)
print("\n--- IDs ---")
print(f"agent_id : {managed.agent_id}")
print(f"environment_id: {managed.environment_id}")
print(f"session_id : {managed.session_id}")
print(f"chat_history : {len(agent.chat_history)} messages")
ids = managed.save_ids()
print(f"\nsave_ids(): {ids}")
@@ -1,22 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Multiple package managers — pip + npm installed before agent starts
managed = ManagedAgent(
config=ManagedConfig(
name="Full Stack Agent",
model="claude-haiku-4-5",
system="You are a full stack developer.",
packages={
"pip": ["pandas", "numpy", "scikit-learn"],
"npm": ["express"],
},
),
)
agent = Agent(name="fullstack", backend=managed)
result = agent.start(
"Verify pandas and express are installed: run 'python3 -c \"import pandas; print(pandas.__version__)\"' and 'node -e \"console.log(require.resolve('express'))\"'",
stream=True,
)
print("\nAgent finished.")
@@ -1,24 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Limited networking: restrict container to specific hosts only
managed = ManagedAgent(
config=ManagedConfig(
name="Restricted Network Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant with restricted network access.",
networking={
"type": "limited",
"allowed_hosts": ["api.github.com"],
"allow_mcp_servers": False,
"allow_package_managers": True,
},
),
)
agent = Agent(name="restricted", backend=managed)
result = agent.start(
"Try to fetch https://api.github.com and report what you get. Then try https://example.com — it should fail.",
stream=True,
)
print("\nAgent finished.")
@@ -1,43 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# Environment management: list, retrieve, archive, delete
managed = ManagedAgent(
config=ManagedConfig(
name="Env Management Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant.",
),
)
agent = Agent(name="env-mgmt", backend=managed)
# First call creates agent + environment + session
agent.start("Say hello briefly")
# Access the underlying Anthropic client for environment management
client = managed._get_client()
env_id = managed.environment_id
print(f"Environment ID: {env_id}")
# List all environments
environments = client.beta.environments.list()
print(f"\nTotal environments: {len(environments.data)}")
for env in environments.data[:5]:
print(f" {env.id} | {env.name} | {getattr(env, 'status', 'active')}")
# Retrieve a specific environment
env = client.beta.environments.retrieve(env_id)
print(f"\nRetrieved: {env.id} | {env.name}")
# Archive (read-only, existing sessions continue)
archived = client.beta.environments.archive(env_id)
print(f"Archived: {archived.id} | status: {getattr(archived, 'status', 'archived')}")
# Delete (only if no sessions reference it — may fail if sessions exist)
try:
client.beta.environments.delete(env_id)
print(f"Deleted: {env_id}")
except Exception as e:
print(f"Delete skipped (expected if sessions exist): {e}")
print("\nEnvironment management complete.")
@@ -1,35 +0,0 @@
from praisonai import Agent, ManagedAgent, ManagedConfig
# MCP servers: configure agent with remote MCP tool servers
# Note: Anthropic's managed agents only support URL-based (SSE) MCP servers
managed = ManagedAgent(
config=ManagedConfig(
name="MCP Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant with access to MCP servers.",
tools=[
{"type": "agent_toolset_20260401"},
{"type": "mcp_toolset", "mcp_server_name": "deepwiki"},
],
mcp_servers=[
{
"type": "url",
"url": "https://mcp.deepwiki.com/sse",
"name": "deepwiki",
},
],
networking={
"type": "limited",
"allow_mcp_servers": True,
"allow_package_managers": True,
},
),
)
agent = Agent(name="mcp-agent", backend=managed)
result = agent.start(
"Use the deepwiki MCP to read the wiki page for the anthropics/anthropic-cookbook github repo and give a one sentence summary",
stream=True,
)
print("\nAgent finished.")
-340
View File
@@ -1,340 +0,0 @@
import json
import pathlib
from praisonai import Agent, ManagedAgent, ManagedConfig
# 1. Create an agent
managed = ManagedAgent()
agent = Agent(name="teacher", backend=managed)
result = agent.start("Say hello briefly", stream=True)
print(f"[1] Agent created: {managed.agent_id} (v{managed.agent_version})")
# 2. Update the agent
managed.update_agent(
name="Teaching Agent v2",
system="You are a senior Python developer. Write clean, production-quality code.",
)
print(f"[2] Agent updated: Teaching Agent v2 (v{managed.agent_version})")
# 3-4. Environment + Session are created automatically (already done in step 1)
print(f"[3] Environment created: {managed.environment_id}")
print(f"[4] Session created: {managed.session_id}")
# 5. Stream a response
print("\n[5] Streaming response...")
result = agent.start("Write a Python script that prints 'Hello from Managed Agents!' and run it", stream=True)
# 6. Multi-turn conversation (same session remembers context)
print("\n[6] Multi-turn: sending follow-up...")
result = agent.start("Now modify that script to accept a name argument and greet that person", stream=True)
# 7. Track usage
info = managed.retrieve_session()
print("\n[7] Usage report:")
if info.get("usage"):
print(f" Input tokens: {info['usage']['input_tokens']}")
print(f" Output tokens: {info['usage']['output_tokens']}")
else:
print(f" Input tokens: {managed.total_input_tokens}")
print(f" Output tokens: {managed.total_output_tokens}")
# 8. List sessions
sessions = managed.list_sessions()
print(f"\n[8] Total sessions: {len(sessions)}")
for s in sessions[:3]:
print(f" {s['id']} | {s['status']} | {s['title']}")
# 9. Selective tools (only bash + read + write)
bash_managed = ManagedAgent(
config=ManagedConfig(
name="Bash Only Agent",
model="claude-haiku-4-5",
system="You can only use bash, read, and write tools.",
tools=[
{
"type": "agent_toolset_20260401",
"default_config": {"enabled": False},
"configs": [
{"name": "bash", "enabled": True},
{"name": "read", "enabled": True},
{"name": "write", "enabled": True},
],
},
],
),
)
bash_agent = Agent(name="bash-only", backend=bash_managed)
print("\n[9] Bash-only agent streaming...")
result = bash_agent.start("Show the current date and Python version using bash", stream=True)
# 10. Disable specific tools (web disabled, everything else on)
no_web_managed = ManagedAgent(
config=ManagedConfig(
name="No Web Agent",
model="claude-haiku-4-5",
system="You are a coding assistant. You cannot access the web.",
tools=[
{
"type": "agent_toolset_20260401",
"configs": [
{"name": "web_fetch", "enabled": False},
{"name": "web_search", "enabled": False},
],
},
],
),
)
no_web_agent = Agent(name="no-web", backend=no_web_managed)
print("\n[10] No-web agent streaming...")
result = no_web_agent.start("Write a Python one-liner that calculates 2**100 and print the result", stream=True)
# 11. Custom tools (you define the tool, PraisonAI calls your callback)
def handle_weather(tool_name, tool_input):
print(f"\n [Custom tool: {tool_name} | Input: {json.dumps(tool_input)}]")
return "Tokyo: 22°C, sunny, humidity 55%"
custom_managed = ManagedAgent(
config=ManagedConfig(
name="Weather Agent",
model="claude-haiku-4-5",
system="You are a weather assistant. Use the get_weather tool to check weather.",
tools=[
{"type": "agent_toolset_20260401"},
{
"type": "custom",
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
},
"required": ["location"],
},
},
],
),
on_custom_tool=handle_weather,
)
custom_agent = Agent(name="weather", backend=custom_managed)
print("\n[11] Custom tool agent streaming...")
result = custom_agent.start("What is the weather in Tokyo?", stream=True)
# 12. Web search agent
search_managed = ManagedAgent(
config=ManagedConfig(
name="Search Agent",
model="claude-haiku-4-5",
system="You are a research assistant. Search the web and summarize.",
),
)
search_agent = Agent(name="searcher", backend=search_managed)
print("\n[12] Web search agent streaming...")
result = search_agent.start("Search the web for Python 3.13 new features and give me 3 bullet points", stream=True)
# 13. Environment with pre-installed packages
data_managed = ManagedAgent(
config=ManagedConfig(
name="Data Science Agent",
model="claude-haiku-4-5",
system="You are a data science assistant.",
packages={"pip": ["pandas", "numpy"]},
),
)
data_agent = Agent(name="data-scientist", backend=data_managed)
print("\n[13] Data science environment streaming...")
result = data_agent.start("Use pandas to create a small DataFrame with 3 rows of sample data and print it", stream=True)
# 14. Interrupt a session
interrupt_managed = ManagedAgent(
config=ManagedConfig(
name="Interruptable Agent",
model="claude-haiku-4-5",
system="You are a helpful coding assistant.",
),
)
interrupt_agent = Agent(name="interruptable", backend=interrupt_managed)
print("\n[14] Interrupt demo...")
result = interrupt_agent.start("Write a Python script that prints numbers 1 to 10", stream=True)
interrupt_managed.interrupt()
print(" [Interrupt sent]")
# 15. Session resume — save IDs, create a fresh ManagedAgent, resume, and verify context
print("\n[15] Session resume demo...")
# Tell the original agent a memorable fact
result = agent.start("Remember this: my favourite number is 42", stream=True)
# Save IDs to disk
ids = managed.save_ids()
ids_file = pathlib.Path("managed_ids.json")
ids_file.write_text(json.dumps(ids, indent=2))
print(f" Saved IDs to {ids_file}: {ids}")
# Create a completely new ManagedAgent and resume the saved session
resume_managed = ManagedAgent()
resume_managed.resume_session(ids["session_id"])
resume_agent = Agent(name="resumed", backend=resume_managed)
# Ask about the fact — proves the session memory was preserved
result = resume_agent.start("What is my favourite number?", stream=True)
print(f" Resumed session: {resume_managed.session_id}")
# Clean up
ids_file.unlink(missing_ok=True)
# 16. Multi-package managers — pip + npm installed before agent starts
print("\n[16] Multi-package managers...")
multi_pkg_managed = ManagedAgent(
config=ManagedConfig(
name="Full Stack Agent",
model="claude-haiku-4-5",
system="You are a full stack developer.",
packages={
"pip": ["pandas", "numpy", "scikit-learn"],
"npm": ["express"],
},
),
)
multi_pkg_agent = Agent(name="fullstack", backend=multi_pkg_managed)
result = multi_pkg_agent.start(
"Verify pandas and express are installed: run python3 -c 'import pandas; print(pandas.__version__)' and node -e 'console.log(require.resolve(\"express\"))'",
stream=True,
)
# 17. Limited networking — restrict container to specific hosts
print("\n[17] Limited networking...")
limited_net_managed = ManagedAgent(
config=ManagedConfig(
name="Restricted Network Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant with restricted network access.",
networking={
"type": "limited",
"allowed_hosts": ["api.github.com"],
"allow_mcp_servers": False,
"allow_package_managers": True,
},
),
)
limited_net_agent = Agent(name="restricted", backend=limited_net_managed)
result = limited_net_agent.start("Fetch https://api.github.com and report the status", stream=True)
# 18. Environment management — list, retrieve, archive
print("\n[18] Environment management...")
env_client = managed._get_client()
# List all environments
environments = env_client.beta.environments.list()
print(f" Total environments: {len(environments.data)}")
for env in environments.data[:3]:
print(f" {env.id} | {env.name}")
# Retrieve a specific environment
env = env_client.beta.environments.retrieve(managed.environment_id)
print(f" Retrieved: {env.id} | {env.name}")
# 19. MCP servers — configure agent with remote MCP tool servers
print("\n[19] MCP servers...")
mcp_managed = ManagedAgent(
config=ManagedConfig(
name="MCP Agent",
model="claude-haiku-4-5",
system="You are a helpful assistant with access to MCP servers.",
tools=[
{"type": "agent_toolset_20260401"},
{"type": "mcp_toolset", "mcp_server_name": "deepwiki"},
],
mcp_servers=[
{
"type": "url",
"url": "https://mcp.deepwiki.com/sse",
"name": "deepwiki",
},
],
networking={
"type": "limited",
"allow_mcp_servers": True,
"allow_package_managers": True,
},
),
)
mcp_agent = Agent(name="mcp-agent", backend=mcp_managed)
result = mcp_agent.start("Use the deepwiki MCP to read the wiki page for the anthropics/anthropic-cookbook github repo and give a one sentence summary", stream=True)
# Final usage summary
print("\n" + "=" * 60)
print("FINAL USAGE SUMMARY")
print("=" * 60)
all_backends = [
("Teaching Agent v2", managed),
("Bash Only Agent", bash_managed),
("No Web Agent", no_web_managed),
("Weather Agent", custom_managed),
("Search Agent", search_managed),
("Data Science Agent", data_managed),
("Interruptable Agent", interrupt_managed),
("Resumed Session", resume_managed),
("Full Stack Agent", multi_pkg_managed),
("Restricted Network", limited_net_managed),
("MCP Agent", mcp_managed),
]
total_input = 0
total_output = 0
for name, backend in all_backends:
info = backend.retrieve_session()
usage = info.get("usage", {})
inp = usage.get("input_tokens", backend.total_input_tokens)
out = usage.get("output_tokens", backend.total_output_tokens)
total_input += inp
total_output += out
print(f" {name:30s} | in: {inp:6d} | out: {out:6d}")
print(f" {'TOTAL':30s} | in: {total_input:6d} | out: {total_output:6d}")
print("=" * 60)
-5
View File
@@ -1,5 +0,0 @@
from praisonai import Agent, ManagedAgent
agent = Agent(name="teacher", backend=ManagedAgent())
result = agent.start("Write a Python script that prints 'Hello from Managed Agents!' and run it")
print(result)
@@ -1,117 +0,0 @@
"""All compute providers — comprehensive test across Local, Docker, E2B, and Modal.
This example mirrors the Anthropic app.py but uses the local provider with
various compute backends instead of Anthropic's managed infrastructure.
Requires:
- Docker running locally
- E2B_API_KEY set
- modal CLI configured (modal token set)
"""
import asyncio
from praisonai import Agent, ManagedAgent, LocalManagedConfig
async def test_provider(name, compute, extra_provision_kwargs=None):
"""Test a single compute provider end-to-end."""
print(f"\n{'='*60}")
print(f" PROVIDER: {name}")
print(f"{'='*60}")
managed = ManagedAgent(
provider="local",
compute=compute,
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful assistant. Be concise.",
name=f"{name}Agent",
),
)
agent = Agent(name=f"{name}-test", backend=managed)
# 1. Provision
print(f"\n [1] Provisioning {name}...")
provision_kwargs = extra_provision_kwargs or {}
info = await managed.provision_compute(**provision_kwargs)
print(f" Instance: {info.instance_id}")
print(f" Status: {info.status}")
# 2. Execute command
print(f"\n [2] Execute in {name}...")
result = await managed.execute_in_compute("python3 -c 'print(42 * 13)'")
stdout = result["stdout"].strip()
assert "546" in stdout, f"Expected 546, got: {stdout}"
print(f" 42 * 13 = {stdout} ✓")
# 3. Execute echo
result = await managed.execute_in_compute(f"echo 'Hello from {name}'")
print(f" Echo: {result['stdout'].strip()}")
# 4. Agent LLM
print(f"\n [3] Agent LLM via {name}...")
llm_result = agent.start("What is 9 * 8? Just the number.", stream=True)
print(f" LLM result: {llm_result}")
# 5. Multi-turn
print("\n [4] Multi-turn...")
llm_result = agent.start("Add 10 to that. Just the number.", stream=True)
print(f" Follow-up: {llm_result}")
# 6. Usage
session = managed.retrieve_session()
usage = session["usage"]
print(f"\n [5] Usage: in={usage['input_tokens']}, out={usage['output_tokens']}")
# 7. Shutdown
print(f"\n [6] Shutdown {name}...")
await managed.shutdown_compute()
print(" Done ✓")
return {
"name": name,
"input_tokens": usage["input_tokens"],
"output_tokens": usage["output_tokens"],
}
async def main():
results = []
# Docker
try:
r = await test_provider("Docker", "docker", {"image": "python:3.12-slim"})
results.append(r)
except Exception as e:
print(f"\n Docker FAILED: {e}")
# E2B
try:
r = await test_provider("E2B", "e2b", {"idle_timeout_s": 120})
results.append(r)
except Exception as e:
print(f"\n E2B FAILED: {e}")
# Modal
try:
r = await test_provider("Modal", "modal", {"idle_timeout_s": 120})
results.append(r)
except Exception as e:
print(f"\n Modal FAILED: {e}")
# Summary
print(f"\n\n{'='*60}")
print(" SUMMARY")
print(f"{'='*60}")
total_in = 0
total_out = 0
for r in results:
total_in += r["input_tokens"]
total_out += r["output_tokens"]
print(f" {r['name']:15s} | in: {r['input_tokens']:6d} | out: {r['output_tokens']:6d}")
print(f" {'TOTAL':15s} | in: {total_in:6d} | out: {total_out:6d}")
print(f"{'='*60}")
print(f" {len(results)}/{3} providers passed")
if __name__ == "__main__":
asyncio.run(main())
@@ -1,55 +0,0 @@
"""Daytona compute provider — run agent tools inside a Daytona cloud sandbox.
Requires: DAYTONA_API_KEY environment variable set.
Organization must have a default region configured in Daytona Dashboard.
Install: pip install daytona-sdk
"""
import asyncio
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# ── 1. Create agent with Daytona compute ──
managed = ManagedAgent(
provider="local",
compute="daytona",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful coding assistant. Be concise.",
name="DaytonaAgent",
),
)
agent = Agent(name="daytona-agent", backend=managed)
print("[1] Agent created with Daytona compute")
print(f" Compute: {managed.compute_provider.provider_name}")
# ── 2. Provision Daytona sandbox ──
print("\n[2] Provisioning Daytona sandbox...")
info = asyncio.run(managed.provision_compute(idle_timeout_s=120))
print(f" Instance: {info.instance_id}")
print(f" Status: {info.status}")
# ── 3. Execute commands ──
print("\n[3] Executing commands in Daytona...")
result = asyncio.run(managed.execute_in_compute("python3 -c 'import sys; print(sys.version)'"))
print(f" Python version: {result['stdout'].strip()}")
print(f" Exit code: {result['exit_code']}")
result = asyncio.run(managed.execute_in_compute("echo 'Hello from Daytona!'"))
print(f" Echo: {result['stdout'].strip()}")
# ── 4. Agent LLM execution ──
print("\n[4] Agent LLM execution...")
result = agent.start("What is 11 * 13? Just the number.", stream=True)
print(f" Result: {result}")
# ── 5. Usage ──
info = managed.retrieve_session()
print(f"\n[5] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# ── 6. Shutdown ──
print("\n[6] Shutting down Daytona sandbox...")
asyncio.run(managed.shutdown_compute())
print(" Shutdown complete.")
print("\nDone!")
@@ -1,67 +0,0 @@
"""Docker compute provider — run agent tools inside a Docker container.
Requires: Docker running locally.
"""
import asyncio
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# ── 1. Basic agent with Docker compute ──
managed = ManagedAgent(
provider="local",
compute="docker",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful coding assistant. Be concise.",
name="DockerAgent",
),
)
agent = Agent(name="docker-agent", backend=managed)
print("[1] Agent created with Docker compute")
print(f" Agent ID: {managed.agent_id or '(lazy — created on first call)'}")
print(f" Compute: {managed.compute_provider.provider_name}")
# ── 2. Provision Docker container ──
print("\n[2] Provisioning Docker container...")
info = asyncio.run(managed.provision_compute(image="python:3.12-slim"))
print(f" Instance: {info.instance_id}")
print(f" Status: {info.status}")
# ── 3. Execute commands inside the container ──
print("\n[3] Executing commands in Docker...")
result = asyncio.run(managed.execute_in_compute("python3 -c 'import sys; print(sys.version)'"))
print(f" Python version: {result['stdout'].strip()}")
print(f" Exit code: {result['exit_code']}")
result = asyncio.run(managed.execute_in_compute("echo 'Hello from Docker!'"))
print(f" Echo: {result['stdout'].strip()}")
# ── 4. Install packages in the container ──
print("\n[4] Installing packages...")
result = asyncio.run(managed.execute_in_compute("pip install requests -q"))
print(f" pip exit code: {result['exit_code']}")
result = asyncio.run(managed.execute_in_compute("python3 -c 'import requests; print(requests.__version__)'"))
print(f" requests version: {result['stdout'].strip()}")
# ── 5. Use agent with LLM (runs locally, compute is for tool sandboxing) ──
print("\n[5] Agent LLM execution...")
result = agent.start("What is 15 * 23? Just the number.", stream=True)
print(f" Result: {result}")
# ── 6. Multi-turn ──
print("\n[6] Multi-turn...")
result = agent.start("Double that number.", stream=True)
print(f" Result: {result}")
# ── 7. Usage tracking ──
info = managed.retrieve_session()
print(f"\n[7] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# ── 8. Shutdown Docker container ──
print("\n[8] Shutting down Docker container...")
asyncio.run(managed.shutdown_compute())
print(" Shutdown complete.")
print("\nDone!")
@@ -1,66 +0,0 @@
"""E2B compute provider — run agent tools inside an E2B cloud sandbox.
Requires: E2B_API_KEY environment variable set.
Install: pip install e2b
"""
import asyncio
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# ── 1. Create agent with E2B compute ──
managed = ManagedAgent(
provider="local",
compute="e2b",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful coding assistant. Be concise.",
name="E2BAgent",
),
)
agent = Agent(name="e2b-agent", backend=managed)
print("[1] Agent created with E2B compute")
print(f" Compute: {managed.compute_provider.provider_name}")
# ── 2. Provision E2B sandbox ──
print("\n[2] Provisioning E2B sandbox...")
info = asyncio.run(managed.provision_compute(idle_timeout_s=120))
print(f" Instance: {info.instance_id}")
print(f" Status: {info.status}")
# ── 3. Execute commands in the sandbox ──
print("\n[3] Executing commands in E2B...")
result = asyncio.run(managed.execute_in_compute("python3 -c 'import sys; print(sys.version)'"))
print(f" Python version: {result['stdout'].strip()}")
print(f" Exit code: {result['exit_code']}")
result = asyncio.run(managed.execute_in_compute("echo 'Hello from E2B!'"))
print(f" Echo: {result['stdout'].strip()}")
# ── 4. Run a Python script ──
print("\n[4] Running Python script...")
result = asyncio.run(managed.execute_in_compute(
"python3 -c 'for i in range(5): print(f\"Line {i}\")'"
))
print(f" Output:\n{result['stdout']}")
# ── 5. Agent LLM execution ──
print("[5] Agent LLM execution...")
result = agent.start("What is the square root of 144? Just the number.", stream=True)
print(f" Result: {result}")
# ── 6. Multi-turn ──
print("\n[6] Multi-turn...")
result = agent.start("Now cube that number.", stream=True)
print(f" Result: {result}")
# ── 7. Usage ──
info = managed.retrieve_session()
print(f"\n[7] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# ── 8. Shutdown ──
print("\n[8] Shutting down E2B sandbox...")
asyncio.run(managed.shutdown_compute())
print(" Shutdown complete.")
print("\nDone!")
@@ -1,96 +0,0 @@
"""Local provider — advanced features: update agent, custom tools, session resume, packages."""
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# ── 1. Create agent ──
managed = ManagedAgent(
provider="local",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful math tutor.",
name="MathTutor",
),
)
agent = Agent(name="tutor", backend=managed)
result = agent.start("What is 17 * 23?", stream=True)
print(f"[1] Created agent: {managed.agent_id} (v{managed.agent_version})")
# ── 2. Update agent ──
managed.update_agent(
name="Senior Math Tutor",
system="You are a senior math tutor. Always show your work step by step.",
)
print(f"[2] Updated to v{managed.agent_version}")
result = agent.start("What is 123 + 456?", stream=True)
# ── 3. Custom tool ──
def handle_calculator(tool_name, tool_input):
expr = tool_input.get("expression", "0")
try:
val = eval(expr, {"__builtins__": {}})
except Exception:
val = "error"
print(f" [Calculator: {expr} = {val}]")
return str(val)
custom_managed = ManagedAgent(
provider="local",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are an assistant with a calculator tool. Use it for math.",
name="CalcAgent",
tools=[
{
"type": "custom",
"name": "calculator",
"description": "Evaluate a math expression",
"input_schema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "Math expression"},
},
"required": ["expression"],
},
},
],
),
on_custom_tool=handle_calculator,
)
custom_agent = Agent(name="calc", backend=custom_managed)
print("\n[3] Custom tool agent...")
result = custom_agent.start("Use the calculator to compute 99 * 77", stream=True)
# ── 4. Session resume ──
print("\n[4] Session resume...")
result = agent.start("Remember: my lucky number is 7", stream=True)
ids = managed.save_ids()
print(f" Saved IDs: {ids}")
resume_managed = ManagedAgent(provider="local", config=LocalManagedConfig(model="gpt-4o-mini"))
resume_managed.restore_ids(ids)
resume_agent = Agent(name="resumed", backend=resume_managed)
result = resume_agent.start("What is my lucky number?", stream=True)
print(f" Resumed session: {resume_managed.session_id}")
# ── 5. Interrupt ──
print("\n[5] Interrupt demo...")
int_managed = ManagedAgent(
provider="local",
config=LocalManagedConfig(model="gpt-4o-mini", system="Be helpful.", name="IntAgent"),
)
int_agent = Agent(name="interruptable", backend=int_managed)
result = int_agent.start("Count from 1 to 5", stream=True)
int_managed.interrupt()
print(" [Interrupt sent]")
# ── 6. Reset session ──
print("\n[6] Reset session...")
managed.reset_session()
result = agent.start("What was my lucky number?", stream=True)
print(" (new session — context lost, expected)")
# ── Usage summary ──
print("\n" + "=" * 50)
info = managed.retrieve_session()
print(f"Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
print("=" * 50)
print("Done!")
@@ -1,45 +0,0 @@
"""Local provider — basic managed agent with gpt-4o-mini.
No external infrastructure needed. Runs the agent loop locally.
"""
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# Create a local managed agent (auto-detects local when no ANTHROPIC_API_KEY)
managed = ManagedAgent(
provider="local",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful assistant. Be concise.",
name="LocalAgent",
),
)
agent = Agent(name="local-basic", backend=managed)
# 1. Basic execution
print("[1] Basic execution...")
result = agent.start("What is the capital of France? One word.", stream=True)
print(f" Result: {result}")
# 2. Agent metadata
print(f"\n[2] Agent ID: {managed.agent_id}")
print(f" Version: {managed.agent_version}")
print(f" Env ID: {managed.environment_id}")
print(f" Session: {managed.session_id}")
# 3. Multi-turn (same session keeps context)
print("\n[3] Multi-turn...")
result = agent.start("What country is that city in?", stream=True)
print(f" Result: {result}")
# 4. Usage tracking
info = managed.retrieve_session()
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# 5. List sessions
sessions = managed.list_sessions()
print(f"\n[5] Sessions: {len(sessions)}")
for s in sessions:
print(f" {s['id']} | {s['status']}")
print("\nDone!")
@@ -1,66 +0,0 @@
"""Modal compute provider — run agent tools inside a Modal cloud sandbox.
Requires: modal CLI configured (modal token set) or MODAL_TOKEN_ID + MODAL_TOKEN_SECRET.
Install: pip install modal
"""
import asyncio
from praisonai import Agent, ManagedAgent, LocalManagedConfig
# ── 1. Create agent with Modal compute ──
managed = ManagedAgent(
provider="local",
compute="modal",
config=LocalManagedConfig(
model="gpt-4o-mini",
system="You are a helpful coding assistant. Be concise.",
name="ModalAgent",
),
)
agent = Agent(name="modal-agent", backend=managed)
print("[1] Agent created with Modal compute")
print(f" Compute: {managed.compute_provider.provider_name}")
# ── 2. Provision Modal sandbox ──
print("\n[2] Provisioning Modal sandbox...")
info = asyncio.run(managed.provision_compute(idle_timeout_s=120))
print(f" Instance: {info.instance_id}")
print(f" Status: {info.status}")
# ── 3. Execute commands in the sandbox ──
print("\n[3] Executing commands in Modal...")
result = asyncio.run(managed.execute_in_compute("python3 -c 'import sys; print(sys.version)'"))
print(f" Python version: {result['stdout'].strip()}")
print(f" Exit code: {result['exit_code']}")
result = asyncio.run(managed.execute_in_compute("echo 'Hello from Modal!'"))
print(f" Echo: {result['stdout'].strip()}")
# ── 4. Run a computation ──
print("\n[4] Running computation...")
result = asyncio.run(managed.execute_in_compute(
"python3 -c 'print(sum(range(1, 101)))'"
))
print(f" Sum 1..100 = {result['stdout'].strip()}")
# ── 5. Agent LLM execution ──
print("\n[5] Agent LLM execution...")
result = agent.start("What is 7 factorial? Just the number.", stream=True)
print(f" Result: {result}")
# ── 6. Multi-turn ──
print("\n[6] Multi-turn...")
result = agent.start("Is that number even or odd?", stream=True)
print(f" Result: {result}")
# ── 7. Usage ──
info = managed.retrieve_session()
print(f"\n[7] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# ── 8. Shutdown ──
print("\n[8] Shutting down Modal sandbox...")
asyncio.run(managed.shutdown_compute())
print(" Shutdown complete.")
print("\nDone!")
-180
View File
@@ -1,180 +0,0 @@
#!/usr/bin/env python3
"""
Managed Agent Backend Example — Real end-to-end test.
Demonstrates using Anthropic's Managed Agents API as an execution backend
for a PraisonAI Agent. The agent runs in Anthropic's managed infrastructure.
Prerequisites:
export ANTHROPIC_API_KEY="your-key"
pip install 'anthropic>=0.94.0' praisonaiagents praisonai
Usage:
python examples/python/managed_agent_example.py
"""
import os
import sys
import logging
# ── Setup logging so we can see what the integration does ──
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
# ── Guard: API key must be set ──
if not os.getenv("ANTHROPIC_API_KEY"):
print("ERROR: ANTHROPIC_API_KEY not set. Export it first.")
sys.exit(1)
def example_1_standalone():
"""Example 1: Use ManagedAgentIntegration directly (no PraisonAI Agent)."""
print("\n" + "=" * 60)
print("Example 1: Standalone ManagedAgentIntegration")
print("=" * 60)
from praisonai.integrations.managed_agents import ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
model="claude-sonnet-4-6",
name="Standalone Test Agent",
system="You are a concise assistant. Answer in one sentence.",
),
)
result = managed._execute_sync("What is 2 + 2? Answer in one sentence.")
print(f"\nResult: {result}")
print(f"Tokens: in={managed.total_input_tokens}, out={managed.total_output_tokens}")
assert result and len(result) > 0, "Expected non-empty response"
print("PASS: Standalone execution")
return result
def example_2_with_agent():
"""Example 2: Use ManagedAgentIntegration as a PraisonAI Agent backend."""
print("\n" + "=" * 60)
print("Example 2: PraisonAI Agent with managed backend")
print("=" * 60)
from praisonaiagents import Agent
from praisonai.integrations.managed_agents import ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
model="claude-sonnet-4-6",
name="PraisonAI Backend Agent",
system="You are a helpful coding assistant. Be concise.",
),
)
agent = Agent(
name="managed-coder",
instructions="You are a helpful coding assistant.",
backend=managed,
)
result = agent.start("Write a Python one-liner that prints the first 10 Fibonacci numbers.")
print(f"\nResult: {result}")
assert result and len(result) > 0, "Expected non-empty response from agent.start()"
print("PASS: Agent backend execution")
return result
def example_3_with_tools():
"""Example 3: Managed agent with built-in tools (bash, file ops)."""
print("\n" + "=" * 60)
print("Example 3: Managed agent with built-in tools")
print("=" * 60)
from praisonaiagents import Agent
from praisonai.integrations.managed_agents import ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
model="claude-sonnet-4-6",
name="Tool-Using Agent",
system="You are a coding agent with access to bash and file tools.",
tools=[{"type": "agent_toolset_20260401"}],
),
)
agent = Agent(
name="tool-agent",
instructions="You are a coding agent.",
backend=managed,
)
result = agent.start(
"Use bash to run: echo 'Hello from managed agent!' && python3 -c 'print(sum(range(10)))'"
)
print(f"\nResult: {result}")
assert result and len(result) > 0, "Expected non-empty response"
print("PASS: Tool-using agent execution")
return result
def example_4_with_packages():
"""Example 4: Managed agent with custom packages installed."""
print("\n" + "=" * 60)
print("Example 4: Managed agent with pip packages")
print("=" * 60)
from praisonaiagents import Agent
from praisonai.integrations.managed_agents import ManagedAgent, ManagedConfig
managed = ManagedAgent(
config=ManagedConfig(
model="claude-sonnet-4-6",
name="Data Agent",
system="You are a data analysis agent. Use pandas when helpful.",
packages={"pip": ["pandas", "numpy"]},
),
)
agent = Agent(
name="data-agent",
instructions="You are a data analysis agent.",
backend=managed,
)
result = agent.start(
"Use bash to run: python3 -c \"import pandas as pd; print(pd.__version__)\""
)
print(f"\nResult: {result}")
assert result and len(result) > 0, "Expected non-empty response"
print("PASS: Package installation agent")
return result
if __name__ == "__main__":
print("Managed Agent Backend — Real End-to-End Tests")
print("Using Anthropic Managed Agents API (beta)")
examples = [
("standalone", example_1_standalone),
("agent_backend", example_2_with_agent),
("with_tools", example_3_with_tools),
("with_packages", example_4_with_packages),
]
results = {}
for name, fn in examples:
try:
results[name] = fn()
except Exception as e:
print(f"FAIL: {name}: {e}")
results[name] = None
# Summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
passed = sum(1 for v in results.values() if v is not None)
total = len(results)
for name, result in results.items():
status = "PASS" if result else "FAIL"
print(f" {status}: {name}")
print(f"\n{passed}/{total} examples passed")
@@ -308,7 +308,7 @@ def manage_state_versioning() -> Dict[str, Any]:
versions[version_id] = {
"config": current_config.copy(),
"timestamp": datetime.now().isoformat(),
"hash": hashlib.sha256(json.dumps(current_config, sort_keys=True).encode()).hexdigest()
"hash": hashlib.md5(json.dumps(current_config, sort_keys=True).encode()).hexdigest()
}
# Make changes
+1 -4
View File
@@ -1,6 +1,5 @@
from praisonaiagents import Agent, Task, AgentTeam
import subprocess
import shlex
import os
def run_terminal_command(command: str):
@@ -8,9 +7,7 @@ def run_terminal_command(command: str):
Run a terminal command and return its output.
"""
try:
# Use shell=False with shlex.split for safer execution
args = shlex.split(command)
result = subprocess.run(args, shell=False, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(f"Command output: {result}")
return {"stdout": result.stdout, "stderr": result.stderr}
except subprocess.CalledProcessError as e:
+1 -4
View File
@@ -2,7 +2,6 @@ from praisonaiagents import Agent, Task, AgentTeam, error_logs
import json, os
from e2b_code_interpreter import Sandbox
import subprocess
import shlex
def code_interpret(code: str):
"""
@@ -41,9 +40,7 @@ def run_terminal_command(command: str):
Run a terminal command and return its output.
"""
try:
# Use shell=False with shlex.split for safer execution
args = shlex.split(command)
result = subprocess.run(args, shell=False, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(f"Command output: {result}")
return {"stdout": result.stdout, "stderr": result.stderr}
except subprocess.CalledProcessError as e:
-140
View File
@@ -1,140 +0,0 @@
# Terminal-Bench 2.0 Integration with PraisonAI
This directory contains examples for integrating PraisonAI Agents with **Terminal-Bench 2.0** via the **Harbor framework** for AI agent benchmarking.
## Overview
[Terminal-Bench 2.0](https://tbench.ai) is a Stanford/Laude Institute benchmark that has become the gold standard for evaluating AI coding agents in real terminal environments. The [Harbor framework](https://harborframework.com) is the official evaluation harness that abstracts container lifecycle, parallelization, and cloud providers.
## Integration Types
### 1. External Agent (Direct Agent Class)
- **File**: `praisonai_external_agent.py`
- **Purpose**: External agent that uses direct `Agent()` class instantiation
- **Usage**: Run with `--agent-import-path` flag
- **Approach**: Uses `praisonaiagents.Agent` directly with `execute_command` tool
### 2. Wrapper Agent (CLI-Based)
- **File**: `praisonai_wrapper_agent.py`
- **Purpose**: Uses `praisonai "TASK"` CLI pattern instead of direct Agent class
- **Usage**: Run with `--agent-import-path examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent`
- **Approach**: Installs `praisonai` CLI inside container and runs tasks via subprocess
- **Best for**: Users who prefer CLI-style interface matching standard `praisonai` usage
### 3. Installed Agent (Production)
- **File**: `praisonai_installed_agent.py`
- **Purpose**: Production-ready agent installed inside Harbor containers
- **Usage**: Integrates with Harbor's leaderboard system
## Quick Start
### Prerequisites
```bash
# Install Harbor framework
pip install harbor
# Install PraisonAI with shell tools
pip install praisonaiagents[tools]
```
### Running External Agent (Direct Agent Class)
```bash
# Test with oracle agent first
harbor run -d terminal-bench/terminal-bench-2 -a oracle
# Run PraisonAI external agent (uses direct Agent() class)
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent \
--model openai/gpt-4o \
--ae OPENAI_API_KEY=$OPENAI_API_KEY \
-n 4
```
### Running Wrapper Agent (CLI-Based)
```bash
# Run PraisonAI wrapper agent (uses `praisonai "TASK"` CLI pattern)
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent \
--model openai/gpt-4o \
--ae OPENAI_API_KEY=$OPENAI_API_KEY \
-n 4
```
### Running on Cloud (Daytona/E2B/Modal)
```bash
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent \
--model openai/gpt-4o \
--env daytona -n 32 \
--ae OPENAI_API_KEY=$OPENAI_API_KEY
```
## Key Features
- **Shell Execution Bridge**: Wraps Harbor's `BaseEnvironment.exec()` as PraisonAI tool
- **Auto-Approval**: Bypasses `@require_approval` for container-isolated execution
- **Token Tracking**: Populates Harbor's `AgentContext` with usage metrics
- **API Key Forwarding**: Supports environment variable injection
- **Multi-Agent Support**: Can run `AgentTeam` and `AgentFlow` workflows
## Architecture
```text
┌─────────────────────────────────────────┐
│ Harbor Framework │
│ (Terminal-Bench 2.0 Evaluation) │
├─────────────────────────────────────────┤
│ BaseEnvironment.exec() ←→ bash_tool │
├─────────────────────────────────────────┤
│ PraisonAI Agent │
│ • Uses execute_command tool │
│ • Auto-approval for container safety │
│ • Token tracking and metrics │
└─────────────────────────────────────────┘
```
## Files
- `README.md` - This documentation
- `praisonai_external_agent.py` - External agent (direct Agent class)
- `praisonai_wrapper_agent.py` - Wrapper agent (CLI-based approach)
- `praisonai_installed_agent.py` - Installed agent implementation
- `multi_agent_example.py` - Multi-agent team example
- `job.yaml` - Example Harbor job configuration
- `test_basic.py` - Basic smoke tests
- `test_integration.py` - Integration tests
- `test_real_agentic.py` - Real agentic tests
## Terminal-Bench 2.0 Tasks
The benchmark includes 89 carefully curated tasks covering:
- Compiling code
- Training models
- Setting up servers
- System administration
- File manipulation
- Package management
Each task provides:
- English instruction
- Docker container environment
- Test script (writes reward to `/logs/verifier/reward.txt`)
- Reference oracle solution
## Contributing
1. Test changes with oracle agent first: `harbor run -d terminal-bench/terminal-bench-2 -a oracle`
2. Run real agentic tests to ensure end-to-end functionality
3. Follow PraisonAI's AGENTS.md architecture guidelines
4. Add both unit tests and integration tests
## Resources
- [Terminal-Bench 2.0 Announcement](https://www.tbench.ai/news/announcement-2-0)
- [Harbor Framework Docs](https://www.harborframework.com/docs)
- [Terminal-Bench Leaderboard](https://tbench.ai/leaderboard)
- [Harbor GitHub Repo](https://github.com/laude-institute/harbor)
-83
View File
@@ -1,83 +0,0 @@
# Harbor Job Configuration for PraisonAI on Terminal-Bench 2.0
#
# Usage:
# harbor run -c examples/terminal_bench/job.yaml
#
# This configuration runs PraisonAI external agent on Terminal-Bench 2.0 tasks
# with 8 concurrent trials using GPT-4o.
# Dataset: Terminal-Bench 2.0 (89 carefully curated terminal tasks)
dataset: terminal-bench/terminal-bench-2
# Agent configuration
agent:
# Use import path for external agent
import_path: examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent
# Model configuration
model_name: openai/gpt-4o
# Environment variables (API keys)
env:
OPENAI_API_KEY: "${OPENAI_API_KEY}"
# Optional: Add other API keys if using different models
# ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
# GEMINI_API_KEY: "${GEMINI_API_KEY}"
# Execution configuration
n_concurrent: 8 # Run 8 trials in parallel
n_attempts: 1 # Single attempt per task (benchmark standard)
# Optional: Environment configuration for cloud execution
# environment:
# provider: daytona # Options: local, daytona, e2b, modal, runloop, gke
# n_concurrent: 32 # Higher concurrency on cloud
# Optional: Filter to specific tasks for testing
# task_filter:
# task_names: ["compile_simple_c", "install_python_package", "create_directory"]
# Optional: Advanced configuration
# timeout_sec: 600 # 10 minute timeout per task
# save_logs: true # Save execution logs
# save_artifacts: true # Save container artifacts
---
# Alternative configuration using wrapper agent (CLI-based approach)
# Uses `praisonai "TASK"` pattern instead of direct Agent class
# Uncomment to use the wrapper approach:
# dataset: terminal-bench/terminal-bench-2
#
# agent:
# import_path: examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent
# model_name: openai/gpt-4o
#
# n_concurrent: 8
# n_attempts: 1
#
# env:
# OPENAI_API_KEY: "${OPENAI_API_KEY}"
---
# Alternative configuration using installed agent (requires Harbor integration)
# Uncomment this section once PraisonAI is integrated into Harbor's codebase
# dataset: terminal-bench/terminal-bench-2
#
# agent:
# name: praisonai
# model_name: openai/gpt-4o
# flags:
# max_turns: 30
# verbose: false
# memory: false
# auto_approval: true
#
# n_concurrent: 8
# n_attempts: 1
#
# env:
# OPENAI_API_KEY: "${OPENAI_API_KEY}"
-22
View File
@@ -1,22 +0,0 @@
# Harbor Job Configuration for PraisonAI - 5 Task Test
# Run with: harbor run -c examples/terminal_bench/job_test_5.yaml -n 1
dataset: terminal-bench/terminal-bench-2
agent:
import_path: examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent
model_name: openai/gpt-4o-mini
env:
OPENAI_API_KEY: "${OPENAI_API_KEY}"
n_concurrent: 1
n_attempts: 1
# Filter to 5 specific simple tasks
task_filter:
task_names:
- "hello-world"
- "fix-permissions"
- "create-bucket"
- "csv-to-parquet"
- "simple-web-scraper"
@@ -1,22 +0,0 @@
# Harbor Job Configuration for PraisonAI Wrapper - 5 Task Test
# Run with: harbor run -c examples/terminal_bench/job_test_5_wrapper.yaml -n 1
dataset: terminal-bench/terminal-bench-2
agent:
import_path: examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent
model_name: openai/gpt-4o-mini
env:
OPENAI_API_KEY: "${OPENAI_API_KEY}"
n_concurrent: 1
n_attempts: 1
# Filter to 5 specific simple tasks
task_filter:
task_names:
- "hello-world"
- "fix-permissions"
- "create-bucket"
- "csv-to-parquet"
- "simple-web-scraper"
@@ -1,331 +0,0 @@
"""
Multi-Agent PraisonAI Integration for Terminal-Bench 2.0
This example shows how to use PraisonAI's AgentTeam and AgentFlow
capabilities with Terminal-Bench 2.0 for complex multi-step tasks.
The multi-agent approach is useful for Terminal-Bench tasks that benefit from:
- Task planning and decomposition
- Specialized roles (planner, executor, verifier)
- Error correction and refinement
- Complex workflows with multiple phases
Usage:
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.multi_agent_example:MultiAgentPraisonAI \
--model openai/gpt-4o
"""
import asyncio
from typing import Any, Dict, Optional
try:
from harbor.agents.base import BaseAgent
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
except ImportError as e:
raise ImportError(f"Harbor framework required: {e}") from e
try:
from praisonaiagents import Agent, AgentTeam, Task
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
except ImportError as e:
raise ImportError(f"PraisonAI required: {e}") from e
class MultiAgentPraisonAI(BaseAgent):
"""Multi-agent PraisonAI system for complex Terminal-Bench tasks."""
@staticmethod
def name() -> str:
return "praisonai-multi"
def version(self) -> str | None:
try:
import praisonaiagents
return getattr(praisonaiagents, "__version__", None)
except ImportError:
return None
async def setup(self, environment: BaseEnvironment) -> None:
"""Setup phase - no container setup needed for external agent."""
pass
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
"""
Run multi-agent PraisonAI team on the Terminal-Bench task.
Uses a three-agent approach:
1. Planner: Analyzes task and creates execution plan
2. Executor: Implements the plan using shell commands
3. Verifier: Tests and validates the solution
"""
# Set auto-approval for container safety
registry = get_approval_registry()
original_backend = registry.get_backend()
registry.set_backend(AutoApproveBackend(), agent_name="multi-agent-planner")
try:
# Create bash tool that wraps Harbor environment
async def bash_tool(command: str) -> str:
"""Execute bash command in Harbor environment."""
if not command.strip():
return "Error: Empty command"
try:
result = await environment.exec(command=command, timeout_sec=30)
output_parts = []
if result.stdout:
output_parts.append(result.stdout.strip())
if result.stderr:
output_parts.append(f"[stderr]: {result.stderr.strip()}")
if result.return_code != 0:
output_parts.append(f"[exit_code]: {result.return_code}")
return "\n".join(output_parts) if output_parts else "(no output)"
except Exception as e:
return f"Error: {str(e)}"
# Create specialized agents
planner = Agent(
name="planner",
instructions=(
"You are an expert task planner for terminal/coding tasks. "
"Analyze the given instruction and create a detailed, step-by-step plan. "
"Break down complex tasks into smaller, manageable steps. "
"Consider potential issues and edge cases. "
"Output your plan as a numbered list of specific actions."
),
llm=self.model_name or "openai/gpt-4o",
)
executor = Agent(
name="executor",
instructions=(
"You are an expert terminal executor. "
"Follow the provided plan step-by-step using bash commands. "
"Use the bash_tool to execute commands safely. "
"Be precise and check each step before proceeding. "
"If a step fails, try alternative approaches."
),
tools=[bash_tool],
llm=self.model_name or "openai/gpt-4o",
)
verifier = Agent(
name="verifier",
instructions=(
"You are a solution verifier and tester. "
"Test the completed solution to ensure it works correctly. "
"Run appropriate tests and checks using bash commands. "
"Report whether the solution meets the requirements. "
"If issues are found, suggest specific fixes."
),
tools=[bash_tool],
llm=self.model_name or "openai/gpt-4o",
)
print(f"🚀 Multi-Agent PraisonAI starting: {instruction[:100]}...")
# Phase 1: Planning
print("📋 Phase 1: Task Planning")
plan = await planner.astart(f"Create a detailed plan for: {instruction}")
print(f"Plan created: {len(plan.split('.')) if plan else 0} steps")
# Phase 2: Execution
print("⚡ Phase 2: Task Execution")
execution_prompt = f"Execute this plan step by step:\n\nOriginal task: {instruction}\n\nPlan:\n{plan}"
execution_result = await executor.astart(execution_prompt)
print("Execution completed")
# Phase 3: Verification
print("✅ Phase 3: Solution Verification")
verification_prompt = f"Verify this solution works correctly:\n\nOriginal task: {instruction}\n\nSolution: {execution_result}\n\nRun tests to confirm it works."
verification_result = await verifier.astart(verification_prompt)
print("Verification completed")
# Combine results
final_result = {
"plan": plan,
"execution": execution_result,
"verification": verification_result,
}
print("✅ Multi-Agent PraisonAI completed task")
# Populate context
self._populate_context([planner, executor, verifier], context, final_result)
except Exception as e:
print(f"❌ Multi-Agent PraisonAI failed: {str(e)}")
context.metadata = {"error": str(e)}
raise
finally:
# Restore original approval backend to avoid global state pollution
if original_backend:
registry.set_backend(original_backend)
else:
registry.remove_backend(agent_name="multi-agent-planner")
def _populate_context(self, agents: list, context: AgentContext, result: Dict[str, Any]) -> None:
"""Populate Harbor context with multi-agent metrics."""
try:
# Aggregate token usage from all agents
total_input_tokens = 0
total_output_tokens = 0
total_cost = 0.0
for agent in agents:
# Use agent's actual metrics properties
total_input_tokens += getattr(agent, '_total_tokens_in', 0)
total_output_tokens += getattr(agent, '_total_tokens_out', 0)
total_cost += agent.total_cost or 0.0
context.n_input_tokens = total_input_tokens if total_input_tokens > 0 else None
context.n_output_tokens = total_output_tokens if total_output_tokens > 0 else None
context.cost_usd = total_cost if total_cost > 0 else None
context.metadata = {
"framework": "praisonai-multi",
"agent_type": "multi-agent-team",
"agents": [agent.name for agent in agents],
"model": self.model_name or "openai/gpt-4o",
"phases": ["planning", "execution", "verification"],
"tools_used": ["bash_tool"],
"result_summary": str(result.get("verification", ""))[:200],
"version": self.version(),
}
except Exception as e:
context.metadata = {"context_error": str(e)}
# Alternative implementation using AgentTeam (more structured)
class AgentTeamPraisonAI(BaseAgent):
"""AgentTeam-based implementation for Terminal-Bench tasks."""
@staticmethod
def name() -> str:
return "praisonai-team"
def version(self) -> str | None:
try:
import praisonaiagents
return getattr(praisonaiagents, "__version__", None)
except ImportError:
return None
async def setup(self, environment: BaseEnvironment) -> None:
pass
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
"""Run structured AgentTeam workflow."""
registry = get_approval_registry()
original_backend = registry.get_backend()
registry.set_backend(AutoApproveBackend(), agent_name="agent-team")
try:
# Create bash tool
async def bash_tool(command: str) -> str:
result = await environment.exec(command=command, timeout_sec=30)
output_parts = []
if result.stdout:
output_parts.append(result.stdout.strip())
if result.stderr:
output_parts.append(f"[stderr]: {result.stderr.strip()}")
if result.return_code != 0:
output_parts.append(f"[exit_code]: {result.return_code}")
return "\n".join(output_parts) if output_parts else "(no output)"
# Create agents
planner = Agent(
name="planner",
instructions="Create detailed execution plans for terminal tasks",
llm=self.model_name or "openai/gpt-4o"
)
executor = Agent(
name="executor",
instructions="Execute terminal commands based on plans",
tools=[bash_tool],
llm=self.model_name or "openai/gpt-4o"
)
# Create tasks
plan_task = Task(
name="plan",
description=f"Create plan for: {instruction}",
agent=planner
)
execute_task = Task(
name="execute",
description=f"Execute plan for: {instruction}",
agent=executor,
dependencies=[plan_task] # Execute after planning
)
# Create and run team
team = AgentTeam(
agents=[planner, executor],
tasks=[plan_task, execute_task],
process="sequential"
)
print(f"🚀 AgentTeam starting: {instruction[:100]}...")
result = await team.astart(instruction)
print("✅ AgentTeam completed")
# Populate context
context.metadata = {
"framework": "praisonai-team",
"workflow": "sequential",
"agents_count": len(team.agents),
"tasks_count": len(team.tasks),
"result": str(result)[:200] if result else None,
}
finally:
# Restore original approval backend to avoid global state pollution
if original_backend:
registry.set_backend(original_backend)
else:
registry.remove_backend(agent_name="agent-team")
if __name__ == "__main__":
print("Multi-Agent PraisonAI for Terminal-Bench 2.0")
print("=" * 50)
print()
print("Available agent implementations:")
print("1. MultiAgentPraisonAI - Custom multi-agent workflow")
print("2. AgentTeamPraisonAI - Structured AgentTeam workflow")
print()
print("Usage examples:")
print()
print("# Multi-agent custom workflow")
print("harbor run -d terminal-bench/terminal-bench-2 \\")
print(" --agent-import-path examples.terminal_bench.multi_agent_example:MultiAgentPraisonAI \\")
print(" --model openai/gpt-4o")
print()
print("# AgentTeam structured workflow")
print("harbor run -d terminal-bench/terminal-bench-2 \\")
print(" --agent-import-path examples.terminal_bench.multi_agent_example:AgentTeamPraisonAI \\")
print(" --model openai/gpt-4o")
print()
print("Benefits of multi-agent approach:")
print("- Task decomposition and planning")
print("- Specialized roles and expertise")
print("- Error detection and correction")
print("- Higher success rates on complex tasks")
@@ -1,222 +0,0 @@
"""
PraisonAI External Agent for Terminal-Bench 2.0 (Harbor)
An external agent that bridges PraisonAI's Agent class with Harbor's
BaseEnvironment interface. The agent uses Harbor's exec() as a bash tool.
Usage:
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent \
--model openai/gpt-4o \
--ae OPENAI_API_KEY=$OPENAI_API_KEY
Architecture:
Harbor Container ←→ BaseEnvironment.exec() ←→ bash_tool ←→ PraisonAI Agent
Dependencies:
pip install harbor praisonaiagents
"""
import os
from typing import Any
try:
from harbor.agents.base import BaseAgent
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
except ImportError as e:
raise ImportError(
f"Harbor framework not installed: {e}\n"
"Install with: pip install harbor"
) from e
try:
from praisonaiagents import Agent
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
except ImportError as e:
raise ImportError(
f"PraisonAI not installed: {e}\n"
"Install with: pip install praisonaiagents"
) from e
class PraisonAIExternalAgent(BaseAgent):
"""External PraisonAI agent that drives a Harbor container environment."""
@staticmethod
def name() -> str:
return "praisonai"
def version(self) -> str | None:
try:
import praisonaiagents
return (
getattr(praisonaiagents, "__version__", None)
or getattr(praisonaiagents, "version", None)
or "unknown"
)
except ImportError:
return None
async def setup(self, environment: BaseEnvironment) -> None:
"""Setup phase - external agent needs no container setup."""
pass
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
"""
Run the PraisonAI agent on the given instruction using Harbor's environment.
This method bridges Harbor's BaseEnvironment.exec() to PraisonAI's tool system.
"""
# Inject API keys from Harbor's --ae env vars into host os.environ
# so litellm can pick them up (--ae only sets them inside Docker, not the host)
agent_env = getattr(context, 'env', {}) or {}
for key, val in agent_env.items():
if key not in os.environ and val:
os.environ[key] = val
print(f"[ENV] Set {key} from Harbor agent env")
# Set auto-approval for container-isolated execution
# Harbor's container provides isolation, so we can safely auto-approve shell commands
registry = get_approval_registry()
original_backend = registry.get_backend()
registry.set_backend(AutoApproveBackend(), agent_name="terminal-agent")
try:
# Create bash tool that wraps Harbor's environment.exec()
async def bash_tool(command: str) -> str:
"""Execute a bash command in the Harbor sandboxed environment."""
if not command.strip():
return "Error: Empty command provided"
print(f"[CMD] {command[:200]}")
try:
# Execute command in Harbor's container
result = await environment.exec(command=command, timeout_sec=300)
# Format output similar to PraisonAI's execute_command tool
output_parts = []
if result.stdout:
output_parts.append(result.stdout.strip())
if result.stderr:
output_parts.append(f"[stderr]: {result.stderr.strip()}")
if result.return_code != 0:
output_parts.append(f"[exit_code]: {result.return_code}")
output = "\n".join(output_parts) if output_parts else "(no output)"
print(f"[OUT] {output[:300]}")
return output
except Exception as e:
print(f"[ERR] {str(e)}")
return f"Error executing command: {str(e)}"
# Create PraisonAI agent with the bash tool
agent = Agent(
name="terminal-agent",
instructions=(
"You are an expert terminal agent. You solve coding, debugging, data analysis, security, "
"and system administration tasks using the bash_tool to run shell commands."
"\n\nCRITICAL RULES:"
"\n1. START by exploring: ls /app/ && cat /app/*.py /app/*.sh /app/*.txt 2>/dev/null | head -200"
" Read ALL task files to understand what is needed before doing anything."
"\n2. Find and run the test IMMEDIATELY to see what is expected:"
" find /app -name 'test_*.py' | head -3 && cd /app && python3 -m pytest -v 2>&1 | tail -40 || true"
" The test error trace tells you EXACTLY what output/behavior is required."
"\n3. ITERATE: implement a solution, run the test, read the error, refine. Repeat until it passes."
" Never give up after one attempt — try multiple approaches if needed."
"\n4. For writing files with special characters use Python:"
" python3 -c \"with open('/app/file','w') as f: f.write('content')\""
" or heredoc: cat > /app/file << 'EOF'\\n...content...\\nEOF"
"\n5. Read ALL error messages — they tell you exactly what to fix next."
"\n6. NEVER just describe your plan. ALWAYS immediately run bash_tool commands."
" Keep calling bash_tool until the test PASSES or you have exhausted all approaches."
),
tools=[bash_tool],
llm=self.model_name or "openai/gpt-4o",
)
# Execute the agent with outer loop to handle premature stopping
print(f"🚀 PraisonAI Agent starting task: {instruction[:100]}...")
result = await agent.achat(instruction)
for _iter in range(19):
result_str = str(result)
# Stop if test passed
if any(sig in result_str.lower() for sig in [
" passed", "passed ", "test passed", "all tests pass", "1 passed"
]):
break
result = await agent.achat(
"The task is NOT complete yet — keep working. Run bash_tool commands now. "
"If you haven't already: (1) read all files in /app/, "
"(2) run the test to see the exact failure, "
"(3) implement a fix, (4) run the test again. "
"Repeat until the test passes. What is your next bash_tool command?"
)
print(f"✅ PraisonAI Agent completed task")
# Populate Harbor context with metadata
self._populate_context(agent, context, result)
except Exception as e:
print(f"❌ PraisonAI Agent failed: {str(e)}")
context.metadata = {"error": str(e)}
raise
finally:
# Restore original approval backend to avoid global state pollution
if original_backend:
registry.set_backend(original_backend)
else:
registry.remove_backend(agent_name="terminal-agent")
def _populate_context(self, agent: Agent, context: AgentContext, result: Any) -> None:
"""
Populate Harbor's AgentContext with metrics from PraisonAI agent execution.
Harbor tracks: n_input_tokens, n_output_tokens, cost_usd, metadata
"""
try:
# Extract token usage and cost from agent
try:
summary = agent.cost_summary() if callable(getattr(agent, 'cost_summary', None)) else None
if isinstance(summary, dict):
context.n_input_tokens = summary.get('tokens_in')
context.n_output_tokens = summary.get('tokens_out')
context.cost_usd = summary.get('cost')
else:
context.n_input_tokens = getattr(agent, '_total_tokens_in', 0)
context.n_output_tokens = getattr(agent, '_total_tokens_out', 0)
context.cost_usd = getattr(agent, 'total_cost', None)
except Exception:
pass
# Store result summary and agent info
context.metadata = {
"agent_name": agent.name,
"model": getattr(agent, 'llm', 'unknown'),
"final_response": str(result)[:500] if result else None,
"tools_used": ["bash_tool"],
"framework": "praisonai",
"version": self.version(),
}
except Exception as e:
# Don't fail the whole run if context population fails
context.metadata = {"context_error": str(e)}
# Example usage for testing
if __name__ == "__main__":
print("PraisonAI External Agent for Terminal-Bench 2.0")
print("Usage: harbor run -d terminal-bench/terminal-bench-2 \\")
print(" --agent-import-path examples.terminal_bench.praisonai_external_agent:PraisonAIExternalAgent \\")
print(" --model openai/gpt-4o")
print()
print("Dependencies:")
print(" pip install harbor praisonaiagents")
@@ -1,340 +0,0 @@
"""
PraisonAI Installed Agent for Terminal-Bench 2.0 (Harbor)
Production-ready agent that installs PraisonAI inside Harbor's container
and runs it headlessly. This follows the same pattern as other agents
in Harbor (codex, gemini-cli, claude-code).
Usage:
# First, this would need to be integrated into Harbor's codebase as:
# src/harbor/agents/installed/praisonai.py
harbor run -d terminal-bench/terminal-bench-2 -a praisonai --model openai/gpt-4o
Architecture:
Harbor Container → praisonaiagents installed inside → execute_command tool
For now, this serves as a reference implementation that could be contributed
to the Harbor project via PR.
"""
import os
import shlex
import json
import asyncio
from pathlib import Path
from typing import Dict, Optional
try:
# These imports would work if this file was in Harbor's codebase
from harbor.agents.installed.base import BaseInstalledAgent, with_prompt_template, CliFlag
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
from harbor.models.trial.paths import EnvironmentPaths
except ImportError:
# Fallback for development/testing outside Harbor
print("Note: This is a reference implementation for Harbor integration")
print("To actually use this, it needs to be integrated into Harbor's codebase")
# Mock classes for development
class BaseInstalledAgent:
pass
def with_prompt_template(func):
return func
class CliFlag:
def __init__(self, name, cli, type, default):
pass
class BaseEnvironment:
async def exec(self, **kwargs):
pass
class AgentContext:
pass
class PraisonAIInstalledAgent(BaseInstalledAgent):
"""
PraisonAI agent installed inside Harbor's container environment.
This is a production-ready integration that follows Harbor's established
patterns for installed agents like Codex CLI, Gemini CLI, etc.
"""
# ATIF trajectory support - would be implemented once PraisonAI has trajectory format
SUPPORTS_ATIF: bool = False
# Command-line configuration flags
CLI_FLAGS = [
CliFlag("max_turns", cli="--max-turns", type="int", default=30),
CliFlag("verbose", cli="--verbose", type="bool", default=False),
CliFlag("memory", cli="--memory", type="bool", default=False),
CliFlag("auto_approval", cli="--auto-approval", type="bool", default=True),
]
@staticmethod
def name() -> str:
return "praisonai"
def get_version_command(self) -> str | None:
"""Command to get PraisonAI version inside the container."""
return "python3 -c 'import praisonaiagents; print(praisonaiagents.__version__)'"
async def install(self, environment: BaseEnvironment) -> None:
"""
Install PraisonAI and dependencies inside the Harbor container.
This runs as root first for system packages, then as agent user
for Python packages.
"""
# System packages (as root)
await self.exec_as_root(
environment,
command="apt-get update && apt-get install -y python3 python3-pip curl git",
env={"DEBIAN_FRONTEND": "noninteractive"},
)
# Install PraisonAI (as agent user)
version_spec = f"=={self._version}" if self._version else ""
install_cmd = f"pip install praisonaiagents{version_spec} --quiet"
await self.exec_as_agent(
environment,
command=(
f"{install_cmd} && "
"python3 -c 'import praisonaiagents; print(\"✅ praisonaiagents installed:\", praisonaiagents.__version__)'"
),
)
# Create headless runner script
runner_script = self._build_runner_script()
await self.exec_as_agent(
environment,
command=f"cat > /tmp/praisonai_runner.py << 'SCRIPT_EOF'\n{runner_script}\nSCRIPT_EOF",
)
print("✅ PraisonAI installation completed")
def _build_runner_script(self) -> str:
"""
Build the Python runner script that will execute inside the container.
This script handles:
- Setting up auto-approval for container safety
- Creating agent with shell tools
- Executing the instruction
- Outputting results in JSON format
"""
return '''#!/usr/bin/env python3
"""
PraisonAI Headless Runner for Harbor Terminal-Bench 2.0
"""
import sys
import json
import os
from typing import Dict, Any
def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No instruction provided"}))
sys.exit(1)
instruction = sys.argv[1]
model = sys.argv[2] if len(sys.argv) > 2 else "openai/gpt-4o"
max_turns = int(sys.argv[3]) if len(sys.argv) > 3 else 30
try:
from praisonaiagents import Agent
from praisonaiagents.tools import execute_command
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
# Set auto-approval for container-isolated execution
get_approval_registry().set_backend(AutoApproveBackend())
# Create terminal agent with shell execution capabilities
agent = Agent(
name="terminal-agent",
instructions=(
"You are an expert terminal agent working on coding and system administration tasks. "
"Use the execute_command tool to run shell commands safely. "
"Be precise, verify your work, and complete the task step by step. "
"Always test your solution to ensure it works correctly."
),
tools=[execute_command],
llm=model,
)
# Execute the instruction
result = agent.start(instruction)
# Extract metrics
metrics = {
"result": str(result) if result else None,
"agent_name": agent.name,
"model": model,
"tools_used": ["execute_command"],
}
# Add token usage if available
if hasattr(agent, '_usage'):
usage = agent._usage
if usage:
metrics.update({
"input_tokens": getattr(usage, 'input_tokens', None),
"output_tokens": getattr(usage, 'output_tokens', None),
})
# Add cost if available
if hasattr(agent, '_cost'):
metrics["cost_usd"] = agent._cost
print(json.dumps(metrics))
except Exception as e:
error_result = {
"error": str(e),
"error_type": type(e).__name__,
}
print(json.dumps(error_result))
sys.exit(1)
if __name__ == "__main__":
main()
'''
@with_prompt_template
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
"""
Run the PraisonAI agent on the given instruction.
This executes the headless runner script with the instruction,
similar to how other installed agents work in Harbor.
"""
model = self.model_name or "openai/gpt-4o"
max_turns = getattr(self, 'max_turns', 30)
# Build command to run PraisonAI
cmd_args = [
"python3", "/tmp/praisonai_runner.py",
shlex.quote(instruction),
shlex.quote(model),
str(max_turns),
]
command = " ".join(cmd_args)
try:
# Execute the agent
await self.exec_as_agent(
environment,
command=command,
env=self._get_environment_vars(),
)
except Exception as e:
# Store error in context for Harbor's reporting
context.metadata = {"execution_error": str(e)}
raise
def _get_environment_vars(self) -> Dict[str, str]:
"""Get environment variables to pass to the agent execution."""
env_vars = {}
# Common API keys that should be forwarded
api_keys = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_API_KEY",
"AZURE_OPENAI_API_KEY",
"GROQ_API_KEY",
]
for key in api_keys:
value = os.environ.get(key)
if value:
env_vars[key] = value
return env_vars
def populate_context_post_run(self, context: AgentContext) -> None:
"""
Parse the agent's output and populate Harbor's AgentContext.
This extracts token counts, costs, and other metrics from the
JSON output of the headless runner script.
"""
try:
# Parse the last stdout output for JSON metrics
# In Harbor's model, the last execution output should contain our JSON
last_output = getattr(context, '_last_stdout', None)
if last_output:
try:
metrics = json.loads(last_output.strip())
# Extract metrics with safe defaults
context.n_input_tokens = metrics.get('input_tokens')
context.n_output_tokens = metrics.get('output_tokens')
context.cost_usd = metrics.get('cost_usd')
# Store additional metadata
context.metadata = {
"framework": "praisonai",
"agent_type": "installed",
"agent_name": metrics.get('agent_name', 'terminal-agent'),
"model": metrics.get('model'),
"tools_used": metrics.get('tools_used', []),
"version": self.get_version() if hasattr(self, 'get_version') else None,
}
except (json.JSONDecodeError, ValueError) as e:
# If JSON parsing fails, store basic metadata
context.metadata = {
"framework": "praisonai",
"agent_type": "installed",
"parse_error": str(e),
"raw_output": str(last_output)[:200] if last_output else None,
}
else:
# No output to parse
context.metadata = {
"framework": "praisonai",
"agent_type": "installed",
"status": "no_output",
}
except Exception as e:
context.metadata = {"context_population_error": str(e)}
# Factory function for Harbor integration
def create_praisonai_agent(**kwargs) -> PraisonAIInstalledAgent:
"""Factory function to create PraisonAI installed agent."""
return PraisonAIInstalledAgent(**kwargs)
# Example usage and testing
if __name__ == "__main__":
print("PraisonAI Installed Agent for Terminal-Bench 2.0")
print()
print("This is a reference implementation for Harbor integration.")
print("To use this in production:")
print()
print("1. Add this file to Harbor's codebase as:")
print(" src/harbor/agents/installed/praisonai.py")
print()
print("2. Register in Harbor's agent factory:")
print(" - Add 'PRAISONAI = \"praisonai\"' to AgentName enum")
print(" - Add PraisonAI to _AGENTS list and _AGENT_MAP")
print()
print("3. Run with Harbor:")
print(" harbor run -d terminal-bench/terminal-bench-2 -a praisonai --model openai/gpt-4o")
print()
print("Dependencies:")
print(" pip install harbor praisonaiagents")
@@ -1,233 +0,0 @@
"""
PraisonAI Wrapper Agent for Terminal-Bench 2.0 (Harbor)
An external agent that uses the PraisonAI CLI wrapper instead of
direct Agent class instantiation. This provides a higher-level interface
that matches the standard `praisonai "TASK"` CLI usage pattern.
Usage:
harbor run -d terminal-bench/terminal-bench-2 \
--agent-import-path examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent \
--model openai/gpt-4o \
--ae OPENAI_API_KEY=$OPENAI_API_KEY \
-n 4
Architecture:
Harbor Container ←→ BaseEnvironment.exec() ←→ praisonai CLI subprocess
Dependencies:
pip install harbor praisonai
"""
import asyncio
import shlex
import json
import os
from typing import Any, Dict, Optional
# These imports require Harbor to be installed
try:
from harbor.agents.base import BaseAgent
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
except ImportError as e:
raise ImportError(
f"Harbor framework not installed: {e}\n"
"Install with: pip install harbor"
) from e
class PraisonAIWrapperAgent(BaseAgent):
"""
PraisonAI wrapper agent that uses the CLI/subprocess approach.
This agent installs PraisonAI inside the Harbor container and runs
tasks using the `praisonai "TASK"` pattern, matching standard CLI usage.
"""
@staticmethod
def name() -> str:
return "praisonai-wrapper"
def version(self) -> str | None:
"""Get PraisonAI version from container."""
try:
import praisonai
return getattr(praisonai, "__version__", None)
except ImportError:
return None
async def setup(self, environment: BaseEnvironment) -> None:
"""
Setup phase - install PraisonAI wrapper inside the container.
"""
print("🔧 Installing PraisonAI wrapper inside container...")
# Install system dependencies
await self._exec_as_root(
environment,
command="apt-get update && apt-get install -y python3 python3-pip curl git",
env={"DEBIAN_FRONTEND": "noninteractive"},
)
# Install PraisonAI wrapper
version_spec = f"=={self._version}" if getattr(self, '_version', None) else ""
install_cmd = f"pip install praisonai{version_spec} --quiet"
await self._exec_as_agent(
environment,
command=(
f"{install_cmd} && "
"python3 -c 'import praisonai; print(\"✅ praisonai installed:\", praisonai.__version__)'"
),
)
print("✅ PraisonAI wrapper setup completed")
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
"""
Run the PraisonAI wrapper agent on the given instruction.
Uses the `praisonai "TASK"` CLI pattern inside the Harbor container.
"""
model = self.model_name or "openai/gpt-4o"
# Build the praisonai CLI command
# Format: praisonai "TASK" --model MODEL
cmd_parts = [
"praisonai",
shlex.quote(instruction),
"--model", shlex.quote(model),
]
# Add API key to environment
env_vars = self._get_environment_vars()
command = " ".join(cmd_parts)
try:
print(f"🚀 PraisonAI wrapper starting task: {instruction[:100]}...")
# Execute praisonai CLI inside the container
result = await environment.exec(
command=command,
timeout_sec=600, # 10 minute timeout for complex tasks
env=env_vars,
)
print(f"✅ PraisonAI wrapper completed task")
# Populate Harbor context with results
self._populate_context(result, context, instruction)
except Exception as e:
print(f"❌ PraisonAI wrapper failed: {str(e)}")
context.metadata = {"error": str(e)}
raise
def _populate_context(
self,
result: Any,
context: AgentContext,
instruction: str
) -> None:
"""
Populate Harbor's AgentContext with execution results.
"""
try:
# Extract output from result
stdout = getattr(result, 'stdout', '') or ''
stderr = getattr(result, 'stderr', '') or ''
return_code = getattr(result, 'return_code', 0)
# Store result summary
context.metadata = {
"agent_name": "praisonai-wrapper",
"model": self.model_name or "openai/gpt-4o",
"framework": "praisonai",
"wrapper_type": "cli",
"instruction_preview": instruction[:200],
"return_code": return_code,
"stderr_preview": stderr[:500] if stderr else None,
}
# Try to parse any JSON output for metrics
try:
# Look for JSON in the output
lines = stdout.strip().split('\n')
for line in reversed(lines):
if line.strip().startswith('{'):
metrics = json.loads(line.strip())
if isinstance(metrics, dict):
context.n_input_tokens = metrics.get('input_tokens')
context.n_output_tokens = metrics.get('output_tokens')
context.cost_usd = metrics.get('cost_usd')
context.metadata['parsed_metrics'] = metrics
break
except (json.JSONDecodeError, ValueError):
pass # No JSON metrics found
# Store final response (last non-empty line or full stdout)
final_lines = [l for l in stdout.split('\n') if l.strip()]
context.metadata['final_response'] = final_lines[-1][:500] if final_lines else stdout[:500]
except Exception as e:
context.metadata = {"context_error": str(e)}
def _get_environment_vars(self) -> Dict[str, str]:
"""Get environment variables to pass to the agent execution."""
env_vars = {}
# Forward API keys
api_keys = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_API_KEY",
"AZURE_OPENAI_API_KEY",
"GROQ_API_KEY",
]
for key in api_keys:
value = os.environ.get(key)
if value:
env_vars[key] = value
# Set LOGLEVEL to reduce noise
env_vars["LOGLEVEL"] = "WARNING"
return env_vars
async def _exec_as_root(self, environment: BaseEnvironment, command: str, env: Optional[Dict] = None) -> Any:
"""Execute command as root in the container."""
if hasattr(environment, 'exec_as_root'):
return await environment.exec_as_root(command=command, env=env or {})
else:
# Fallback: use sudo
return await environment.exec(command=f"sudo {command}", env=env or {})
async def _exec_as_agent(self, environment: BaseEnvironment, command: str) -> Any:
"""Execute command as the agent user in the container."""
return await environment.exec(command=command)
# Example usage for testing
if __name__ == "__main__":
print("PraisonAI Wrapper Agent for Terminal-Bench 2.0")
print("=" * 50)
print()
print("This agent uses the PraisonAI CLI wrapper approach:")
print(' praisonai "TASK" --model MODEL')
print()
print("Usage with Harbor:")
print(" harbor run -d terminal-bench/terminal-bench-2 \\")
print(" --agent-import-path examples.terminal_bench.praisonai_wrapper_agent:PraisonAIWrapperAgent \\")
print(" --model openai/gpt-4o")
print()
print("Dependencies:")
print(" pip install harbor praisonai")
@@ -1,347 +0,0 @@
#!/usr/bin/env python3
"""
Agent Comparison Test - Run 5 tasks with both wrapper and direct agent
This test compares:
1. Direct Agent approach (praisonai_external_agent pattern)
2. CLI Wrapper approach (praisonai_wrapper_agent pattern)
Usage:
python test_agent_comparison.py
"""
import sys
import os
import time
import asyncio
from datetime import datetime
# Add paths
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'praisonai-agents'))
# 5 Test Tasks (simple to moderately complex)
TEST_TASKS = [
{
"name": "list_directory",
"instruction": "List all files in the current directory and count how many Python files exist",
"expected_artifacts": ["file count", ".py count"]
},
{
"name": "create_structure",
"instruction": "Create a directory called 'test_workspace', create a file inside it named 'readme.txt' with content 'Hello World', then verify it exists",
"expected_artifacts": ["test_workspace/", "readme.txt"]
},
{
"name": "file_operations",
"instruction": "Create a file called 'numbers.txt' with numbers 1-10 each on a new line, then display the first 3 lines",
"expected_artifacts": ["numbers.txt"]
},
{
"name": "system_info",
"instruction": "Show the current working directory, current user, and Python version",
"expected_artifacts": ["pwd output", "whoami output"]
},
{
"name": "text_processing",
"instruction": "Create a file 'words.txt' with 5 words (apple, banana, cherry, date, elderberry), then count the total characters in the file",
"expected_artifacts": ["words.txt", "character count"]
}
]
class DirectAgentRunner:
"""Runs tasks using direct Agent class approach."""
def __init__(self):
from praisonaiagents import Agent
from praisonaiagents.tools import execute_command
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
self.Agent = Agent
self.execute_command = execute_command
self.registry = get_approval_registry()
self.registry.set_backend(AutoApproveBackend())
self.results = []
def run_task(self, task: dict) -> dict:
"""Run a single task and return metrics."""
print(f"\n🎯 Direct Agent - Running: {task['name']}")
print(f" Instruction: {task['instruction'][:60]}...")
start_time = time.time()
try:
agent = self.Agent(
name=f"direct-{task['name']}",
instructions="You are a terminal task agent. Use execute_command tool to complete tasks.",
tools=[self.execute_command],
llm="gpt-4o-mini"
)
result = agent.start(task['instruction'])
elapsed = time.time() - start_time
# Get cost if available
cost = getattr(agent, 'total_cost', 0) or 0
print(f" ✅ Completed in {elapsed:.2f}s")
print(f" 💰 Cost: ${cost:.6f}")
return {
"task": task['name'],
"success": True,
"time": elapsed,
"cost": cost,
"result_preview": str(result)[:100] if result else "No output",
"error": None
}
except Exception as e:
elapsed = time.time() - start_time
print(f" ❌ Failed in {elapsed:.2f}s: {e}")
return {
"task": task['name'],
"success": False,
"time": elapsed,
"cost": 0,
"result_preview": None,
"error": str(e)
}
class WrapperAgentRunner:
"""Runs tasks using CLI wrapper approach via subprocess."""
def __init__(self):
self.results = []
def run_task(self, task: dict) -> dict:
"""Run a single task using praisonai CLI via subprocess."""
print(f"\n🎯 Wrapper Agent - Running: {task['name']}")
print(f" Instruction: {task['instruction'][:60]}...")
start_time = time.time()
try:
import subprocess
import shlex
# Build the praisonai CLI command
# Format: praisonai "TASK" --model MODEL
cmd = [
"praisonai",
task['instruction'],
"--model", "gpt-4o-mini"
]
# Run the command
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300, # 5 minute timeout
env={**os.environ, "LOGLEVEL": "WARNING"}
)
elapsed = time.time() - start_time
if result.returncode == 0:
print(f" ✅ Completed in {elapsed:.2f}s")
return {
"task": task['name'],
"success": True,
"time": elapsed,
"cost": 0, # Cost tracking not available in CLI mode
"result_preview": result.stdout[:100] if result.stdout else "No output",
"error": None
}
else:
print(f" ❌ Failed with exit code {result.returncode} in {elapsed:.2f}s")
return {
"task": task['name'],
"success": False,
"time": elapsed,
"cost": 0,
"result_preview": None,
"error": f"Exit code {result.returncode}: {result.stderr[:200]}"
}
except subprocess.TimeoutExpired:
elapsed = time.time() - start_time
print(f" ⏱️ Timeout after {elapsed:.2f}s")
return {
"task": task['name'],
"success": False,
"time": elapsed,
"cost": 0,
"result_preview": None,
"error": "Timeout after 300s"
}
except FileNotFoundError:
elapsed = time.time() - start_time
print(f" ⚠️ praisonai CLI not found")
return {
"task": task['name'],
"success": False,
"time": elapsed,
"cost": 0,
"result_preview": None,
"error": "praisonai CLI not installed (pip install praisonai)"
}
except Exception as e:
elapsed = time.time() - start_time
print(f" ❌ Failed in {elapsed:.2f}s: {e}")
return {
"task": task['name'],
"success": False,
"time": elapsed,
"cost": 0,
"result_preview": None,
"error": str(e)
}
def compare_results(direct_results: list, wrapper_results: list):
"""Compare results from both approaches."""
print("\n" + "=" * 70)
print("📊 COMPARISON RESULTS")
print("=" * 70)
# Calculate metrics
direct_success = sum(1 for r in direct_results if r['success'])
wrapper_success = sum(1 for r in wrapper_results if r['success'])
direct_total_time = sum(r['time'] for r in direct_results)
wrapper_total_time = sum(r['time'] for r in wrapper_results)
direct_total_cost = sum(r['cost'] for r in direct_results)
print(f"\n✅ Success Rate:")
print(f" Direct Agent: {direct_success}/5 ({direct_success*20}%)")
print(f" Wrapper Agent: {wrapper_success}/5 ({wrapper_success*20}%)")
print(f"\n⏱️ Total Time:")
print(f" Direct Agent: {direct_total_time:.2f}s (avg: {direct_total_time/5:.2f}s per task)")
print(f" Wrapper Agent: {wrapper_total_time:.2f}s (avg: {wrapper_total_time/5:.2f}s per task)")
print(f"\n💰 Total Cost (Direct Agent only):")
print(f" ${direct_total_cost:.6f} (avg: ${direct_total_cost/5:.6f} per task)")
print(f"\n📋 Detailed Results:")
print(f" {'Task':<20} {'Direct':<12} {'Wrapper':<12} {'Winner':<10}")
print(f" {'-'*56}")
for d, w in zip(direct_results, wrapper_results):
d_status = "✅" if d['success'] else "❌"
w_status = "✅" if w['success'] else "❌"
if d['success'] and w['success']:
winner = "Direct" if d['time'] < w['time'] else "Wrapper"
elif d['success']:
winner = "Direct"
elif w['success']:
winner = "Wrapper"
else:
winner = "None"
print(f" {d['task']:<20} {d_status} {d['time']:>6.2f}s {w_status} {w['time']:>6.2f}s {winner}")
# Overall winner
print(f"\n🏆 OVERALL WINNER:")
if direct_success > wrapper_success:
print(f" 🥇 Direct Agent (higher success rate)")
elif wrapper_success > direct_success:
print(f" 🥇 Wrapper Agent (higher success rate)")
elif direct_success == wrapper_success == 5:
if direct_total_time < wrapper_total_time:
print(f" 🥇 Direct Agent (faster, same success rate)")
else:
print(f" 🥇 Wrapper Agent (faster, same success rate)")
else:
print(f" ⚠️ Mixed results - both had failures")
print("\n" + "=" * 70)
def main():
print("=" * 70)
print("🔬 AGENT COMPARISON TEST - 5 Tasks")
print("=" * 70)
print(f"\nTesting {len(TEST_TASKS)} tasks with both approaches...")
print("Model: gpt-4o-mini")
print(f"Start time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# Clean up any previous test artifacts
print("\n🧹 Cleaning up previous test artifacts...")
cleanup_commands = [
"rm -rf test_workspace numbers.txt words.txt readme.txt 2>/dev/null; true"
]
for cmd in cleanup_commands:
os.system(cmd)
# Run with Direct Agent
print("\n" + "=" * 70)
print("🤖 RUNNING WITH DIRECT AGENT (Agent class)")
print("=" * 70)
direct_runner = DirectAgentRunner()
direct_results = []
for task in TEST_TASKS:
result = direct_runner.run_task(task)
direct_results.append(result)
# Clean up between runs
print("\n🧹 Cleaning up before wrapper test...")
for cmd in cleanup_commands:
os.system(cmd)
# Run with Wrapper Agent
print("\n" + "=" * 70)
print("📦 RUNNING WITH WRAPPER AGENT (CLI approach)")
print("=" * 70)
wrapper_runner = WrapperAgentRunner()
wrapper_results = []
for task in TEST_TASKS:
result = wrapper_runner.run_task(task)
wrapper_results.append(result)
# Compare results
compare_results(direct_results, wrapper_results)
# Final cleanup
print("\n🧹 Final cleanup...")
for cmd in cleanup_commands:
os.system(cmd)
print(f"\n✅ Comparison complete! End time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
return direct_results, wrapper_results
if __name__ == "__main__":
try:
direct_results, wrapper_results = main()
# Exit with error if both failed completely
direct_success = sum(1 for r in direct_results if r['success'])
wrapper_success = sum(1 for r in wrapper_results if r['success'])
if direct_success == 0 and wrapper_success == 0:
print("\n❌ Both approaches failed completely!")
sys.exit(1)
else:
print("\n✅ At least one approach succeeded on some tasks")
sys.exit(0)
except KeyboardInterrupt:
print("\n\n⚠️ Test interrupted by user")
sys.exit(130)
except Exception as e:
print(f"\n❌ Fatal error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
-170
View File
@@ -1,170 +0,0 @@
#!/usr/bin/env python3
"""
Basic smoke tests for Terminal-Bench Integration
Tests basic functionality without requiring LLM calls or external dependencies.
"""
import sys
import os
# Add the package to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'praisonai-agents'))
def test_imports():
"""Test that all required modules can be imported."""
try:
from praisonaiagents import Agent
print("✅ Agent imported successfully")
from praisonaiagents.tools import execute_command
print("✅ execute_command tool imported successfully")
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
print("✅ Approval system imported successfully")
return True
except ImportError as e:
print(f"❌ Import failed: {e}")
return False
def test_agent_creation():
"""Test that Agent can be created without issues."""
try:
from praisonaiagents import Agent
# Create agent without calling LLM
agent = Agent(
name='test-agent',
instructions='Test agent for terminal tasks'
)
assert agent.name == 'test-agent'
print("✅ Agent creation successful")
return True
except Exception as e:
print(f"❌ Agent creation failed: {e}")
return False
def test_approval_system():
"""Test approval system configuration."""
try:
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
registry = get_approval_registry()
registry.set_backend(AutoApproveBackend())
print("✅ Approval system configuration successful")
return True
except Exception as e:
print(f"❌ Approval system test failed: {e}")
return False
def test_external_agent_import():
"""Test that our external agent can be imported."""
try:
# Mock Harbor imports for testing
import sys
from unittest.mock import MagicMock
sys.modules['harbor'] = MagicMock()
sys.modules['harbor.agents'] = MagicMock()
sys.modules['harbor.agents.base'] = MagicMock()
sys.modules['harbor.environments'] = MagicMock()
sys.modules['harbor.environments.base'] = MagicMock()
sys.modules['harbor.models'] = MagicMock()
sys.modules['harbor.models.agent'] = MagicMock()
sys.modules['harbor.models.agent.context'] = MagicMock()
# Now try to import our agent
import praisonai_external_agent
print("✅ External agent import successful")
return True
except Exception as e:
print(f"❌ External agent import failed: {e}")
return False
def test_multi_agent_import():
"""Test that multi-agent example can be imported."""
try:
import sys
from unittest.mock import MagicMock
# Mock Harbor for testing
sys.modules['harbor'] = MagicMock()
sys.modules['harbor.agents'] = MagicMock()
sys.modules['harbor.agents.base'] = MagicMock()
sys.modules['harbor.environments'] = MagicMock()
sys.modules['harbor.environments.base'] = MagicMock()
sys.modules['harbor.models'] = MagicMock()
sys.modules['harbor.models.agent'] = MagicMock()
sys.modules['harbor.models.agent.context'] = MagicMock()
import multi_agent_example
print("✅ Multi-agent example import successful")
return True
except Exception as e:
print(f"❌ Multi-agent example import failed: {e}")
return False
def test_wrapper_agent_import():
"""Test that wrapper agent can be imported."""
try:
import sys
from unittest.mock import MagicMock
# Mock Harbor for testing
sys.modules['harbor'] = MagicMock()
sys.modules['harbor.agents'] = MagicMock()
sys.modules['harbor.agents.base'] = MagicMock()
sys.modules['harbor.environments'] = MagicMock()
sys.modules['harbor.environments.base'] = MagicMock()
sys.modules['harbor.models'] = MagicMock()
sys.modules['harbor.models.agent'] = MagicMock()
sys.modules['harbor.models.agent.context'] = MagicMock()
import praisonai_wrapper_agent
print("✅ Wrapper agent import successful (CLI-based approach)")
return True
except Exception as e:
print(f"❌ Wrapper agent import failed: {e}")
return False
if __name__ == "__main__":
print("PraisonAI Terminal-Bench Integration - Basic Tests")
print("=" * 55)
tests = [
("Basic Imports", test_imports),
("Agent Creation", test_agent_creation),
("Approval System", test_approval_system),
("External Agent Import", test_external_agent_import),
("Multi-Agent Import", test_multi_agent_import),
("Wrapper Agent Import", test_wrapper_agent_import),
]
passed = 0
total = len(tests)
for test_name, test_func in tests:
print(f"\n🧪 Testing {test_name}...")
try:
if test_func():
passed += 1
except Exception as e:
print(f"❌ Test {test_name} crashed: {e}")
print("\n" + "=" * 55)
print(f"Results: {passed}/{total} tests passed")
if passed == total:
print("🎉 ALL BASIC TESTS PASSED!")
print("The Terminal-Bench integration components are properly structured.")
else:
print("❌ Some tests failed - check output above")
sys.exit(1)
-274
View File
@@ -1,274 +0,0 @@
"""
Tests for PraisonAI Terminal-Bench 2.0 Integration
This module contains both unit tests and integration tests for the
PraisonAI Harbor integration.
Run tests with:
python -m pytest examples/terminal_bench/test_integration.py -v
Requirements:
pip install pytest praisonaiagents
# Harbor is optional for unit tests, required for integration tests
"""
import pytest
import asyncio
from unittest.mock import Mock, AsyncMock, patch
from pathlib import Path
class TestPraisonAIExternalAgent:
"""Test the external agent implementation."""
def test_agent_metadata(self):
"""Test agent name and version reporting."""
try:
from .praisonai_external_agent import PraisonAIExternalAgent
except ImportError:
pytest.skip("Harbor not installed - skipping Harbor-specific tests")
agent = PraisonAIExternalAgent()
assert agent.name() == "praisonai"
# Version should be None if praisonaiagents not installed, or actual version
version = agent.version()
assert version is None or isinstance(version, str)
@pytest.mark.asyncio
async def test_setup(self):
"""Test agent setup phase."""
try:
from .praisonai_external_agent import PraisonAIExternalAgent
except ImportError:
pytest.skip("Harbor not installed")
agent = PraisonAIExternalAgent()
mock_env = Mock()
# Setup should complete without error (external agent needs no setup)
await agent.setup(mock_env)
@pytest.mark.asyncio
async def test_bash_tool_execution(self):
"""Test the bash tool wrapper around Harbor's exec()."""
try:
from .praisonai_external_agent import PraisonAIExternalAgent
except ImportError:
pytest.skip("Harbor not installed")
# Mock Harbor environment
mock_env = Mock()
mock_result = Mock()
mock_result.stdout = "Hello, World!"
mock_result.stderr = ""
mock_result.return_code = 0
mock_env.exec = AsyncMock(return_value=mock_result)
# Mock agent context
mock_context = Mock()
mock_context.metadata = {}
agent = PraisonAIExternalAgent()
# Mock PraisonAI Agent to avoid LLM calls in tests
with patch('praisonaiagents.Agent') as mock_agent_class:
mock_agent_instance = Mock()
mock_agent_instance.start.return_value = "Task completed successfully"
mock_agent_instance.name = "terminal-agent"
mock_agent_class.return_value = mock_agent_instance
# Mock approval backend
with patch('praisonaiagents.approval.set_approval_backend'):
await agent.run("echo 'Hello, World!'", mock_env, mock_context)
# Verify agent was created with correct parameters
mock_agent_class.assert_called_once()
args, kwargs = mock_agent_class.call_args
assert kwargs['name'] == 'terminal-agent'
assert 'tools' in kwargs
assert len(kwargs['tools']) == 1 # bash_tool
assert kwargs['verbose'] is False
assert kwargs['memory'] is False
def test_context_population(self):
"""Test that agent context is properly populated."""
try:
from .praisonai_external_agent import PraisonAIExternalAgent
except ImportError:
pytest.skip("Harbor not installed")
agent_impl = PraisonAIExternalAgent()
# Mock agent with usage data
mock_agent = Mock()
mock_agent.name = "test-agent"
mock_agent.llm = "gpt-4o"
mock_agent._usage = Mock()
mock_agent._usage.input_tokens = 100
mock_agent._usage.output_tokens = 50
mock_agent._cost = 0.01
mock_context = Mock()
mock_context.metadata = {}
# Test context population
agent_impl._populate_context(mock_agent, mock_context, "Test result")
assert mock_context.n_input_tokens == 100
assert mock_context.n_output_tokens == 50
assert mock_context.cost_usd == 0.01
assert mock_context.metadata['agent_name'] == 'test-agent'
assert mock_context.metadata['model'] == 'gpt-4o'
assert mock_context.metadata['framework'] == 'praisonai'
class TestPraisonAIInstalledAgent:
"""Test the installed agent implementation."""
def test_agent_configuration(self):
"""Test agent CLI flags and configuration."""
try:
from .praisonai_installed_agent import PraisonAIInstalledAgent
except ImportError:
pytest.skip("Harbor not installed")
agent = PraisonAIInstalledAgent()
assert agent.name() == "praisonai"
assert agent.SUPPORTS_ATIF is False # Until trajectory format is implemented
# Check CLI flags
flag_names = [flag.name if hasattr(flag, 'name') else str(flag) for flag in agent.CLI_FLAGS]
expected_flags = ['max_turns', 'verbose', 'memory', 'auto_approval']
for expected in expected_flags:
assert any(expected in flag_name for flag_name in flag_names)
def test_version_command(self):
"""Test version detection command."""
try:
from .praisonai_installed_agent import PraisonAIInstalledAgent
except ImportError:
pytest.skip("Harbor not installed")
agent = PraisonAIInstalledAgent()
version_cmd = agent.get_version_command()
assert version_cmd is not None
assert "praisonaiagents" in version_cmd
assert "__version__" in version_cmd
def test_runner_script_generation(self):
"""Test that the headless runner script is properly generated."""
try:
from .praisonai_installed_agent import PraisonAIInstalledAgent
except ImportError:
pytest.skip("Harbor not installed")
agent = PraisonAIInstalledAgent()
script = agent._build_runner_script()
assert "import praisonaiagents" in script
assert "Agent" in script
assert "execute_command" in script
assert "AutoApproveBackend" in script
assert "json.dumps" in script
class TestIntegration:
"""Integration tests that require both PraisonAI and Harbor."""
@pytest.mark.integration
def test_praisonai_agent_real(self):
"""
Real agentic test - agent must call LLM end-to-end.
This is a MANDATORY test per AGENTS.md §9.4.
Agent MUST call agent.start() with real prompt and produce LLM output.
"""
try:
from praisonaiagents import Agent
except ImportError:
pytest.skip("PraisonAI not installed")
# Create real agent that will call LLM
agent = Agent(
name="test-terminal-agent",
instructions="You are a helpful terminal assistant"
)
# Real agentic test - agent must call LLM and produce text response
result = agent.start("Say hello in one sentence and mention you can help with terminal tasks")
# Verify we got actual LLM output
assert result is not None
assert isinstance(result, str)
assert len(result) > 0
# Print output for manual verification
print("✅ Real agentic test result:")
print(result)
@pytest.mark.integration
@pytest.mark.asyncio
async def test_bash_tool_real_execution(self):
"""Test bash tool with real command execution (if safe)."""
# This would test real bash execution in a safe environment
# For now, we'll mock it to avoid system changes
mock_result = Mock()
mock_result.stdout = "PraisonAI Terminal Test\n"
mock_result.stderr = ""
mock_result.return_code = 0
# Test that our bash tool wrapper works correctly
async def mock_exec(command, timeout_sec=30):
assert "echo" in command # Ensure we're testing echo command
return mock_result
# This simulates Harbor's BaseEnvironment.exec()
result = await mock_exec("echo 'PraisonAI Terminal Test'")
assert result.stdout.strip() == "PraisonAI Terminal Test"
assert result.return_code == 0
@pytest.mark.integration
def test_auto_approval_setup(self):
"""Test that auto-approval backend works correctly."""
try:
from praisonaiagents.approval import set_approval_backend, AutoApproveBackend
except ImportError:
pytest.skip("PraisonAI approval system not available")
# Test setting and restoring approval backend
original = set_approval_backend(AutoApproveBackend())
new_backend = set_approval_backend(original)
assert isinstance(new_backend, AutoApproveBackend)
if __name__ == "__main__":
# Allow running tests directly
import sys
print("PraisonAI Terminal-Bench 2.0 Integration Tests")
print("=" * 50)
# Check dependencies
try:
import praisonaiagents
print(f"✅ PraisonAI version: {praisonaiagents.__version__}")
except ImportError:
print("❌ PraisonAI not installed: pip install praisonaiagents")
sys.exit(1)
try:
import harbor
print("✅ Harbor framework available")
except ImportError:
print("⚠️ Harbor not installed: pip install harbor")
print(" (Some tests will be skipped)")
print()
print("Run tests with: python -m pytest examples/terminal_bench/test_integration.py -v")
print("Run real agentic test: python -m pytest examples/terminal_bench/test_integration.py::TestIntegration::test_praisonai_agent_real -v -s")
@@ -1,105 +0,0 @@
#!/usr/bin/env python3
"""
Real Agentic Test for Terminal-Bench Integration
This is a MANDATORY test per AGENTS.md §9.4.
The agent MUST call agent.start() with a real prompt and produce LLM output.
"""
import sys
import os
# Add the package to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'praisonai-agents'))
def test_real_agentic():
"""Real agentic test - agent must call LLM end-to-end."""
try:
from praisonaiagents import Agent
except ImportError as e:
print(f"❌ Cannot import praisonaiagents: {e}")
print("Make sure you're running from the correct directory")
return False
try:
# Create agent that will call LLM
agent = Agent(
name='terminal-bench-test-agent',
instructions='You are a helpful assistant that can help with terminal and coding tasks'
)
# MUST call agent.start() with real prompt - this calls the LLM
print("🚀 Starting real agentic test (calling LLM)...")
result = agent.start('Say hello and briefly mention you can help with terminal tasks. Keep it to one sentence.')
# Verify we got actual LLM output
assert result is not None, "Agent returned None"
assert isinstance(result, str), f"Agent returned {type(result)}, expected str"
assert len(result) > 0, "Agent returned empty string"
print("✅ Real agentic test PASSED!")
print(f"📊 Result length: {len(result)} characters")
print(f"🤖 Agent response: {result}")
return True
except Exception as e:
print(f"❌ Real agentic test FAILED: {e}")
return False
def test_shell_tools():
"""Test that shell tools are available and importable."""
try:
from praisonaiagents.tools import execute_command
print("✅ Shell tools imported successfully")
# Test tool metadata
print(f"📋 execute_command available: {callable(execute_command)}")
return True
except ImportError as e:
print(f"❌ Cannot import shell tools: {e}")
return False
def test_approval_system():
"""Test that approval system works for auto-approval."""
try:
from praisonaiagents.approval import get_approval_registry, AutoApproveBackend
# Test setting auto-approval backend
registry = get_approval_registry()
registry.set_backend(AutoApproveBackend())
print("✅ Approval system works correctly")
return True
except ImportError as e:
print(f"❌ Cannot import approval system: {e}")
return False
if __name__ == "__main__":
print("PraisonAI Terminal-Bench Integration - Real Agentic Test")
print("=" * 60)
all_passed = True
# Test 1: Shell tools
print("\n🧪 Test 1: Shell Tools Import")
if not test_shell_tools():
all_passed = False
# Test 2: Approval system
print("\n🧪 Test 2: Approval System")
if not test_approval_system():
all_passed = False
# Test 3: Real agentic test (MANDATORY)
print("\n🧪 Test 3: Real Agentic Test (MANDATORY)")
if not test_real_agentic():
all_passed = False
print("\n" + "=" * 60)
if all_passed:
print("🎉 ALL TESTS PASSED - Terminal-Bench integration is ready!")
else:
print("❌ Some tests failed - check output above")
sys.exit(1)
Loaded 100 of 371 files, more files were not shown because too many files have changed in this diff. Show more