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
841 changed files with 13913 additions and 104186 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. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. ' + 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. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. 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. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. 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. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. 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
+16 -200
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,136 +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' || 'NOTE: The branch is under MervinPraison/PraisonAI (not a fork). You are able to make modifications and push directly to this branch.\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.
SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify or touch praisonai-rust or praisonai-ts packages.
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):
- Create a fix branch: git checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
- Implement a minimal, focused fix
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
- Commit: git add -A && git commit -m "fix: <description> (fixes #$ISSUE_NUMBER)"
- Push: git push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
CRITICAL: After pushing, you MUST immediately run: gh pr create --title "fix: <title>" --body "Fixes #$ISSUE_NUMBER" --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
Do NOT stop after pushing. Do NOT say "manually create a PR". Always create the PR automatically.
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 -2
View File
@@ -94,8 +94,7 @@ Cargo.lock
**/*.rs.bk
*.pdb
.windsurf
.agent
src/praisonai/build*.egg-info/
src/praisonai/PraisonAI.egg-info/
jobs/*
*.egg-info
-29
View File
@@ -1,29 +0,0 @@
# Contributing to PraisonAI
Thank you for your interest! Here's how to get started:
## Getting Started
1. Fork the repository
2. Clone your fork and install dependencies:
```bash
git clone https://github.com/your-username/PraisonAI.git
cd PraisonAI
pip install -e src/praisonai-agents/
pip install -e src/praisonai/
```
3. Create a feature branch: `git checkout -b feature/your-feature`
4. Make your changes
5. Submit a pull request
## Guidelines
- Follow existing code style (PEP 8)
- Add tests for new features and run them locally using pytest:
```bash
cd src/praisonai-agents && pytest tests/
```
- Update documentation as needed
- Check existing issues or open a new one to discuss your idea before implementing large changes
For questions or support, please visit our [GitHub Issues](https://github.com/MervinPraison/PraisonAI/issues).
+96 -78
View File
@@ -23,17 +23,14 @@
</div>
PraisonAI 🦞 — **Hire a 24/7 AI Workforce.** Stop writing boilerplate and start shipping autonomous, self-improving agents that research, plan, and execute tasks across your apps. From one agent to an entire organization, deployed in 5 lines of code.
```bash
curl -fsSL https://praison.ai/install.sh | bash
```
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>
@@ -72,6 +69,26 @@ curl -fsSL https://praison.ai/install.sh | bash
---
> **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:
@@ -87,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" />
@@ -182,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?
@@ -231,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
```
---
@@ -654,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) |
@@ -729,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
@@ -751,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:
+3 -87
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>
@@ -172,7 +167,6 @@ Methods:
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate</a>() -> bool</code>
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_class</a>() -> bool</code>
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">__call__</a>(*args, **kwargs) -> Any</code>
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">check_availability</a>() -> tuple[bool, str]</code>
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">injected_params</a>() -> Dict[str, Any]</code>
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">run</a>(**kwargs) -> Any</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">clear</a>() -> None</code>
@@ -180,7 +174,6 @@ Methods:
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">discover_single_file_plugins</a>() -> int</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_all</a>() -> Dict[str, Union[BaseTool, Callable]]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_available_tools</a>(context: Optional[Dict[str, Any]] = None) -> List[Union[BaseTool, Callable]]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_base_tools</a>() -> List[BaseTool]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">list_tools</a>() -> List[str]</code>
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None, overwrite: bool = False) -> None</code>
@@ -208,12 +201,10 @@ Methods:
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">execute_callback_sync</a>(task_output: TaskOutput) -> None</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">get_next_task</a>(context: Dict[str, Any]) -> Optional[str]</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">initialize_memory</a>()</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">initialize_memory_async</a>()</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">set_status</a>(new_status: str) -> None</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">store_in_memory</a>(content: str, agent_name: str = None, task_id: str = None)</code>
* <code title="class Task">Task.<a href="./src/praisonai-agents/praisonaiagents/task/task.py">to_dict</a>() -> Dict[str, Any]</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">loop</a>(step: Any = None, steps: Optional[List[Any]] = None, over: Optional[str] = None, from_csv: Optional[str] = None, from_file: Optional[str] = None, var_name: str = 'item', parallel: bool = False, max_workers: Optional[int] = None, output_variable: Optional[str] = None) -> Loop</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">parallel</a>(steps: List, max_workers: Optional[int] = None, on_failure: str = 'partial_ok') -> Parallel</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">parallel</a>(steps: List) -> Parallel</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">repeat</a>(step: Any, until: Optional[Callable[[WorkflowContext], bool]] = None, max_iterations: int = 10) -> Repeat</code>
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">route</a>(routes: Dict[str, List], default: Optional[List] = None) -> Route</code>
@@ -368,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
@@ -470,23 +458,14 @@ Methods:
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">activate_by_name</a>(name: str) -> bool</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">add_skill</a>(skill_path: str) -> Optional[LoadedSkill]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">clear</a>() -> None</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">create_skill</a>(name: str, content: str, category: str = None) -> dict</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">delete_skill</a>(name: str) -> dict</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">discover</a>(skill_dirs: Optional[List[str]] = None, include_defaults: bool = True) -> int</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">edit_skill</a>(name: str, content: str) -> dict</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_allowed_tools</a>(name: str) -> List[str]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_available_skills</a>() -> List[SkillMetadata]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_instructions</a>(name: str) -> Optional[str]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_skill</a>(name: str) -> Optional[LoadedSkill]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">get_user_invocable_skills</a>() -> List[LoadedSkill]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">invoke</a>(name: str, raw_args: str = '', session_id: Optional[str] = None, shell_exec: bool = False) -> Optional[str]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">load_resources</a>(name: str) -> bool</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">patch_skill</a>(name: str, old_string: str, new_string: str, file_path: str = None, replace_all: bool = False) -> dict</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">remove_skill_file</a>(name: str, file_path: str) -> dict</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skill_names</a>() -> List[str]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">skills</a>() -> List[LoadedSkill]</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">to_prompt</a>() -> str</code>
* <code title="class SkillManager">SkillManager.<a href="./src/praisonai-agents/praisonaiagents/skills/manager.py">write_skill_file</a>(name: str, file_path: str, file_content: str) -> dict</code>
* <code title="class SkillMetadata">SkillMetadata.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">from_properties</a>(props: SkillProperties) -> 'SkillMetadata'</code>
* <code title="class SkillProperties">SkillProperties.<a href="./src/praisonai-agents/praisonaiagents/skills/models.py">to_dict</a>() -> dict</code>
* <code title="class SkillsConfig">SkillsConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
@@ -568,7 +547,7 @@ Methods:
Types:
```python
from praisonaiagents import FlowDisplay, track_workflow
from praisonaiagents import FlowDisplay, obs, track_workflow
```
Methods:
@@ -713,7 +692,6 @@ Methods:
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">astart</a>(content = None, return_dict = False, **kwargs)</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clean_json_output</a>(output: str) -> str</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">clear_state</a>() -> None</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">close</a>() -> None</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">context_manager</a>()</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">current_plan</a>()</code>
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">default_completion_checker</a>(task, agent_output)</code>
@@ -755,7 +733,7 @@ Methods:
Types:
```python
from praisonai import Agent, AgentApp, AgentOS, AnthropicManagedAgent, CloudProvider, DB, Deploy, DeployConfig, DeployType, HostedAgent, HostedAgentConfig, LocalAgent, LocalAgentConfig, LocalManagedAgent, LocalManagedConfig, ManagedAgent, ManagedConfig, PraisonAI, __version__
from praisonai import AgentApp, AgentOS, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
```
# CLI
@@ -785,8 +763,6 @@ Methods:
* <code title="cli">praisonai bot bot-callback <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot discord <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot email <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot install-daemon <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot linear <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot slack <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot start <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
* <code title="cli">praisonai bot telegram <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
@@ -827,7 +803,6 @@ Methods:
* <code title="cli">praisonai context show <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context stats <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai context tail <a href="./src/praisonai/praisonai/cli/commands/context.py">--help</a></code>
* <code title="cli">praisonai dashboard unified <a href="./src/praisonai/praisonai/cli/commands/dashboard.py">--help</a></code>
* <code title="cli">praisonai debug acp <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug debug-callback <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
* <code title="cli">praisonai debug interactive <a href="./src/praisonai/praisonai/cli/commands/debug.py">--help</a></code>
@@ -867,28 +842,19 @@ Methods:
* <code title="cli">praisonai eval judge <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
* <code title="cli">praisonai eval list-judges <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
* <code title="cli">praisonai eval performance <a href="./src/praisonai/praisonai/cli/commands/eval.py">--help</a></code>
* <code title="cli">praisonai examples find <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples info <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples list <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples report <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples run <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples run-all <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai examples stats <a href="./src/praisonai/praisonai/cli/commands/examples.py">--help</a></code>
* <code title="cli">praisonai flow export <a href="./src/praisonai/praisonai/cli/commands/flow.py">--help</a></code>
* <code title="cli">praisonai flow flow-start <a href="./src/praisonai/praisonai/cli/commands/flow.py">--help</a></code>
* <code title="cli">praisonai flow import <a href="./src/praisonai/praisonai/cli/commands/flow.py">--help</a></code>
* <code title="cli">praisonai flow list <a href="./src/praisonai/praisonai/cli/commands/flow.py">--help</a></code>
* <code title="cli">praisonai flow version <a href="./src/praisonai/praisonai/cli/commands/flow.py">--help</a></code>
* <code title="cli">praisonai gateway channels <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway gateway-callback <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway install <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway logs <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <code title="cli">praisonai gateway mint-link <a href="./src/praisonai/praisonai/cli/commands/gateway.py">--help</a></code>
* <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 gateway uninstall <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>
@@ -896,19 +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 langextract render <a href="./src/praisonai/praisonai/cli/commands/langextract.py">--help</a></code>
* <code title="cli">praisonai langextract view <a href="./src/praisonai/praisonai/cli/commands/langextract.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>
@@ -916,17 +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 delete <a href="./src/praisonai/praisonai/cli/commands/managed.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>
@@ -944,22 +886,10 @@ Methods:
* <code title="cli">praisonai memory search <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory show <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai memory status <a href="./src/praisonai/praisonai/cli/commands/memory.py">--help</a></code>
* <code title="cli">praisonai n8n export <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n import <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n list <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n preview <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n pull <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n push <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai n8n test <a href="./src/praisonai/praisonai/cli/commands/n8n.py">--help</a></code>
* <code title="cli">praisonai obs obs-fallback <a href="./src/praisonai/praisonai/cli/commands/obs.py">--help</a></code>
* <code title="cli">praisonai onboard onboard-callback <a href="./src/praisonai/praisonai/cli/commands/onboard.py">--help</a></code>
* <code title="cli">praisonai package install <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package list <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai package uninstall <a href="./src/praisonai/praisonai/cli/commands/package.py">--help</a></code>
* <code title="cli">praisonai pairing approve <a href="./src/praisonai/praisonai/cli/commands/pairing.py">--help</a></code>
* <code title="cli">praisonai pairing clear <a href="./src/praisonai/praisonai/cli/commands/pairing.py">--help</a></code>
* <code title="cli">praisonai pairing list <a href="./src/praisonai/praisonai/cli/commands/pairing.py">--help</a></code>
* <code title="cli">praisonai pairing revoke <a href="./src/praisonai/praisonai/cli/commands/pairing.py">--help</a></code>
* <code title="cli">praisonai paths show <a href="./src/praisonai/praisonai/cli/commands/paths.py">--help</a></code>
* <code title="cli">praisonai plugins create <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
* <code title="cli">praisonai plugins disable <a href="./src/praisonai/praisonai/cli/commands/plugins.py">--help</a></code>
@@ -971,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>
@@ -1056,10 +982,6 @@ Methods:
* <code title="cli">praisonai session list <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
* <code title="cli">praisonai session resume <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
* <code title="cli">praisonai session show <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
* <code title="cli">praisonai setup config <a href="./src/praisonai/praisonai/cli/commands/setup.py">--help</a></code>
* <code title="cli">praisonai setup reset <a href="./src/praisonai/praisonai/cli/commands/setup.py">--help</a></code>
* <code title="cli">praisonai setup setup-callback <a href="./src/praisonai/praisonai/cli/commands/setup.py">--help</a></code>
* <code title="cli">praisonai setup wizard <a href="./src/praisonai/praisonai/cli/commands/setup.py">--help</a></code>
* <code title="cli">praisonai skills check <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills create <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
* <code title="cli">praisonai skills eligible <a href="./src/praisonai/praisonai/cli/commands/skills.py">--help</a></code>
@@ -1099,13 +1021,7 @@ Methods:
* <code title="cli">praisonai train llm <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
* <code title="cli">praisonai train show <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
* <code title="cli">praisonai train train-callback <a href="./src/praisonai/praisonai/cli/commands/train.py">--help</a></code>
* <code title="cli">praisonai ui agents <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai ui bot <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai ui realtime <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai ui ui <a href="./src/praisonai/praisonai/cli/commands/ui.py">--help</a></code>
* <code title="cli">praisonai up logs <a href="./src/praisonai/praisonai/cli/commands/up.py">--help</a></code>
* <code title="cli">praisonai up status <a href="./src/praisonai/praisonai/cli/commands/up.py">--help</a></code>
* <code title="cli">praisonai up up-start <a href="./src/praisonai/praisonai/cli/commands/up.py">--help</a></code>
* <code title="cli">praisonai version check <a href="./src/praisonai/praisonai/cli/commands/version.py">--help</a></code>
* <code title="cli">praisonai version show <a href="./src/praisonai/praisonai/cli/commands/version.py">--help</a></code>
* <code title="cli">praisonai version version-callback <a href="./src/praisonai/praisonai/cli/commands/version.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.37" \
"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.37" \
"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.37" \
"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)
+30 -22
View File
@@ -1,40 +1,48 @@
"""
Langfuse Integration Example (Updated for TraceSinkProtocol)
Langfuse Integration Example
This example shows how to use Langfuse for LLM observability with PraisonAI's native trace infrastructure.
This example shows how to use Langfuse for LLM observability.
Setup:
pip install "praisonai[langfuse]"
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
export LANGFUSE_SECRET_KEY=sk-lf-xxx
1. Sign up at https://langfuse.com/
2. Get your API keys from the project settings
3. Set environment variables:
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
export LANGFUSE_SECRET_KEY=sk-lf-xxx
4. Install dependencies:
pip install opentelemetry-sdk opentelemetry-exporter-otlp
Usage:
python langfuse_example.py
"""
import os
from praisonai_tools.observability import obs
from praisonaiagents import Agent
from praisonai.observability import LangfuseSink
from praisonaiagents.trace.protocol import (
TraceEmitter, set_default_emitter
# Initialize Langfuse
success = obs.init(
provider="langfuse",
project_name="praisonai-demo",
)
# Initialize Langfuse observability
sink = LangfuseSink()
emitter = TraceEmitter(sink=sink, enabled=True)
set_default_emitter(emitter)
if not success:
print("Failed to initialize Langfuse. Check your API keys.")
print("Required: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY")
exit(1)
# Create and run agent — all traces automatically captured
print("Langfuse initialized successfully!")
print(f"View traces at: https://cloud.langfuse.com/")
# Create agent
agent = Agent(
name="Coder",
instructions="You are a helpful coding assistant.",
llm="openai/gpt-4o-mini",
model="gpt-4o-mini",
)
try:
result = agent.start("Write a Python function to calculate fibonacci numbers")
print(result)
finally:
# Ensure traces are flushed and resources cleaned up
sink.flush()
sink.close()
# Run with tracing
with obs.trace("coding-session", user_id="developer-1"):
response = agent.chat("Write a Python function to calculate fibonacci numbers")
print(response)
print("\nCheck Langfuse dashboard for traces!")
@@ -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,
-68
View File
@@ -1,68 +0,0 @@
"""
Linear Agent Example for PraisonAI.
Demonstrates how to create a Linear bot that responds to issue mentions
and assignments with full coding capabilities.
"""
import asyncio
from praisonaiagents import Agent
from praisonai.bots import LinearBot
# Create an agent with Linear tools and coding capabilities
agent = Agent(
name="PraisonAI Coder",
instructions="""
You are an autonomous coding agent integrated with Linear.
When you are mentioned or assigned an issue:
1. Analyze the issue description and requirements
2. Break down complex tasks into smaller steps
3. Use available tools to implement solutions
4. Update the issue with progress comments
5. Create GitHub pull requests when code changes are needed
Focus on being helpful, thorough, and providing clear updates.
""",
llm="gpt-4o-mini",
tools=[
"linear_search_issues",
"linear_get_issue",
"linear_add_comment",
"linear_update_issue",
"linear_list_teams",
"linear_list_issue_states",
"github_create_branch",
"github_commit_and_push",
"github_create_pull_request",
"read_file",
"write_file",
"execute_command"
],
memory=True,
web_search=True,
auto_approve_tools=True,
)
# Create Linear bot
bot = LinearBot(
token="your-linear-oauth-token", # or set LINEAR_OAUTH_TOKEN env var
signing_secret="your-webhook-secret", # or set LINEAR_WEBHOOK_SECRET env var
agent=agent,
webhook_port=8080,
)
async def main():
"""Start the Linear bot."""
print("Starting Linear bot...")
print("Set up your Linear webhook to point to: http://your-host:8080/webhook")
print("Configure AgentSession and Comment events in Linear webhook settings")
try:
await bot.start()
except KeyboardInterrupt:
print("\nStopping bot...")
await bot.stop()
if __name__ == "__main__":
asyncio.run(main())
@@ -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 demonstrates local agent loops with various compute backends for tool sandboxing.
Uses the new LocalAgent class to clearly indicate local execution with optional cloud compute.
Requires:
- Docker running locally
- E2B_API_KEY set
- modal CLI configured (modal token set)
"""
import asyncio
from praisonai import Agent
from praisonai.integrations import LocalAgent, LocalAgentConfig
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 = LocalAgent(
compute=compute,
config=LocalAgentConfig(
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,47 +0,0 @@
"""Run every managed-runtime example and report a summary.
Executes the runtime_*.py files in this directory as subprocesses with a
30s per-example timeout. Exits non-zero if any example fails.
"""
import os
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
EXAMPLES = [
# ── LLM providers (where the LLM reasoning happens) ──
"runtime_anthropic.py", # full loop in Anthropic cloud
"runtime_openai.py", # LLM in OpenAI cloud
"runtime_gemini.py", # LLM in Google cloud
"runtime_ollama.py", # LLM on local/self-hosted Ollama
# ── Cloud/compute providers (where tool execution happens) ──
"runtime_e2b.py", # tools sandboxed in E2B cloud VM
"runtime_modal.py", # tools sandboxed in Modal serverless cloud
"runtime_fly.py", # tools sandboxed on Fly.io Machines
"runtime_daytona.py", # tools sandboxed in Daytona cloud workspace
"runtime_docker.py", # tools sandboxed in local Docker container
]
results = []
for name in EXAMPLES:
path = os.path.join(HERE, name)
start = time.time()
proc = subprocess.run(
[sys.executable, path],
capture_output=True, text=True, timeout=120,
env=os.environ.copy(), # propagate API keys and PYTHONPATH
)
dur = time.time() - start
ok = proc.returncode == 0
results.append((name, ok, dur, proc.stdout.strip().splitlines()[-1] if proc.stdout.strip() else ""))
print(f"[{'OK' if ok else 'FAIL'}] {name:30s} {dur:.2f}s")
if not ok:
print(proc.stdout)
print(proc.stderr)
print("\nSummary:")
for name, ok, dur, tail in results:
print(f" {'✓' if ok else '✗'} {name:30s} {dur:5.2f}s {tail[:60]}")
sys.exit(0 if all(r[1] for r in results) else 1)
@@ -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,46 +0,0 @@
"""Local agent — basic local execution with gpt-4o-mini.
No external infrastructure needed. Runs the agent loop locally.
Uses the new canonical LocalAgent class for clarity.
"""
from praisonai import Agent
from praisonai.integrations import LocalAgent, LocalAgentConfig
# Create a local agent (runs locally, no managed runtime)
managed = LocalAgent(
config=LocalAgentConfig(
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!")
@@ -1,47 +0,0 @@
"""Anthropic managed runtime — full agent loop runs in Anthropic's cloud.
The ENTIRE loop (LLM calls, tool execution, memory, sessions) runs remotely
on Anthropic's managed infrastructure. This is the only provider today where
"the whole thing runs in that cloud".
Requires: ANTHROPIC_API_KEY environment variable set.
"""
import os
if not os.getenv("ANTHROPIC_API_KEY"):
print("[skip] ANTHROPIC_API_KEY not set — set it to run this example.")
raise SystemExit(0)
try:
import anthropic # noqa: F401 (required by AnthropicManagedAgent)
except ImportError:
print("[skip] 'anthropic' SDK not installed — pip install 'anthropic>=0.94.0'")
raise SystemExit(0) from None
try:
from praisonai import Agent, ManagedAgent, ManagedConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
managed = ManagedAgent(
provider="anthropic",
config=ManagedConfig(
model="claude-3-5-sonnet-latest",
system="You are a concise coding assistant. One-sentence answers.",
name="AnthropicRuntimeAgent",
),
)
agent = Agent(name="anthropic-runtime", backend=managed)
# 1. Agent work entirely in the cloud
print("[1] Agent:", agent.start("Write a Python one-liner that sums 1..10. Just the code."))
# 2. Multi-turn reuses the same cloud session
print("[2] Agent:", agent.start("Now change it to factorial of 5."))
# 3. Cloud-side session metadata
info = managed.retrieve_session()
print(f"[3] session={managed.session_id} status={info.get('status')} "
f"in={info.get('usage', {}).get('input_tokens', 0)} "
f"out={info.get('usage', {}).get('output_tokens', 0)}")
@@ -1,52 +0,0 @@
"""Agent runtime with tools sandboxed in a Daytona cloud workspace.
The agent's LLM loop runs locally; tool / code execution runs in a Daytona-
managed workspace (disposable cloud dev environment, pre-configured images).
Requires:
- ``OPENAI_API_KEY`` for the LLM
- ``DAYTONA_API_KEY`` for the Daytona API
- ``pip install daytona-sdk``
"""
import asyncio
import os
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set.")
raise SystemExit(0)
if not os.getenv("DAYTONA_API_KEY"):
print("[skip] DAYTONA_API_KEY not set.")
raise SystemExit(0)
try:
from praisonai import Agent
from praisonai.integrations import SandboxedAgent, SandboxedAgentConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
async def main():
sandboxed = SandboxedAgent(
compute="daytona", # tool execution runs in a Daytona workspace
config=SandboxedAgentConfig(
model="gpt-4o-mini",
system="You are a concise coding assistant.",
name="DaytonaRuntimeAgent",
),
)
agent = Agent(name="daytona-runtime", backend=sandboxed)
info = await sandboxed.provision_compute(idle_timeout_s=120)
print(f"[1] Daytona workspace: {info.instance_id} ({info.status})")
result = await sandboxed.execute_in_compute("python3 -c 'print(len([1,2,3,4,5]) ** 3)'")
print(f"[2] Cloud compute result: {result['stdout'].strip()}")
print("[3] Agent:", agent.start("What is 5 cubed? Just the number."))
await sandboxed.shutdown_compute()
print("[4] Daytona workspace shut down.")
asyncio.run(main())
@@ -1,59 +0,0 @@
"""Agent runtime with tools sandboxed in a local Docker container.
The agent's LLM loop runs locally; tool / code execution is isolated in a
Docker container. Not a cloud provider, but the same pattern — useful for
local development and CI.
Requires:
- ``OPENAI_API_KEY`` for the LLM
- Docker daemon running (``docker info`` must succeed)
"""
import asyncio
import os
import subprocess
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set.")
raise SystemExit(0)
try:
subprocess.run(
["docker", "info"],
check=True, capture_output=True, timeout=5,
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
print("[skip] Docker daemon not available — start Docker Desktop.")
raise SystemExit(0) from None
try:
from praisonai import Agent
from praisonai.integrations import SandboxedAgent, SandboxedAgentConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
async def main():
sandboxed = SandboxedAgent(
compute="docker", # tool execution runs in a local Docker container
config=SandboxedAgentConfig(
model="gpt-4o-mini",
system="You are a concise coding assistant.",
name="DockerRuntimeAgent",
),
)
agent = Agent(name="docker-runtime", backend=sandboxed)
info = await sandboxed.provision_compute(image="python:3.12-slim")
print(f"[1] Docker container: {info.instance_id} ({info.status})")
result = await sandboxed.execute_in_compute("python3 -c 'print(sum(i*i for i in range(1, 11)))'")
print(f"[2] Container compute result: {result['stdout'].strip()}")
print("[3] Agent:", agent.start("What is the sum of squares from 1 to 10? Just the number."))
await sandboxed.shutdown_compute()
print("[4] Docker container shut down.")
asyncio.run(main())
@@ -1,56 +0,0 @@
"""Agent runtime with tools sandboxed in an E2B cloud VM.
The agent's LLM loop runs locally; any tool / code execution is sandboxed
in an ephemeral E2B cloud instance. Uses ``SandboxedAgent`` — the honest
name for the "local loop + cloud tools" pattern.
Requires:
- ``OPENAI_API_KEY`` for the LLM
- ``E2B_API_KEY`` for the cloud sandbox
- ``pip install e2b``
"""
import asyncio
import os
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set.")
raise SystemExit(0)
if not os.getenv("E2B_API_KEY"):
print("[skip] E2B_API_KEY not set — set it to run in the E2B cloud sandbox.")
raise SystemExit(0)
try:
from praisonai import Agent
from praisonai.integrations import SandboxedAgent, SandboxedAgentConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
async def main():
sandboxed = SandboxedAgent(
compute="e2b", # tool execution runs in E2B cloud
config=SandboxedAgentConfig(
model="gpt-4o-mini",
system="You are a concise coding assistant.",
name="E2BRuntimeAgent",
),
)
agent = Agent(name="e2b-runtime", backend=sandboxed)
# 1. Provision the cloud sandbox
info = await sandboxed.provision_compute(idle_timeout_s=120)
print(f"[1] E2B sandbox: {info.instance_id} ({info.status})")
# 2. Execute code IN the cloud sandbox
result = await sandboxed.execute_in_compute("python3 -c 'print(sum(range(1, 11)))'")
print(f"[2] Cloud compute result: {result['stdout'].strip()}")
# 3. Agent loop (LLM local, any tool calls auto-sandboxed in E2B)
print("[3] Agent:", agent.start("What is 13 * 17? Just the number."))
await sandboxed.shutdown_compute()
print("[4] E2B sandbox shut down.")
asyncio.run(main())
@@ -1,57 +0,0 @@
"""Agent runtime with tools sandboxed on Fly.io Machines.
The agent's LLM loop runs locally; tool / code execution runs in a Fly.io
Machine (fast-booting micro-VM with per-second billing and global regions).
Requires:
- ``OPENAI_API_KEY`` for the LLM
- ``FLY_API_TOKEN`` for the Fly.io Machines API
- ``FLY_APP_NAME`` (optional) existing Fly app; auto-created otherwise
"""
import asyncio
import os
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set.")
raise SystemExit(0)
if not os.getenv("FLY_API_TOKEN"):
print("[skip] FLY_API_TOKEN not set — get one via `fly auth token`.")
raise SystemExit(0)
try:
from praisonai import Agent
from praisonai.integrations import SandboxedAgent, SandboxedAgentConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
async def main():
sandboxed = SandboxedAgent(
compute="flyio", # tool execution runs on Fly.io Machines
config=SandboxedAgentConfig(
model="gpt-4o-mini",
system="You are a concise coding assistant.",
name="FlyioRuntimeAgent",
),
)
agent = Agent(name="fly-runtime", backend=sandboxed)
info = await sandboxed.provision_compute(
image="python:3.12-slim",
cpu=1,
memory_mb=512,
idle_timeout_s=120,
)
print(f"[1] Fly Machine: {info.instance_id} ({info.status})")
result = await sandboxed.execute_in_compute("python3 -c 'print(2 ** 20)'")
print(f"[2] Cloud compute result: {result['stdout'].strip()}")
print("[3] Agent:", agent.start("What is 2 raised to the 20th power? Just the number."))
await sandboxed.shutdown_compute()
print("[4] Fly Machine shut down.")
asyncio.run(main())
@@ -1,40 +0,0 @@
"""Gemini managed runtime — agent loop local, LLM calls go to Google's cloud.
``ManagedAgent(provider="gemini")`` wires a local managed loop to Gemini's
hosted models via litellm.
Requires: GEMINI_API_KEY (or GOOGLE_API_KEY) environment variable set.
"""
import os
if not (os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")):
print("[skip] GEMINI_API_KEY / GOOGLE_API_KEY not set — set one to run this example.")
raise SystemExit(0)
try:
from praisonai import Agent, ManagedAgent, LocalManagedConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
managed = ManagedAgent(
provider="gemini",
config=LocalManagedConfig(
# Use explicit litellm prefix to guarantee routing to Google
model="gemini/gemini-2.0-flash-exp",
system="You are a concise assistant. One-sentence answers.",
name="GeminiRuntimeAgent",
),
)
agent = Agent(name="gemini-runtime", backend=managed)
# 1. Agent work (LLM call goes to Google cloud)
print("[1] Agent:", agent.start("What does REST stand for? One sentence."))
# 2. Multi-turn reuses the session
print("[2] Agent:", agent.start("How is it different from GraphQL?"))
info = managed.retrieve_session()
print(f"[3] session={managed.session_id} "
f"in={info.get('usage', {}).get('input_tokens', 0)} "
f"out={info.get('usage', {}).get('output_tokens', 0)}")
@@ -1,63 +0,0 @@
"""Anthropic hosted runtime — entire agent runs on Anthropic's managed infrastructure.
Uses the new canonical HostedAgent class which clearly communicates that the entire
agent loop runs in Anthropic's cloud, not locally.
"""
import os
import sys
# Skip guards - exit cleanly if prerequisites not met
if not (os.getenv("ANTHROPIC_API_KEY") or os.getenv("CLAUDE_API_KEY")):
print("[skip] ANTHROPIC_API_KEY or CLAUDE_API_KEY not set")
sys.exit(0)
try:
import anthropic
except ImportError:
print("[skip] anthropic SDK not installed")
sys.exit(0)
# Heavy imports only after skip-guards pass
from praisonai import Agent
from praisonai.integrations import HostedAgent, HostedAgentConfig
# Create a hosted agent running entirely on Anthropic's managed runtime
hosted = HostedAgent(
provider="anthropic",
config=HostedAgentConfig(
model="claude-3-5-sonnet-latest",
system="You are a helpful coding assistant. Be concise.",
name="AnthropicHostedAgent",
tools=[{"type": "agent_toolset_20260401"}],
),
)
agent = Agent(name="anthropic-hosted", backend=hosted)
# 1. Basic execution - runs entirely in Anthropic's cloud
print("[1] Hosted execution on Anthropic infrastructure...")
result = agent.start("What is the capital of France? One word.", stream=True)
print(f" Result: {result}")
# 2. Agent metadata from Anthropic's API
print(f"\n[2] Agent ID: {hosted.agent_id}")
print(f" Version: {hosted.agent_version}")
print(f" Env ID: {hosted.environment_id}")
print(f" Session: {hosted.session_id}")
# 3. Multi-turn (same session keeps context in Anthropic's cloud)
print("\n[3] Multi-turn conversation...")
result = agent.start("What country is that city in?", stream=True)
print(f" Result: {result}")
# 4. Usage tracking from Anthropic's usage API
info = hosted.retrieve_session()
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# 5. List all sessions for this agent
sessions = hosted.list_sessions()
print(f"\n[5] Sessions: {len(sessions)}")
for s in sessions[:3]: # Show first 3
print(f" {s['id']} | {s['status']}")
print("\nDone! Agent loop ran entirely on Anthropic's managed infrastructure.")
@@ -1,61 +0,0 @@
"""Local agent loop with Gemini LLM — runs locally, not in a managed runtime.
Uses the new canonical LocalAgent class which clearly communicates that only the
agent loop runs locally. The LLM calls go to Google's Gemini API, but there's no managed runtime involved.
"""
import os
import sys
# Skip guards - exit cleanly if prerequisites not met
if not (os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")):
print("[skip] GEMINI_API_KEY or GOOGLE_API_KEY not set")
sys.exit(0)
try:
import google.generativeai
except ImportError:
print("[skip] google-generativeai SDK not installed")
sys.exit(0)
# Heavy imports only after skip-guards pass
from praisonai import Agent
from praisonai.integrations import LocalAgent, LocalAgentConfig
# Create a local agent using Gemini LLM
local = LocalAgent(
config=LocalAgentConfig(
model="gemini/gemini-2.0-flash", # Use Gemini with litellm routing prefix
system="You are a helpful coding assistant. Be concise.",
name="LocalGeminiAgent",
tools=["execute_command", "read_file", "write_file"],
),
)
agent = Agent(name="local-gemini", backend=local)
# 1. Basic execution - agent loop runs locally, LLM calls go to Gemini
print("[1] Local execution with Gemini LLM...")
result = agent.start("What is the capital of France? One word.", stream=True)
print(f" Result: {result}")
# 2. Agent metadata (locally generated UUIDs)
print(f"\n[2] Agent ID: {local.agent_id}")
print(f" Version: {local.agent_version}")
print(f" Env ID: {local.environment_id}")
print(f" Session: {local.session_id}")
# 3. Multi-turn conversation (session state maintained locally)
print("\n[3] Multi-turn conversation...")
result = agent.start("What country is that city in?", stream=True)
print(f" Result: {result}")
# 4. Usage tracking (accumulated locally)
info = local.retrieve_session()
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# 5. Tool execution (runs in local subprocess)
print("\n[5] Tool execution example...")
result = agent.start("Create a file called hello_gemini.txt with 'Hello from Gemini agent!'", stream=True)
print(f" Tool result: {result}")
print("\nDone! Agent loop ran locally with Gemini LLM calls.")
@@ -1,61 +0,0 @@
"""Local agent loop with Ollama LLM — runs locally, not in a managed runtime.
Uses the new canonical LocalAgent class which clearly communicates that only the
agent loop runs locally. The LLM calls go to a local Ollama instance, no managed runtime involved.
"""
import os
import sys
import requests
# Skip guards - exit cleanly if prerequisites not met
# Check if Ollama daemon is running
try:
response = requests.get("http://localhost:11434/api/tags", timeout=2)
if response.status_code != 200:
raise Exception("Ollama not responding")
except Exception:
print("[skip] Ollama daemon not running at localhost:11434")
sys.exit(0)
# Heavy imports only after skip-guards pass
from praisonai import Agent
from praisonai.integrations import LocalAgent, LocalAgentConfig
# Create a local agent using Ollama LLM
local = LocalAgent(
config=LocalAgentConfig(
model="ollama/llama3.2", # Use Ollama with litellm routing prefix
system="You are a helpful coding assistant. Be concise.",
name="LocalOllamaAgent",
tools=["execute_command", "read_file", "write_file"],
),
)
agent = Agent(name="local-ollama", backend=local)
# 1. Basic execution - agent loop runs locally, LLM calls go to local Ollama
print("[1] Local execution with Ollama LLM...")
result = agent.start("What is the capital of France? One word.", stream=True)
print(f" Result: {result}")
# 2. Agent metadata (locally generated UUIDs)
print(f"\n[2] Agent ID: {local.agent_id}")
print(f" Version: {local.agent_version}")
print(f" Env ID: {local.environment_id}")
print(f" Session: {local.session_id}")
# 3. Multi-turn conversation (session state maintained locally)
print("\n[3] Multi-turn conversation...")
result = agent.start("What country is that city in?", stream=True)
print(f" Result: {result}")
# 4. Usage tracking (accumulated locally)
info = local.retrieve_session()
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# 5. Tool execution (runs in local subprocess)
print("\n[5] Tool execution example...")
result = agent.start("Create a file called hello_ollama.txt with 'Hello from Ollama agent!'", stream=True)
print(f" Tool result: {result}")
print("\nDone! Agent loop ran locally with Ollama LLM calls.")
@@ -1,61 +0,0 @@
"""Local agent loop with OpenAI LLM — runs locally, not in a managed runtime.
Uses the new canonical LocalAgent class which clearly communicates that only the
agent loop runs locally. The LLM calls go to OpenAI, but there's no managed runtime involved.
"""
import os
import sys
# Skip guards - exit cleanly if prerequisites not met
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set")
sys.exit(0)
try:
import openai
except ImportError:
print("[skip] openai SDK not installed")
sys.exit(0)
# Heavy imports only after skip-guards pass
from praisonai import Agent
from praisonai.integrations import LocalAgent, LocalAgentConfig
# Create a local agent using OpenAI's LLM
local = LocalAgent(
config=LocalAgentConfig(
model="gpt-4o-mini",
system="You are a helpful coding assistant. Be concise.",
name="LocalOpenAIAgent",
tools=["execute_command", "read_file", "write_file"],
),
)
agent = Agent(name="local-openai", backend=local)
# 1. Basic execution - agent loop runs locally, LLM calls go to OpenAI
print("[1] Local execution with OpenAI LLM...")
result = agent.start("What is the capital of France? One word.", stream=True)
print(f" Result: {result}")
# 2. Agent metadata (locally generated UUIDs)
print(f"\n[2] Agent ID: {local.agent_id}")
print(f" Version: {local.agent_version}")
print(f" Env ID: {local.environment_id}")
print(f" Session: {local.session_id}")
# 3. Multi-turn conversation (session state maintained locally)
print("\n[3] Multi-turn conversation...")
result = agent.start("What country is that city in?", stream=True)
print(f" Result: {result}")
# 4. Usage tracking (accumulated locally)
info = local.retrieve_session()
print(f"\n[4] Usage: in={info['usage']['input_tokens']}, out={info['usage']['output_tokens']}")
# 5. Tool execution (runs in local subprocess)
print("\n[5] Tool execution example...")
result = agent.start("Create a file called hello.txt with 'Hello from local agent!'", stream=True)
print(f" Tool result: {result}")
print("\nDone! Agent loop ran locally with OpenAI LLM calls.")
@@ -1,61 +0,0 @@
"""Agent runtime with tools sandboxed in Modal cloud functions.
The agent's LLM loop runs locally; tool / code execution is sandboxed in
Modal's serverless cloud. Modal gives fast cold-starts and per-second billing.
Requires:
- ``OPENAI_API_KEY`` for the LLM
- Modal CLI configured: ``modal token set --token-id ... --token-secret ...``
- ``pip install modal``
"""
import asyncio
import os
if not os.getenv("OPENAI_API_KEY"):
print("[skip] OPENAI_API_KEY not set.")
raise SystemExit(0)
try:
import modal # noqa: F401
except ImportError:
print("[skip] 'modal' package not installed — pip install modal")
raise SystemExit(0) from None
# Modal auth: presence of ~/.modal.toml or MODAL_TOKEN_ID + MODAL_TOKEN_SECRET
if not (os.path.exists(os.path.expanduser("~/.modal.toml"))
or (os.getenv("MODAL_TOKEN_ID") and os.getenv("MODAL_TOKEN_SECRET"))):
print("[skip] Modal not configured — run `modal token set ...`")
raise SystemExit(0)
try:
from praisonai import Agent
from praisonai.integrations import SandboxedAgent, SandboxedAgentConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
async def main():
sandboxed = SandboxedAgent(
compute="modal", # tool execution runs in Modal's cloud
config=SandboxedAgentConfig(
model="gpt-4o-mini",
system="You are a concise coding assistant.",
name="ModalRuntimeAgent",
),
)
agent = Agent(name="modal-runtime", backend=sandboxed)
info = await sandboxed.provision_compute(idle_timeout_s=120)
print(f"[1] Modal instance: {info.instance_id} ({info.status})")
result = await sandboxed.execute_in_compute("python3 -c 'import math; print(math.factorial(10))'")
print(f"[2] Cloud compute result: {result['stdout'].strip()}")
print("[3] Agent:", agent.start("What is the 10th Fibonacci number? Just the number."))
await sandboxed.shutdown_compute()
print("[4] Modal instance shut down.")
asyncio.run(main())
@@ -1,45 +0,0 @@
"""Ollama managed runtime — agent runs against a local/self-hosted LLM.
``ManagedAgent(provider="ollama")`` wires the managed loop to an Ollama
server. Model runs locally (or on a self-hosted box) instead of in a public
cloud — useful when you want the "managed agent" developer experience with
no external API dependency.
Requires: An Ollama server reachable (default ``http://localhost:11434``)
with at least one model pulled (e.g., ``ollama pull llama3.2``).
"""
import os
import urllib.error
import urllib.request
base = os.getenv("OLLAMA_HOST", "http://localhost:11434")
try:
urllib.request.urlopen(f"{base}/api/tags", timeout=2).read()
except (urllib.error.URLError, OSError) as e:
print(f"[skip] Ollama not reachable at {base} ({e}). Start it with `ollama serve`.")
raise SystemExit(0)
try:
from praisonai import Agent, ManagedAgent, LocalManagedConfig
except ImportError as e:
print(f"[skip] praisonai wrapper not importable ({e}).")
raise SystemExit(0) from None
managed = ManagedAgent(
provider="ollama",
config=LocalManagedConfig(
model="llama3.2",
system="You are a concise assistant. One-sentence answers.",
name="OllamaRuntimeAgent",
),
)
agent = Agent(name="ollama-runtime", backend=managed)
# 1. Agent work (LLM call goes to your Ollama server)
print("[1] Agent:", agent.start("What is the capital of Japan? One word."))
# 2. Multi-turn reuses the session
print("[2] Agent:", agent.start("Name its most famous mountain."))
info = managed.retrieve_session()
print(f"[3] session={managed.session_id}")
Loaded 100 of 841 files, more files were not shown because too many files have changed in this diff. Show more