mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 01:08:39 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe87b9514e | ||
|
|
fec0b5fa32 | ||
|
|
c8a3881dda |
No files matched your search
@@ -1,51 +0,0 @@
|
||||
# PraisonAI Environment Variables
|
||||
# Copy this file to .env and update with your actual values
|
||||
|
||||
# === Required ===
|
||||
OPENAI_API_KEY=sk-... # OpenAI key for LLM calls. Get yours at https://platform.openai.com
|
||||
|
||||
# === Required for Claw web-search tool ===
|
||||
TAVILY_API_KEY=tvly-... # Get yours at https://app.tavily.com (free tier available)
|
||||
|
||||
# === Optional: Claw dashboard security ===
|
||||
PRAISONAI_ALLOW_LOCAL_TOOLS=0 # Set to 1 to enable local shell tools (security risk)
|
||||
|
||||
# === Optional: Alternative LLM Providers ===
|
||||
ANTHROPIC_API_KEY=sk-ant-... # Anthropic Claude models
|
||||
GOOGLE_API_KEY=... # Google Gemini models
|
||||
GROQ_API_KEY=gsk_... # Groq (fast inference)
|
||||
COHERE_API_KEY=... # Cohere models
|
||||
AZURE_OPENAI_API_KEY=... # Azure OpenAI
|
||||
AZURE_OPENAI_ENDPOINT=... # Azure OpenAI endpoint
|
||||
|
||||
# === Optional: Memory backends ===
|
||||
MEM0_API_KEY=... # Mem0 memory service
|
||||
REDIS_URL=redis://localhost:6379 # Redis for state management
|
||||
|
||||
# === Optional: Observability ===
|
||||
LANGFUSE_SECRET_KEY=sk-lf-... # Langfuse tracing
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-... # Langfuse public key
|
||||
|
||||
# === Optional: Bot platforms ===
|
||||
TELEGRAM_BOT_TOKEN=... # Telegram bot token
|
||||
DISCORD_BOT_TOKEN=... # Discord bot token
|
||||
SLACK_BOT_TOKEN=xoxb-... # Slack bot token
|
||||
SLACK_APP_TOKEN=xapp-... # Slack app token
|
||||
WHATSAPP_ACCESS_TOKEN=... # WhatsApp bot access token
|
||||
WHATSAPP_PHONE_NUMBER_ID=... # WhatsApp phone number ID
|
||||
|
||||
# === Optional: Database ===
|
||||
DATABASE_URL=sqlite:///~/.praison/database.sqlite # Database connection string
|
||||
|
||||
# === Optional: Tool Security ===
|
||||
# ALLOWED_TOOLS=search,send_message # Whitelist specific tools to prevent name collisions
|
||||
# Useful in multi-environment setups with overlapping tool names
|
||||
# Leave unset to use all tools (with collision warnings)
|
||||
# Do NOT set to empty string: that's treated as configuration error
|
||||
# In CI, unknown tool names in the whitelist cause strict startup failure
|
||||
# HERMES_ONLY_TOOLS=search,send_message # (Deprecated) Backward compatibility alias for ALLOWED_TOOLS
|
||||
|
||||
# === Optional: Development ===
|
||||
LOGLEVEL=INFO # Logging level (DEBUG, INFO, WARNING, ERROR)
|
||||
CHAINLIT_AUTH_SECRET=... # Chainlit authentication secret
|
||||
DEBUG=false # Enable debug mode
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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".
|
||||
@@ -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]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
@@ -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
-203
@@ -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,139 +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
|
||||
env:
|
||||
PR_BRANCH: ${{ steps.check_fork.outputs.pr_branch }}
|
||||
IS_FORK: ${{ steps.check_fork.outputs.is_fork }}
|
||||
run: |
|
||||
# Fetch PR head from base repo and put it into local branch
|
||||
git fetch origin pull/${{ github.event.issue.number }}/head:"${PR_BRANCH}"
|
||||
|
||||
# If it's a fork, make origin point to local to trick claude-code-action's `git fetch origin <branch>`
|
||||
if [ "${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:*)
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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,158 +0,0 @@
|
||||
name: Cross-Repo Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'src/praisonai/praisonai/integration/**'
|
||||
- 'src/praisonai/tests/integration/test_aiui_*'
|
||||
- 'src/praisonai/pyproject.toml'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'src/praisonai/praisonai/integration/**'
|
||||
- 'src/praisonai/tests/integration/test_aiui_*'
|
||||
- 'src/praisonai/pyproject.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cross-repo-integration:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout PraisonAI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: PraisonAI
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Checkout PraisonAIUI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: MervinPraison/PraisonAIUI
|
||||
path: PraisonAIUI
|
||||
ref: main
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
# Install PraisonAI packages in development mode
|
||||
cd PraisonAI/src/praisonai-agents
|
||||
pip install -e .
|
||||
cd ../praisonai
|
||||
pip install -e ".[ui,dev]"
|
||||
# Install PraisonAIUI in development mode
|
||||
cd ../../../PraisonAIUI
|
||||
pip install -e .
|
||||
|
||||
- name: Verify versions
|
||||
run: |
|
||||
python -c "import praisonaiagents; print(f'praisonaiagents: {praisonaiagents.__version__}')"
|
||||
python -c "import praisonai; print(f'praisonai: {praisonai.__version__}')"
|
||||
python -c "import praisonaiui; print(f'praisonaiui: {praisonaiui.__version__}')"
|
||||
|
||||
- name: Run PraisonAI integration tests
|
||||
run: |
|
||||
cd PraisonAI/src/praisonai
|
||||
python -m pytest tests/integration/test_aiui_* -v --timeout=300
|
||||
|
||||
- name: Run PraisonAIUI integration tests
|
||||
run: |
|
||||
cd PraisonAIUI
|
||||
if [ -f tests/integration/test_agentic_roundtrip.py ]; then
|
||||
python -m pytest tests/integration/test_agentic_roundtrip.py -v --timeout=300
|
||||
fi
|
||||
if [ -f tests/test_feature_sdk_backends.py ]; then
|
||||
python -m pytest tests/test_feature_sdk_backends.py -v --timeout=300
|
||||
fi
|
||||
|
||||
- name: Test Pattern C CLI
|
||||
run: |
|
||||
cd PraisonAI/src/praisonai
|
||||
# Test that ui-gateway command exists
|
||||
python -m praisonai serve --help | grep ui-gateway
|
||||
|
||||
- name: Test public API exports
|
||||
run: |
|
||||
cd PraisonAI/src/praisonai
|
||||
python -c "from praisonai import run_integrated_gateway; print('✓ run_integrated_gateway imported')"
|
||||
python -c "from praisonai import configure_host; print('✓ configure_host imported')"
|
||||
|
||||
- name: Integration smoke test
|
||||
env:
|
||||
PRAISONAI_TEST_MODE: "1"
|
||||
PYTHONPATH: ${{ github.workspace }}/PraisonAI/src/praisonai:${{ github.workspace }}/PraisonAI/src/praisonai-agents:${{ github.workspace }}/PraisonAIUI
|
||||
run: |
|
||||
cd PraisonAI/src/praisonai
|
||||
python -c "
|
||||
import sys
|
||||
sys.path.insert(0, '.')
|
||||
from praisonai.integration.host_app import configure_host
|
||||
from praisonai import run_integrated_gateway
|
||||
print('✓ Integration imports successful')
|
||||
|
||||
# Test configure_host with new parameters
|
||||
try:
|
||||
configure_host(
|
||||
style='dashboard',
|
||||
context_paths=['AGENTS.md'],
|
||||
title='Test App'
|
||||
)
|
||||
print('✓ configure_host with new parameters works')
|
||||
except Exception as e:
|
||||
print(f'× configure_host failed: {e}')
|
||||
sys.exit(1)
|
||||
"
|
||||
|
||||
# Optional job with API key for agentic tests (if secret is available)
|
||||
agentic-integration:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
if: ${{ vars.RUN_AGENTIC_TESTS == 'true' }}
|
||||
needs: cross-repo-integration
|
||||
|
||||
steps:
|
||||
- name: Checkout PraisonAI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: PraisonAI
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Checkout PraisonAIUI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: MervinPraison/PraisonAIUI
|
||||
path: PraisonAIUI
|
||||
ref: main
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
cd PraisonAI/src/praisonai-agents
|
||||
pip install -e .
|
||||
cd ../praisonai
|
||||
pip install -e ".[ui,dev]"
|
||||
cd ../../../PraisonAIUI
|
||||
pip install -e .
|
||||
|
||||
- name: Run agentic integration test
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
cd PraisonAI/src/praisonai
|
||||
if [ -n "$OPENAI_API_KEY" ]; then
|
||||
python -m pytest tests/integration/test_aiui_host_agentic.py -v --timeout=600
|
||||
else
|
||||
echo "OPENAI_API_KEY not available, skipping agentic tests"
|
||||
fi
|
||||
@@ -1,71 +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
|
||||
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"
|
||||
ISSUE_NUMBER_INPUT: ${{ inputs.issue_number }}
|
||||
ISSUE_NUMBER_EVENT: ${{ github.event.issue.number }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
export ISSUE_NUMBER="$ISSUE_NUMBER_INPUT"
|
||||
else
|
||||
export ISSUE_NUMBER="$ISSUE_NUMBER_EVENT"
|
||||
fi
|
||||
praisonai github triage --issue $ISSUE_NUMBER --agent-file .github/praisonai-issue-triage.yaml
|
||||
@@ -1,73 +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
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
PR_NUMBER_INPUT: ${{ inputs.pr_number }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "issue_comment" ]; then
|
||||
echo "ref=refs/pull/$ISSUE_NUMBER/head" >> "$GITHUB_OUTPUT"
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "ref=refs/pull/$PR_NUMBER_INPUT/head" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ref=$PR_HEAD_SHA" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
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
|
||||
@@ -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:
|
||||
|
||||
@@ -22,8 +22,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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:
|
||||
@@ -176,18 +175,6 @@ jobs:
|
||||
run: praisonai queue list
|
||||
continue-on-error: true
|
||||
|
||||
# ==================== PACKAGING TESTS ====================
|
||||
- name: Test packaging/daemon entry points
|
||||
run: |
|
||||
echo "Testing both entry point methods..."
|
||||
python -m praisonai --version
|
||||
praisonai --version
|
||||
|
||||
- name: Test doctor packaging command
|
||||
run: |
|
||||
echo "Testing doctor packaging diagnostics..."
|
||||
praisonai doctor packaging --json
|
||||
|
||||
# ==================== REAL API TESTS ====================
|
||||
- name: Real API test - Agent run
|
||||
if: env.OPENAI_API_KEY != ''
|
||||
|
||||
+1
-2
@@ -94,8 +94,7 @@ Cargo.lock
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
.windsurf
|
||||
.agent
|
||||
|
||||
src/praisonai/build*.egg-info/
|
||||
src/praisonai/PraisonAI.egg-info/
|
||||
jobs/*
|
||||
*.egg-info
|
||||
@@ -1,14 +0,0 @@
|
||||
# Contributing to PraisonAI
|
||||
|
||||
Thank you for your interest! Here's how to get started:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Submit a pull request
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Follow existing code style
|
||||
- Add tests for new features
|
||||
- Update documentation as needed
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
@@ -294,35 +340,8 @@ def search(query: str) -> str:
|
||||
|
||||
@tool
|
||||
def calculate(expression: str) -> float:
|
||||
"""Safely evaluate a numeric arithmetic expression."""
|
||||
import ast
|
||||
import operator
|
||||
|
||||
# Define allowed operations
|
||||
_OPS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.Pow: operator.pow,
|
||||
ast.USub: operator.neg,
|
||||
ast.UAdd: operator.pos,
|
||||
}
|
||||
|
||||
def _safe_eval(node):
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
||||
return node.value
|
||||
elif isinstance(node, ast.BinOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_safe_eval(node.left), _safe_eval(node.right))
|
||||
elif isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_safe_eval(node.operand))
|
||||
else:
|
||||
raise ValueError("Unsupported expression")
|
||||
|
||||
try:
|
||||
return _safe_eval(ast.parse(expression, mode="eval").body)
|
||||
except (ValueError, SyntaxError, TypeError, ZeroDivisionError, OverflowError):
|
||||
raise ValueError("Invalid arithmetic expression")
|
||||
"""Evaluate a math expression."""
|
||||
return eval(expression)
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant",
|
||||
@@ -331,7 +350,6 @@ agent = Agent(
|
||||
agent.start("Search for AI news and calculate 15*4")
|
||||
```
|
||||
|
||||
> ⚠️ **Security Note:** Never use `eval()`, `exec()`, or `subprocess` in tool functions that process LLM-generated or user-supplied input. Always validate and sanitize inputs to prevent code injection attacks.
|
||||
> 📖 [Full tools docs](https://docs.praison.ai/docs/tools/tools) — BaseTool, tool packages, 100+ built-in tools
|
||||
|
||||
### 5. Persistence (Databases)
|
||||
@@ -358,15 +376,6 @@ pip install "praisonai[claw]"
|
||||
praisonai claw
|
||||
```
|
||||
|
||||
#### Required Environment Variables
|
||||
|
||||
Copy `.env.example` to `.env` and configure the following variables:
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `OPENAI_API_KEY` | Yes | OpenAI API key for all LLM calls |
|
||||
| `TAVILY_API_KEY` | Yes (Claw) | Tavily key for the built-in web-search tool. Get one free at https://app.tavily.com |
|
||||
|
||||
Open **http://localhost:8082** — the dashboard comes with 13 built-in pages: Chat, Agents, Memory, Knowledge, Channels, Guardrails, Cron, and more. Add messaging channels directly from the UI.
|
||||
|
||||
> 📖 [Full Claw docs](https://docs.praison.ai/docs/concepts/claw) — platform tokens, CLI options, Docker, and YAML agent mode
|
||||
@@ -382,7 +391,7 @@ praisonai flow
|
||||
|
||||
Open **http://localhost:7861** — use the **Agent** and **Agent Team** components to create sequential or parallel workflows. Connect Chat Input → Agent Team → Chat Output for instant multi-agent pipelines.
|
||||
|
||||
> 📖 [Full Flow docs](https://docs.praison.ai/docs/concepts/agentflow) — visual agent building, component reference, and deployment
|
||||
> 📖 [Full Flow docs](https://docs.praison.ai/docs/concepts/flow) — visual agent building, component reference, and deployment
|
||||
|
||||
### 8. PraisonAI UI 🤖 (Clean Chat)
|
||||
|
||||
@@ -691,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) |
|
||||
@@ -766,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
|
||||
@@ -788,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:
|
||||
|
||||
@@ -15,7 +15,6 @@ Methods:
|
||||
* <code title="class GuardrailResult">GuardrailResult.<a href="./src/praisonai-agents/praisonaiagents/guardrails/guardrail_result.py">from_tuple</a>(result: Tuple[bool, Any]) -> 'GuardrailResult'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">from_dict</a>(data: Dict[str, Any]) -> 'HandoffConfig'</code>
|
||||
* <code title="class HandoffConfig">HandoffConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class HandoffResult">HandoffResult.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">from_outcome</a>(outcome: AgentRunOutcome, target_agent: Optional[str] = None, source_agent: Optional[str] = None, handoff_depth: int = 0) -> 'HandoffResult'</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">json</a>() -> Optional[str]</code>
|
||||
* <code title="class TaskOutput">TaskOutput.<a href="./src/praisonai-agents/praisonaiagents/main.py">to_dict</a>() -> dict</code>
|
||||
* <code title="class ToolResult">ToolResult.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
@@ -29,20 +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">clone_for_channel</a>() -> 'Agent'</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>
|
||||
@@ -58,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>
|
||||
@@ -76,7 +69,7 @@ Methods:
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_until</a>(prompt: str, criteria: str, threshold: float = 8.0, max_iterations: int = 5, mode: str = 'optimize', on_iteration: Optional[Callable[[Any], None]] = None, verbose: bool = False) -> 'EvaluationLoopResult'</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">run_until_async</a>(prompt: str, criteria: str, threshold: float = 8.0, max_iterations: int = 5, mode: str = 'optimize', on_iteration: Optional[Callable[[Any], None]] = None, verbose: bool = False) -> 'EvaluationLoopResult'</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">skill_manager</a>() -> Optional[Any]</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">store_memory</a>(content: str, memory_type: str = 'short_term', action: str = 'add', **kwargs: Any) -> None</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">store_memory</a>(content: str, memory_type: str = 'short_term', **kwargs: Any) -> None</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">stream_emitter</a>() -> Optional[Any]</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">stream_emitter</a>(value: Optional[Any]) -> None</code>
|
||||
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">thinking_budget</a>() -> Optional[int]</code>
|
||||
@@ -173,10 +166,7 @@ Methods:
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">safe_run</a>(**kwargs) -> ToolResult</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate</a>() -> bool</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_class</a>() -> bool</code>
|
||||
* <code title="class BaseTool">BaseTool.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_schema_roundtrip</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">get_schema</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">injected_params</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class FunctionTool">FunctionTool.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">run</a>(**kwargs) -> Any</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">clear</a>() -> None</code>
|
||||
@@ -184,18 +174,14 @@ 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">get_tool_definitions</a>() -> List[Dict[str, Any]]</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_trust_level</a>(name: str) -> Optional[str]</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, ttl_seconds: Optional[float] = 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">list_tools_with_allowed_filter</a>(context: Optional[Dict[str, Any]] = None) -> 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, trust_level: Optional[str] = None, dynamic_schema_overrides: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None) -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None, overwrite: bool = False) -> None</code>
|
||||
* <code title="class ToolRegistry">ToolRegistry.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">unregister</a>(name: str) -> bool</code>
|
||||
* <code title="class Tools">Tools.<a href="./src/praisonai-agents/praisonaiagents/tools/tools.py">internet_search</a>(*args, **kwargs)</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_registry</a>() -> ToolRegistry</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">get_tool</a>(name: str) -> Optional[Union[BaseTool, Callable]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register_tool</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None, trust_level: Optional[str] = None, dynamic_schema_overrides: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None) -> None</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/registry.py">register_tool</a>(tool: Union[BaseTool, Callable], name: Optional[str] = None) -> None</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/decorator.py">tool</a>(func: Optional[Callable] = None) -> Union[FunctionTool, Callable[[Callable], FunctionTool]]</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/tools/base.py">validate_tool</a>(tool: Any) -> bool</code>
|
||||
|
||||
@@ -215,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>
|
||||
|
||||
@@ -357,7 +341,7 @@ Methods:
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">to_tool_function</a>(source_agent: 'Agent') -> Callable</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_description</a>() -> str</code>
|
||||
* <code title="class Handoff">Handoff.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">tool_name</a>() -> str</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">handoff</a>(agent: 'Agent', tool_name_override: Optional[str] = None, tool_description_override: Optional[str] = None, on_handoff: Optional[Callable] = None, input_type: Optional[type] = None, input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None, config: Optional[HandoffConfig] = None, context_policy: Optional[str] = None, timeout_seconds: Optional[float] = None, max_concurrent: Optional[int] = None, detect_cycles: Optional[bool] = None, max_depth: Optional[int] = None, tool_policy_mode: Optional[Literal['intersect', 'passthrough']] = None, blocked_tools: Optional[List[str]] = None) -> Handoff</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">handoff</a>(agent: 'Agent', tool_name_override: Optional[str] = None, tool_description_override: Optional[str] = None, on_handoff: Optional[Callable] = None, input_type: Optional[type] = None, input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None, config: Optional[HandoffConfig] = None, context_policy: Optional[str] = None, timeout_seconds: Optional[float] = None, max_concurrent: Optional[int] = None, detect_cycles: Optional[bool] = None, max_depth: Optional[int] = None) -> Handoff</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">compress_history</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">keep_last_n_messages</a>(n: int) -> Callable[[HandoffInputData], HandoffInputData]</code>
|
||||
* <code title="class handoff_filters">handoff_filters.<a href="./src/praisonai-agents/praisonaiagents/agent/handoff.py">remove_all_tools</a>(data: HandoffInputData) -> HandoffInputData</code>
|
||||
@@ -375,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
|
||||
|
||||
@@ -477,26 +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_available_skills_by_state</a>(state: SkillState) -> List[LoadedSkill]</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_skills_diagnostics</a>() -> Dict[str, ValidationResult]</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">validate_skill_capabilities</a>(skill_name: str, force_refresh: bool = False) -> ValidationResult</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>
|
||||
@@ -578,7 +547,7 @@ Methods:
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import FlowDisplay, track_workflow
|
||||
from praisonaiagents import FlowDisplay, obs, track_workflow
|
||||
```
|
||||
|
||||
Methods:
|
||||
@@ -611,13 +580,6 @@ Methods:
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">set_tool_budget</a>(tool_name: str, max_tokens: int, protected: bool = False) -> None</code>
|
||||
* <code title="class ContextManager">ContextManager.<a href="./src/praisonai-agents/praisonaiagents/context/manager.py">truncate_tool_output</a>(tool_name: str, output: str) -> str</code>
|
||||
|
||||
# UI
|
||||
|
||||
Types:
|
||||
```python
|
||||
from praisonaiagents import A2UI
|
||||
```
|
||||
|
||||
# Config
|
||||
|
||||
Types:
|
||||
@@ -630,7 +592,6 @@ Methods:
|
||||
* <code title="class AutonomyConfig">AutonomyConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/autonomy.py">effective_track_changes</a>() -> bool</code>
|
||||
* <code title="class AutonomyConfig">AutonomyConfig.<a href="./src/praisonai-agents/praisonaiagents/agent/autonomy.py">from_dict</a>(data: Dict[str, Any]) -> 'AutonomyConfig'</code>
|
||||
* <code title="class CachingConfig">CachingConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class ExecutionConfig">ExecutionConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">from_dict</a>(data: Dict[str, Any]) -> 'ExecutionConfig'</code>
|
||||
* <code title="class ExecutionConfig">ExecutionConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
* <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 HooksConfig">HooksConfig.<a href="./src/praisonai-agents/praisonaiagents/config/feature_configs.py">to_dict</a>() -> Dict[str, Any]</code>
|
||||
@@ -723,19 +684,14 @@ Methods:
|
||||
* <code title="class AgentFlow">AgentFlow.<a href="./src/praisonai-agents/praisonaiagents/workflows/workflows.py">verbose</a>(value: bool)</code>
|
||||
* <code title="class AgentOSProtocol">AgentOSProtocol.<a href="./src/praisonai-agents/praisonaiagents/app/protocols.py">get_app</a>() -> Any</code>
|
||||
* <code title="class AgentOSProtocol">AgentOSProtocol.<a href="./src/praisonai-agents/praisonaiagents/app/protocols.py">serve</a>(host: Optional[str] = None, port: Optional[int] = None, reload: bool = False, **kwargs: Any) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aannounce_completion</a>(agent_id: str, task_id: str, result: Any, success: bool = True, error: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">add_task</a>(task)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aexecute_task</a>(task_id)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">announce_completion</a>(agent_id: str, task_id: str, result: Any, success: bool = True, error: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">append_to_state</a>(key: str, value: Any, max_length: Optional[int] = None) -> List[Any]</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_all_tasks</a>()</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">arun_task</a>(task_id)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">aspawn_sub_agent</a>(agent: Agent, task: Any, completion_callback: Optional[Callable[[SubAgentCompletionEvent], Any]] = None, metadata: Optional[Dict[str, Any]] = None) -> SpawnedSubAgent</code>
|
||||
* <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">await_for_completions</a>(timeout: Optional[float] = None, agent_ids: Optional[List[str]] = None) -> List[SubAgentCompletionEvent]</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>
|
||||
@@ -747,7 +703,6 @@ Methods:
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_all_tasks_status</a>()</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_detailed_token_report</a>() -> Dict[str, Any]</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_plan_markdown</a>() -> str</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_spawned_agents</a>() -> List[SpawnedSubAgent]</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_state</a>(key: str, default: Any = None) -> Any</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_details</a>(task_id)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">get_task_result</a>(task_id)</code>
|
||||
@@ -764,12 +719,10 @@ Methods:
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_output_to_file</a>(task, task_output)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">save_session_state</a>(session_id: str, include_memory: bool = True) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">set_state</a>(key: str, value: Any) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">spawn_sub_agent</a>(agent: Agent, task: Any, completion_callback: Optional[Callable[[SubAgentCompletionEvent], Any]] = None, metadata: Optional[Dict[str, Any]] = None) -> SpawnedSubAgent</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">start</a>(content = None, return_dict = False, output = None, **kwargs)</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">todo_list</a>()</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_plan_step_status</a>(step_id: str, status: str) -> bool</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">update_state</a>(updates: Dict) -> None</code>
|
||||
* <code title="class AgentTeam">AgentTeam.<a href="./src/praisonai-agents/praisonaiagents/agents/agents.py">wait_for_completions</a>(timeout: Optional[float] = None, agent_ids: Optional[List[str]] = None) -> List[SubAgentCompletionEvent]</code>
|
||||
* <code title="class AutoApproveBackend">AutoApproveBackend.<a href="./src/praisonai-agents/praisonaiagents/approval/backends.py">request_approval</a>(request: ApprovalRequest) -> ApprovalDecision</code>
|
||||
* <code title="class AutoApproveBackend">AutoApproveBackend.<a href="./src/praisonai-agents/praisonaiagents/approval/backends.py">request_approval_sync</a>(request: ApprovalRequest) -> ApprovalDecision</code>
|
||||
* <code title="function">praisonaiagents.<a href="./src/praisonai-agents/praisonaiagents/embedding/embed.py">aembedding</a>(input: Union[str, List[str]], model: str = 'text-embedding-3-small', dimensions: Optional[int] = None, encoding_format: str = 'float', timeout: float = 600.0, api_key: Optional[str] = None, api_base: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, **kwargs) -> EmbeddingResult</code>
|
||||
@@ -780,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__, arun, run, run_integrated_gateway
|
||||
from praisonai import AgentApp, AgentOS, CloudProvider, Deploy, DeployConfig, DeployType, PraisonAI, __version__
|
||||
```
|
||||
|
||||
# CLI
|
||||
@@ -810,11 +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 list <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot purge <a href="./src/praisonai/praisonai/cli/commands/bot.py">--help</a></code>
|
||||
* <code title="cli">praisonai bot replay <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>
|
||||
@@ -855,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>
|
||||
@@ -882,10 +829,8 @@ Methods:
|
||||
* <code title="cli">praisonai doctor env <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor mcp <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor network <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor packaging <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor performance <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor selftest <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor skills <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor tools <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai doctor troubleshoot <a href="./src/praisonai/praisonai/cli/commands/doctor.py">--help</a></code>
|
||||
* <code title="cli">praisonai endpoints list <a href="./src/praisonai/praisonai/cli/commands/endpoints.py">--help</a></code>
|
||||
@@ -897,61 +842,26 @@ 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 stop <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>
|
||||
* <code title="cli">praisonai kanban block <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban boards <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban comment <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban complete <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban create <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban dispatch <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban link <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban list <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban move <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban reclaim <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban show <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai kanban unblock <a href="./src/praisonai/praisonai/cli/commands/kanban.py">--help</a></code>
|
||||
* <code title="cli">praisonai knowledge add <a href="./src/praisonai/praisonai/cli/commands/knowledge.py">--help</a></code>
|
||||
* <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>
|
||||
@@ -959,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>
|
||||
@@ -987,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>
|
||||
@@ -1014,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>
|
||||
@@ -1092,7 +975,6 @@ Methods:
|
||||
* <code title="cli">praisonai serve status <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve stop <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve ui <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve ui-gateway <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai serve unified <a href="./src/praisonai/praisonai/cli/commands/serve.py">--help</a></code>
|
||||
* <code title="cli">praisonai session delete <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
* <code title="cli">praisonai session export <a href="./src/praisonai/praisonai/cli/commands/session.py">--help</a></code>
|
||||
@@ -1100,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>
|
||||
@@ -1143,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>
|
||||
|
||||
@@ -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.55" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.6.55" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=4.6.55" \
|
||||
"praisonai>=4.5.112" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
@@ -12,13 +12,8 @@ from praisonaiagents import Agent
|
||||
from praisonaiagents.approval import AgentApproval
|
||||
from praisonaiagents.tools.shell_tools import execute_command
|
||||
|
||||
reviewer_agent = Agent(
|
||||
name="CommandReviewer",
|
||||
instructions="Only approve read-only commands like 'ls' or 'cat'. Deny destructive commands.",
|
||||
)
|
||||
|
||||
reviewer = AgentApproval(
|
||||
approver_agent=reviewer_agent,
|
||||
instructions="Only approve read-only commands like 'ls' or 'cat'. Deny destructive commands.",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
|
||||
@@ -35,7 +35,7 @@ def run_quick_comparison():
|
||||
prompt="What is 2+2?",
|
||||
iterations=2,
|
||||
paths=["openai_sdk", "praisonai_agent"],
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Print comparison table
|
||||
@@ -56,7 +56,7 @@ def run_full_benchmark():
|
||||
report = handler.run_full_benchmark(
|
||||
prompt="Hi",
|
||||
iterations=3,
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Print full report
|
||||
@@ -78,7 +78,7 @@ def run_agent_benchmark():
|
||||
prompt="Explain Python in one sentence",
|
||||
iterations=3,
|
||||
paths=["openai_sdk", "praisonai_agent"],
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Show timeline diagrams
|
||||
|
||||
@@ -22,7 +22,7 @@ custom_modes = {
|
||||
}
|
||||
|
||||
# Create handler with verbose output
|
||||
handler = CompareHandler(verbose=True)
|
||||
handler = CompareHandler(output="verbose")
|
||||
|
||||
# Example: Manually create comparison results for analysis
|
||||
# (useful for testing without API calls)
|
||||
|
||||
@@ -22,7 +22,7 @@ modes = parse_modes("basic,tools,planning")
|
||||
print(f"\nParsed modes: {modes}")
|
||||
|
||||
# Create compare handler
|
||||
handler = CompareHandler(verbose=True)
|
||||
handler = CompareHandler(output="verbose")
|
||||
|
||||
# Compare modes (this will run actual agent calls)
|
||||
# Uncomment to run:
|
||||
|
||||
@@ -15,7 +15,7 @@ agent = Agent(
|
||||
# With array [preset, overrides]
|
||||
agent_override = Agent(
|
||||
instructions="You are a helpful assistant.",
|
||||
memory=MemoryConfig(backend="redis", user_id="custom_user"),
|
||||
memory=["redis", {"user_id": "custom_user"}],
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents import AgentFlow, Task
|
||||
from praisonaiagents.workflows.workflow_configs import (
|
||||
WorkflowPlanningConfig, WorkflowMemoryConfig,
|
||||
WorkflowOutputConfig, WorkflowPlanningConfig, WorkflowMemoryConfig,
|
||||
)
|
||||
|
||||
# Full-featured workflow with all consolidated params
|
||||
@@ -32,7 +32,7 @@ workflow = AgentFlow(
|
||||
),
|
||||
],
|
||||
# Workflow-level consolidated params
|
||||
output="verbose",
|
||||
output=WorkflowOutputConfig(output="verbose", stream=True),
|
||||
planning=WorkflowPlanningConfig(enabled=True, reasoning=True),
|
||||
memory=WorkflowMemoryConfig(backend="file"),
|
||||
# Agent-like params
|
||||
|
||||
@@ -10,7 +10,7 @@ from praisonaiagents import Agent
|
||||
# Basic: Enable web search with preset
|
||||
agent = Agent(
|
||||
instructions="You are a research assistant with web access.",
|
||||
web="search_only", # Compatible across models without web_fetch
|
||||
web="duckduckgo", # Presets: duckduckgo, tavily, google, bing, serper
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -15,20 +15,8 @@ workflow = AgentFlow(
|
||||
],
|
||||
output="verbose",
|
||||
planning=True,
|
||||
variables={"topic": "AI agents"},
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
import sys
|
||||
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
print("Set OPENAI_API_KEY to run this example.")
|
||||
sys.exit(0)
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
result = workflow.run("AI agents")
|
||||
result = workflow.run(variables={"topic": "AI agents"})
|
||||
print(result)
|
||||
@@ -24,7 +24,7 @@ if has_api_key:
|
||||
input_text="What is 2 + 2?",
|
||||
expected_output="4",
|
||||
num_iterations=3, # Run 3 times for statistical significance
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Run evaluation
|
||||
@@ -42,7 +42,7 @@ else:
|
||||
input_text="What is 2 + 2?",
|
||||
expected_output="4",
|
||||
num_iterations=1,
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Test with mock output
|
||||
|
||||
@@ -27,7 +27,7 @@ if has_api_key:
|
||||
scoring_type="numeric", # Score 1-10
|
||||
threshold=7.0, # Pass if score >= 7
|
||||
num_iterations=2,
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Run evaluation
|
||||
@@ -45,7 +45,7 @@ if has_api_key:
|
||||
agent=agent,
|
||||
input_text="Tell me a joke",
|
||||
scoring_type="binary", # Pass or Fail
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
binary_result = binary_evaluator.run(print_summary=True)
|
||||
|
||||
@@ -26,7 +26,7 @@ if has_api_key:
|
||||
num_iterations=10, # Run 10 benchmark iterations
|
||||
warmup_runs=2, # 2 warmup runs before measurement
|
||||
track_memory=True, # Track memory usage
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Run evaluation
|
||||
|
||||
@@ -16,19 +16,8 @@ def search_web(query: str) -> str:
|
||||
|
||||
|
||||
def calculate(expression: str) -> str:
|
||||
"""Calculate a math expression safely."""
|
||||
import ast, operator
|
||||
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv,
|
||||
ast.Mod: operator.mod, ast.Pow: operator.pow,
|
||||
ast.USub: operator.neg, ast.UAdd: operator.pos}
|
||||
def _ev(n):
|
||||
if isinstance(n, ast.Expression): return _ev(n.body)
|
||||
if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
|
||||
if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
|
||||
if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
|
||||
raise ValueError(f"Unsupported: {ast.dump(n)}")
|
||||
return str(_ev(ast.parse(expression, mode="eval")))
|
||||
"""Calculate a math expression."""
|
||||
return str(eval(expression))
|
||||
|
||||
|
||||
# Check if we have an API key
|
||||
@@ -48,7 +37,7 @@ if has_api_key:
|
||||
input_text="Search for the weather in Paris and calculate 25 * 4",
|
||||
expected_tools=["search_web", "calculate"], # Tools that should be called
|
||||
forbidden_tools=["delete_file"], # Tools that should NOT be called
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
# Run evaluation
|
||||
@@ -70,7 +59,7 @@ evaluator = ReliabilityEvaluator(
|
||||
agent=mock_agent,
|
||||
expected_tools=["search_web", "calculate"], # Tools that should be called
|
||||
forbidden_tools=["delete_file"], # Tools that should NOT be called
|
||||
verbose=True
|
||||
output="verbose"
|
||||
)
|
||||
|
||||
result2 = evaluator.evaluate_tool_calls(
|
||||
|
||||
@@ -184,7 +184,7 @@ Answer questions based ONLY on the provided knowledge context.
|
||||
When asked about codes, provide the EXACT code from the documents.
|
||||
Be concise but accurate.""",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "compression_demo_user"},
|
||||
user_id="compression_demo_user",
|
||||
output="verbose",
|
||||
)
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ Be precise and quote exact values when asked about codes or numbers.""",
|
||||
print(f"\nAnswer: {response}\n")
|
||||
|
||||
# Verify the answer contains the unique code
|
||||
if "ZEBRA-71" in str(response or "").upper():
|
||||
if "ZEBRA-71" in response.upper():
|
||||
print("✅ VERIFIED: Agent correctly retrieved the unique code from context!\n")
|
||||
else:
|
||||
print("❌ WARNING: Agent did not find the code - check retrieval\n")
|
||||
@@ -104,7 +104,7 @@ Be precise and quote exact values when asked about codes or numbers.""",
|
||||
response = agent.chat("What is the security incident code?")
|
||||
print(f"\nAnswer: {response}\n")
|
||||
|
||||
if "HAWK-88" in str(response or "").upper():
|
||||
if "HAWK-88" in response.upper():
|
||||
print("✅ VERIFIED: Agent correctly retrieved the security code!\n")
|
||||
else:
|
||||
print("❌ WARNING: Agent did not find the code - check retrieval\n")
|
||||
|
||||
@@ -164,7 +164,7 @@ Answer questions based ONLY on the provided knowledge context.
|
||||
When asked about department codes, provide the EXACT code from the documents.
|
||||
Be specific about which department you're referring to.""",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "hierarchy_demo_user"},
|
||||
user_id="hierarchy_demo_user",
|
||||
output="verbose",
|
||||
)
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def main():
|
||||
# Show strategy selection for different corpus sizes
|
||||
print(f"\nStrategy selection by corpus size:")
|
||||
for size in [100, 1000, 10000, 50000, 100000]:
|
||||
strategy = select_strategy(CorpusStats(file_count=max(1, size // 1000), total_tokens=size))
|
||||
strategy = select_strategy(corpus_tokens=size)
|
||||
print(f" {size:>6} tokens -> {strategy.value}")
|
||||
|
||||
except ImportError as e:
|
||||
@@ -142,7 +142,7 @@ Answer questions based ONLY on the provided knowledge context.
|
||||
When asked about verification codes, provide the EXACT code from the documents.
|
||||
If information is not in the context, say 'Information not found in documents.'""",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "strategy_demo_user"},
|
||||
user_id="strategy_demo_user",
|
||||
output="verbose", # Use new consolidated param
|
||||
)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ def main():
|
||||
name="PolicyExpert",
|
||||
instructions="You are a company policy expert. Answer questions based ONLY on the provided knowledge. Quote exact values when asked about codes or numbers.",
|
||||
knowledge=[temp_dir], # Add directory with documents
|
||||
memory={"user_id": "example_user"},
|
||||
user_id="example_user", # Required for mem0 backend
|
||||
)
|
||||
|
||||
print("\n1. Testing Agent.chat() with context-required question:")
|
||||
|
||||
@@ -50,7 +50,7 @@ def main():
|
||||
name="HR_Assistant_User1",
|
||||
instructions="You are an HR assistant.",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "user_alice"},
|
||||
user_id="user_alice", # Knowledge scoped to Alice
|
||||
)
|
||||
|
||||
response = user1_agent.chat("What is the policy on remote work?")
|
||||
@@ -80,7 +80,7 @@ def main():
|
||||
name="Personal_HR_Bot",
|
||||
instructions="You are a personal HR assistant.",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "user_bob"},
|
||||
user_id="user_bob",
|
||||
agent_id="hr_bot_v2",
|
||||
)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ Only authorized personnel with code WRENCH-992 may access this project.
|
||||
name="AgentA_Ingester",
|
||||
instructions="You ingest and store knowledge.",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "user_alice"},
|
||||
user_id="user_alice", # Scoped to Alice
|
||||
output="silent",
|
||||
)
|
||||
|
||||
@@ -103,7 +103,7 @@ Only authorized personnel with code WRENCH-992 may access this project.
|
||||
name="AgentB_Querier",
|
||||
instructions="Answer questions based on the provided knowledge. If you don't have the information, say so.",
|
||||
knowledge=[temp_dir],
|
||||
memory={"user_id": "user_alice"},
|
||||
user_id="user_alice", # Same scope as Agent A
|
||||
output="silent",
|
||||
)
|
||||
|
||||
|
||||
@@ -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,27 +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.",
|
||||
memory={
|
||||
"session_id": "crdb-demo-session",
|
||||
"db": {"database_url": os.getenv("COCKROACHDB_URL")},
|
||||
},
|
||||
)
|
||||
|
||||
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,36 +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.",
|
||||
memory={
|
||||
"session_id": "neon-demo-session",
|
||||
"db": {"database_url": os.getenv("NEON_DATABASE_URL")},
|
||||
},
|
||||
)
|
||||
|
||||
# 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,30 +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.",
|
||||
memory={
|
||||
"session_id": "supabase-demo-session",
|
||||
"db": {"database_url": os.getenv("SUPABASE_DATABASE_URL")},
|
||||
},
|
||||
)
|
||||
|
||||
result = agent.start("What is Supabase? Explain in 2 sentences.")
|
||||
print(result)
|
||||
@@ -1,31 +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.",
|
||||
memory={
|
||||
"session_id": "turso-demo-session",
|
||||
"db": {
|
||||
"database_url": os.getenv("TURSO_DATABASE_URL"),
|
||||
"auth_token": os.getenv("TURSO_AUTH_TOKEN"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
result = agent.start("What is edge computing? Explain in 2 sentences.")
|
||||
print(result)
|
||||
@@ -19,43 +19,20 @@ import os
|
||||
|
||||
def calculate(expression: str) -> str:
|
||||
"""
|
||||
A simple calculator tool that evaluates mathematical expressions safely.
|
||||
|
||||
A simple calculator tool that evaluates mathematical expressions.
|
||||
|
||||
Args:
|
||||
expression: A mathematical expression to evaluate (e.g., "2 + 2")
|
||||
|
||||
|
||||
Returns:
|
||||
The result of the calculation
|
||||
"""
|
||||
import ast
|
||||
import operator
|
||||
|
||||
_OPS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.FloorDiv: operator.floordiv,
|
||||
ast.Mod: operator.mod,
|
||||
ast.Pow: operator.pow,
|
||||
ast.USub: operator.neg,
|
||||
ast.UAdd: operator.pos,
|
||||
}
|
||||
|
||||
def _safe_eval(node):
|
||||
if isinstance(node, ast.Expression):
|
||||
return _safe_eval(node.body)
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
||||
return node.value
|
||||
if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_safe_eval(node.operand))
|
||||
if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_safe_eval(node.left), _safe_eval(node.right))
|
||||
raise ValueError(f"Unsupported expression: {ast.dump(node)}")
|
||||
|
||||
try:
|
||||
tree = ast.parse(expression, mode="eval")
|
||||
result = _safe_eval(tree)
|
||||
# Safe evaluation of mathematical expressions
|
||||
allowed_chars = set("0123456789+-*/.() ")
|
||||
if not all(c in allowed_chars for c in expression):
|
||||
return "Error: Invalid characters in expression"
|
||||
result = eval(expression)
|
||||
return f"Result: {result}"
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
@@ -24,18 +24,12 @@ def main():
|
||||
def calculate(expression: str) -> str:
|
||||
"""Safely evaluate a mathematical expression."""
|
||||
try:
|
||||
import ast, operator
|
||||
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv,
|
||||
ast.Mod: operator.mod, ast.Pow: operator.pow,
|
||||
ast.USub: operator.neg, ast.UAdd: operator.pos}
|
||||
def _ev(n):
|
||||
if isinstance(n, ast.Expression): return _ev(n.body)
|
||||
if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
|
||||
if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
|
||||
if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
|
||||
raise ValueError(f"Unsupported: {ast.dump(n)}")
|
||||
return f"Result: {_ev(ast.parse(expression, mode='eval'))}"
|
||||
# Only allow safe math operations
|
||||
allowed = set("0123456789+-*/.(). ")
|
||||
if not all(c in allowed for c in expression):
|
||||
return "Error: Invalid characters in expression"
|
||||
result = eval(expression) # Safe due to character filtering
|
||||
return f"Result: {result}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from praisonaiagents import Agent
|
||||
agent = Agent(
|
||||
name="FlexBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
llm={"model": "gpt-4o-mini", "configurable": True},
|
||||
llm="gpt-4o-mini",
|
||||
llm_config={"configurable": True}
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -21,7 +21,7 @@ agent = Agent(
|
||||
name="ContextBot",
|
||||
instructions="You help show context information.",
|
||||
tools=[show_context],
|
||||
memory={"session_id": "my-session-123"},
|
||||
session_id="my-session-123"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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!")
|
||||
@@ -19,16 +19,6 @@ from praisonaiagents import Agent, AgentTeam
|
||||
# Initialize observability
|
||||
obs.init()
|
||||
|
||||
|
||||
def set_span_attr(span, key, value):
|
||||
"""Set span attribute safely across observability implementations."""
|
||||
if hasattr(span, "set_attribute"):
|
||||
span.set_attribute(key, value)
|
||||
return
|
||||
attrs = getattr(span, "attributes", None)
|
||||
if attrs is not None:
|
||||
attrs[key] = value
|
||||
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
@@ -47,13 +37,13 @@ with obs.trace("content-creation", metadata={"workflow": "research-write"}):
|
||||
|
||||
# Research phase
|
||||
with obs.span("research-phase", kind=SpanKind.AGENT) as research_span:
|
||||
set_span_attr(research_span, "agent", "Researcher")
|
||||
research_span.attributes["agent"] = "Researcher"
|
||||
research_result = researcher.chat("Research the benefits of AI in healthcare")
|
||||
|
||||
# Writing phase
|
||||
with obs.span("writing-phase", kind=SpanKind.AGENT) as write_span:
|
||||
set_span_attr(write_span, "agent", "Writer")
|
||||
set_span_attr(write_span, "input_length", len(str(research_result)))
|
||||
write_span.attributes["agent"] = "Writer"
|
||||
write_span.attributes["input_length"] = len(str(research_result))
|
||||
final_content = writer.chat(f"Write a blog post based on: {research_result}")
|
||||
|
||||
print("Final Content:")
|
||||
|
||||
@@ -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!")
|
||||
@@ -50,12 +50,10 @@ print("=== Qdrant Knowledge Store Demo (Agent-First) ===")
|
||||
agent = Agent(
|
||||
name="KnowledgeAssistant",
|
||||
instructions="You are a helpful assistant with access to technical documentation.",
|
||||
knowledge=[doc_path],
|
||||
knowledge={
|
||||
"sources": [doc_path],
|
||||
"vector_store": {
|
||||
"provider": "qdrant",
|
||||
"url": "http://localhost:6333",
|
||||
},
|
||||
"vector_store": "qdrant",
|
||||
"url": "http://localhost:6333"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ Expected output:
|
||||
- Session can be resumed later with same session_id
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database connection (PostgreSQL + Redis)
|
||||
my_db = db(
|
||||
@@ -26,12 +25,10 @@ my_db = db(
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Be concise.",
|
||||
memory={
|
||||
"db": my_db,
|
||||
# session_id is optional - defaults to per-hour ID (YYYYMMDDHH-hash)
|
||||
# Set explicitly for session continuity across runs:
|
||||
"session_id": "my-persistent-session",
|
||||
},
|
||||
db=my_db,
|
||||
# session_id is optional - defaults to per-hour ID (YYYYMMDDHH-hash)
|
||||
# Set explicitly for session continuity across runs:
|
||||
session_id="my-persistent-session"
|
||||
)
|
||||
|
||||
# Chat - messages are automatically persisted
|
||||
|
||||
@@ -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!")
|
||||
@@ -12,8 +12,7 @@ Expected output:
|
||||
- Export shows full session data
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
@@ -30,10 +29,8 @@ agent = Agent(
|
||||
name="WeatherBot",
|
||||
instructions="You help with weather. Use the get_weather tool when asked.",
|
||||
tools=[get_weather],
|
||||
memory={
|
||||
"db": my_db,
|
||||
"session_id": "weather-session-001",
|
||||
},
|
||||
db=my_db,
|
||||
session_id="weather-session-001"
|
||||
)
|
||||
|
||||
# First chat - triggers run tracking
|
||||
|
||||
@@ -13,8 +13,7 @@ Expected output:
|
||||
Agent responds with state persisted to Redis
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
print("=== Redis State Store (Agent-First) ===")
|
||||
|
||||
@@ -27,10 +26,8 @@ my_db = db(
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
memory={
|
||||
"db": my_db,
|
||||
"session_id": "redis-state-session",
|
||||
},
|
||||
db=my_db,
|
||||
session_id="redis-state-session"
|
||||
)
|
||||
|
||||
# Chat - state is automatically managed via Redis
|
||||
|
||||
@@ -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!")
|
||||
@@ -11,8 +11,7 @@ Expected output:
|
||||
- Second run: Resumes with history
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database
|
||||
my_db = db(
|
||||
@@ -26,10 +25,8 @@ SESSION_ID = "resume-demo-session"
|
||||
agent = Agent(
|
||||
name="MemoryBot",
|
||||
instructions="You remember our conversation. Be helpful and concise.",
|
||||
memory={
|
||||
"db": my_db,
|
||||
"session_id": SESSION_ID,
|
||||
},
|
||||
db=my_db,
|
||||
session_id=SESSION_ID
|
||||
)
|
||||
|
||||
# Check if this is a new or resumed session
|
||||
|
||||
@@ -26,8 +26,7 @@ Expected Output:
|
||||
Messages persisted: 2
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
|
||||
# Create database adapter (simplified import)
|
||||
db_instance = db(
|
||||
@@ -38,10 +37,8 @@ db_instance = db(
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful assistant. Keep responses brief.",
|
||||
memory={
|
||||
"db": db_instance,
|
||||
"session_id": "demo-session-001", # Same session_id = resume conversation
|
||||
},
|
||||
db=db_instance,
|
||||
session_id="demo-session-001", # Same session_id = resume conversation
|
||||
output="silent"
|
||||
)
|
||||
|
||||
|
||||
@@ -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!")
|
||||
@@ -11,8 +11,7 @@ Expected output:
|
||||
- Data persisted to local SQLite file
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.db import db
|
||||
from praisonaiagents import Agent, db
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
@@ -26,10 +25,8 @@ print(f"Database: {db_path}")
|
||||
agent = Agent(
|
||||
name="LocalBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
memory={
|
||||
"db": my_db,
|
||||
"session_id": "local-session",
|
||||
},
|
||||
db=my_db,
|
||||
session_id="local-session"
|
||||
)
|
||||
|
||||
# Chat
|
||||
|
||||
@@ -57,8 +57,8 @@ def main():
|
||||
agent = Agent(
|
||||
name="SecureAgent",
|
||||
instructions="You are a file management assistant.",
|
||||
policy=engine
|
||||
)
|
||||
agent.policy = engine
|
||||
|
||||
print("\n--- Agent with Policy Enforcement Created ---")
|
||||
print(f"Agent: {agent.name}")
|
||||
|
||||
@@ -21,36 +21,9 @@ def search_web(query: str) -> str:
|
||||
return f"Search results for: {query}"
|
||||
|
||||
def calculate(expression: str) -> str:
|
||||
"""Calculate a mathematical expression safely."""
|
||||
import ast
|
||||
import operator
|
||||
|
||||
_OPS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.FloorDiv: operator.floordiv,
|
||||
ast.Mod: operator.mod,
|
||||
ast.Pow: operator.pow,
|
||||
ast.USub: operator.neg,
|
||||
ast.UAdd: operator.pos,
|
||||
}
|
||||
|
||||
def _eval(node):
|
||||
if isinstance(node, ast.Expression):
|
||||
return _eval(node.body)
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
||||
return node.value
|
||||
if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_eval(node.operand))
|
||||
if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
|
||||
return _OPS[type(node.op)](_eval(node.left), _eval(node.right))
|
||||
raise ValueError(f"Unsupported expression: {ast.dump(node)}")
|
||||
|
||||
"""Calculate a mathematical expression."""
|
||||
try:
|
||||
tree = ast.parse(expression, mode="eval")
|
||||
return f"Result: {_eval(tree)}"
|
||||
return f"Result: {eval(expression)}"
|
||||
except Exception:
|
||||
return "Invalid expression"
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal A2UI example — agent + send_a2ui_messages tool.
|
||||
|
||||
Install:
|
||||
pip install praisonaiagents[a2ui]
|
||||
|
||||
Run:
|
||||
python send_surface.py
|
||||
|
||||
For live rendering, use PraisonAIUI with AG-UI (/agui) — see integrate-a2ui-frontend docs.
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
from praisonaiagents.tools.a2ui_tools import send_a2ui_messages
|
||||
|
||||
agent = Agent(
|
||||
name="ui-assistant",
|
||||
instructions=(
|
||||
"When asked for UI, call send_a2ui_messages with A2UI v0.9 JSON "
|
||||
"(createSurface on surface id 'main')."
|
||||
),
|
||||
tools=[send_a2ui_messages],
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = agent.start("Show a simple welcome card on surface main")
|
||||
print(result)
|
||||
@@ -60,10 +60,9 @@ agents_system = AgentTeam(
|
||||
process="sequential", output="verbose"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting hierarchical agent generation...")
|
||||
result = agents_system.start()
|
||||
print("Starting hierarchical agent generation...")
|
||||
result = agents_system.start()
|
||||
|
||||
print(f"\nHierarchical Result: {str(result)[:200]}...")
|
||||
print("\n✅ AutoAgents hierarchical generation complete!")
|
||||
print("Demonstrated coordinated hierarchical workflow between manager and specialist agents.")
|
||||
print(f"\nHierarchical Result: {result[:200]}...")
|
||||
print("\n✅ AutoAgents hierarchical generation complete!")
|
||||
print("Demonstrated coordinated hierarchical workflow between manager and specialist agents.")
|
||||
@@ -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")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
if __name__ == "__main__":
|
||||
agent = Agent(name="TweetAgent", instructions="Create a Tweet based on the topic provided")
|
||||
agent.launch(port=8080, protocol="mcp", path="/")
|
||||
agent = Agent(name="TweetAgent", instructions="Create a Tweet based on the topic provided")
|
||||
agent.launch(port=8080, protocol="mcp")
|
||||
@@ -90,18 +90,9 @@ def main():
|
||||
|
||||
if result:
|
||||
print("\n🔍 Analysis Results:")
|
||||
task_results = result.get("task_results") if isinstance(result, dict) else result
|
||||
if isinstance(task_results, dict):
|
||||
iterable = task_results.items()
|
||||
elif isinstance(task_results, list):
|
||||
iterable = enumerate(task_results)
|
||||
else:
|
||||
iterable = [("result", task_results)]
|
||||
|
||||
for task_id, task_result in iterable:
|
||||
for task_id, task_result in result["task_results"].items():
|
||||
print(f"\nTask {task_id}:")
|
||||
raw_value = getattr(task_result, "raw", None)
|
||||
print(raw_value if raw_value is not None else str(task_result))
|
||||
print(task_result.raw)
|
||||
else:
|
||||
print("❌ Failed to capture and analyze camera feed")
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class ContinuousCameraMonitor:
|
||||
agents=[self.vision_agent],
|
||||
tasks=[task],
|
||||
process="sequential",
|
||||
output="silent", # Reduced verbosity for continuous monitoring
|
||||
verbose=0 # Reduced verbosity for continuous monitoring
|
||||
)
|
||||
|
||||
return agents.start()
|
||||
|
||||
@@ -148,19 +148,10 @@ def main():
|
||||
"scene_analysis": "🌍 SCENE ANALYSIS"
|
||||
}
|
||||
|
||||
task_results = result.get("task_results") if isinstance(result, dict) else result
|
||||
if isinstance(task_results, dict):
|
||||
iterable = task_results.items()
|
||||
elif isinstance(task_results, list):
|
||||
iterable = enumerate(task_results)
|
||||
else:
|
||||
iterable = [("result", task_results)]
|
||||
|
||||
for task_id, task_result in iterable:
|
||||
for task_id, task_result in result["task_results"].items():
|
||||
print(f"\n{agent_names.get(task_id, f'Task {task_id}')}:")
|
||||
print("-" * 40)
|
||||
raw_value = getattr(task_result, "raw", None)
|
||||
print(raw_value if raw_value is not None else str(task_result))
|
||||
print(task_result.raw)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ Multi-agent analysis complete!")
|
||||
|
||||
@@ -34,5 +34,5 @@ print(f"\nChanged to: {handler.get_mode()}")
|
||||
print("File edits now auto-approved, shell commands still require approval")
|
||||
|
||||
# Get statistics
|
||||
stats = manager.get_stats()
|
||||
stats = handler.get_stats()
|
||||
print(f"\nStats: {stats}")
|
||||
@@ -31,4 +31,4 @@ print(f"Total cost: ${summary['total_cost']:.4f}")
|
||||
print(f"Avg cost/request: ${summary['avg_cost_per_request']:.4f}")
|
||||
|
||||
# Display formatted (uses Rich if available)
|
||||
handler.print_summary()
|
||||
handler.display_summary()
|
||||
@@ -8,7 +8,7 @@ Docs: https://docs.praison.ai/cli/git-integration
|
||||
from praisonai.cli.features import GitIntegrationHandler
|
||||
|
||||
# Initialize
|
||||
handler = GitIntegrationHandler(verbose=True)
|
||||
handler = GitIntegrationHandler(output="verbose")
|
||||
git = handler.initialize(repo_path=".")
|
||||
|
||||
# Check if it's a git repo
|
||||
|
||||
Loaded 100 of 1269 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user