Compare commits

...
64 Commits
Author SHA1 Message Date
Mervin Praison 89f48cf7a2 Merge pull request #553 from MervinPraison/develop
chore: update PraisonAI version to 2.2.20 across Dockerfiles and docu…
2025-05-30 14:16:47 +01:00
MervinPraison 97fb7fdd52 chore: update PraisonAI version to 2.2.20 across Dockerfiles and documentation
- Updated the version of PraisonAI from 2.2.19 to 2.2.20 in all relevant Dockerfiles.
- Adjusted README and local development documentation to reflect the new version.
- Ensured consistency in versioning across all instances where PraisonAI is referenced.
2025-05-30 14:16:19 +01:00
Mervin Praison ea4cb42324 Merge pull request #552 from MervinPraison/claude/issue-115-20250530_125352
fix: resolve ForwardRef TypeError for praisonai code command
2025-05-30 14:14:11 +01:00
Mervin Praison 71c5b929b7 Merge pull request #551 from MervinPraison/develop
Develop
2025-05-30 13:57:04 +01:00
claude[bot]andMervinPraison c9edf19907 fix: resolve ForwardRef TypeError for praisonai code command
Fixes #115

Resolves TypeError: ForwardRef._evaluate() missing recursive_guard argument when running `praisonai code`.

## Changes
- Moved crewai import from module level to conditional import in handle_direct_prompt
- The `code` command doesn't need crewai, only chainlit and other UI dependencies
- Follows existing pattern for optional dependencies

## Testing
- `praisonai code` now works without requiring crewai installation
- Direct prompt functionality still works when crewai is available

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 12:56:57 +00:00
MervinPraison 6cdc79f4a2 fix: update import path for database_config module in unit tests
- Adjusted the import statement in test_database_config.py to reference the correct module path.
- Ensures that the tests can locate the database_config functionalities properly.
2025-05-30 13:56:49 +01:00
MervinPraison fdda350b72 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 13:33:56 +01:00
Mervin Praison 85f748d5d1 Merge pull request #549 from DhivyaBharathy-web/add-langchain-praisonai-agents-notebook
Add LangChain + PraisonAI agents Colab notebook
2025-05-30 13:33:50 +01:00
MervinPraison bf24548598 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 13:33:24 +01:00
Mervin Praison a1622a2a79 Merge pull request #550 from MervinPraison/claude/issue-17-20250530_113738
feat: add piped input support to CLI
2025-05-30 13:32:57 +01:00
DhivyaBharathy-web 209faff03c Add LangChain + PraisonAI agents Colab notebook 2025-05-30 11:49:03 +00:00
Mervin Praison ad67089813 Merge pull request #548 from MervinPraison/claude/issue-201-20250530_113510
fix: move OpenAI API key validation from import-time to runtime
2025-05-30 12:41:49 +01:00
claude[bot]andMervinPraison 3e44eadd37 feat: add piped input support to CLI
Implement piped command functionality similar to fabric project, allowing:
- echo "content" | praisonai "command"
- cat file.txt | praisonai --auto "task"
- echo "question" | praisonai

Added read_stdin_if_available() method to detect and read piped input,
and modified main() to handle piped content in three scenarios:
1. Command + piped input
2. Direct prompt + piped input  
3. Only piped input

Fixes #17

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:39:42 +00:00
claude[bot]andMervinPraison 764e9aa838 fix: move OpenAI API key validation from import-time to runtime in call.py
- Removes import-time API key requirement that prevented GUI startup
- Moves validation to run_server() function where it's actually needed
- Allows PraisonAI to start without OpenAI API key for non-voice features
- Users can now configure API key through existing GUI interfaces
- Follows existing codebase patterns for conditional feature validation

Fixes #201

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:39:28 +00:00
Mervin Praison ccfc92564e Merge pull request #547 from MervinPraison/claude/issue-140-20250529_041935
fix: add fallback mechanism for conda run failure in training
2025-05-30 12:33:06 +01:00
MervinPraison 1f61000922 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:30:26 +01:00
Mervin Praison 2164f3e658 Merge pull request #546 from MervinPraison/claude/issue-190-20250530_111525
fix: add console scripts to project.scripts section for pip compatibility
2025-05-30 12:30:18 +01:00
MervinPraison 739a9ff8b5 docker 2025-05-30 12:28:49 +01:00
MervinPraison dbe63cbbc9 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:27:33 +01:00
Mervin Praison f4bff11e83 Merge pull request #545 from MervinPraison/claude/issue-130-20250530_105428
feat: implement comprehensive Docker support and package publishing for issue #130
2025-05-30 12:27:23 +01:00
Mervin Praison 6552f61322 Update __init__.py 2025-05-30 12:26:57 +01:00
Mervin Praison d5d898ff1b Update __init__.py 2025-05-30 12:26:15 +01:00
Mervin Praison 4df8d122fc Update __init__.py 2025-05-30 12:25:51 +01:00
Mervin Praison 766e5789e3 Update __init__.py 2025-05-30 12:25:14 +01:00
Mervin Praison f9585e83cb Delete src/praisonai/praisonai/inc/directory_manager.py 2025-05-30 12:24:57 +01:00
claude[bot]andMervinPraison e019bab987 fix: address critical security and implementation issues
- Remove automatic directory initialization on package import to prevent unexpected file operations
- Add DOCKER_CONTAINER=true environment variable to all Dockerfiles
- Fix hardcoded password security issue in docker-compose.dev.yml
- Prevent api.py overwrite in main Dockerfile by checking if file exists
- Use non-editable pip install in production Dockerfile for praisonaiagents
- Move package installations from runtime commands to Dockerfile build stage for better efficiency

These fixes address the critical issues identified in the code review while maintaining the core functionality.

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:20:27 +00:00
claude[bot]andMervinPraison 54072a1d55 fix: add console scripts to project.scripts section for pip compatibility
- Add [project.scripts] section with praisonai, setup-conda-env, and praisonai-call entry points
- Fixes issue where praisonai command was not found when installing with pip instead of Poetry
- Entry points were only defined in [tool.poetry.scripts] which pip ignores
- Maintains compatibility with both pip and Poetry installation methods

Fixes #190

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:17:09 +00:00
Mervin Praison 16ddcb8644 Merge pull request #544 from MervinPraison/claude/issue-246-20250530_105609
fix: add targeted o1 model support for system messages
2025-05-30 12:06:16 +01:00
MervinPraison e0b9739b66 Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 12:04:03 +01:00
claude[bot]andMervinPraison 1bb6c8ca61 fix: add targeted o1 model support for system messages
- Add `_needs_system_message_skip()` method to detect legacy o1 models
- Skip system messages only for specific models: o1-preview, o1-mini, o1-mini-2024-09-12
- Update all 4 system message handling locations in LLM class
- Maintains backward compatibility for all other models
- Fixes 400 error for legacy o1 models that don't support system messages

Fixes #246

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 11:03:18 +00:00
claude[bot]andMervinPraison a242a26353 feat: implement directory management solution for issue #130
- Add DirectoryManager class to organize files in ~/.praison/
- Automatic migration of existing configuration files from root
- Environment variable management for proper config paths
- Solves directory clutter issue (chainlit.md, .chainlit, public)

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:42 +00:00
claude[bot]andMervinPraison ff4f63fc54 feat: add Docker documentation and development setup
- Comprehensive Docker setup guide and documentation
- Environment template for easy configuration
- Development Docker Compose with hot reload
- Interactive quick-start script for user convenience

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:35 +00:00
claude[bot]andMervinPraison 48707686f0 feat: update Docker configurations for improved multi-service setup
- Add new praisonaiagents Dockerfile
- Update docker-compose.yml with multi-service orchestration
- Align versions across all Dockerfiles
- Add directory management environment variables
- Support for ~/.praison/ configuration directory

Co-Authored-By: MervinPraison <[email protected]>
2025-05-30 11:01:23 +00:00
MervinPraison 1a414989bf Merge branch 'main' of https://github.com/MervinPraison/PraisonAI into develop 2025-05-30 11:59:17 +01:00
Mervin Praison 141535c160 Merge pull request #543 from MervinPraison/claude/issue-238-20250528_161801
feat: add camera integration examples and documentation
2025-05-30 11:58:29 +01:00
MervinPraison 6911d6ad64 fix: Update import path for database_config module in unit tests
- Adjusted the sys.path in test_database_config.py to correctly locate the database_config module.
- Ensured compatibility with the module's new location for improved test functionality.
2025-05-30 11:51:29 +01:00
Mervin Praison 6b4be5ca2f Merge pull request #542 from MervinPraison/develop
chore: Update praisonai version to 2.2.19 across Dockerfiles and rela…
2025-05-30 11:46:35 +01:00
MervinPraison a5d08f8f33 chore: Update praisonai version to 2.2.19 across Dockerfiles and related configurations
- Increment praisonai version from 2.2.18 to 2.2.19 in Dockerfiles and deployment scripts
- Update version in pyproject.toml for praisonaiagents from 0.0.91 to 0.0.92
- Ensure consistency across all relevant files for the new version

This update addresses compatibility and feature improvements associated with the latest praisonai release.
2025-05-30 11:46:15 +01:00
Mervin Praison 8e0fa939a4 Merge pull request #540 from MervinPraison/claude/issue-240-20250528_161140
fix: Add DeepSeek compatibility fallback for hierarchical process mode
2025-05-30 11:42:07 +01:00
Mervin Praison 359d02bd56 Merge pull request #537 from MervinPraison/claude/issue-335-20250529_033521
fix: Add FORCE_SQLITE environment variable to bypass production database detection
2025-05-30 11:12:41 +01:00
Mervin Praison 7630e35622 Rename test_database_config.py to src/praisonai/tests/unit/test_database_config.py 2025-05-30 11:12:21 +01:00
claude[bot]andMervinPraison 1fc3a22335 refactor: centralize FORCE_SQLITE logic to eliminate code duplication
- Create shared database_config module with utility functions
- Eliminate duplicated FORCE_SQLITE parsing logic between db.py and sql_alchemy.py  
- Improve maintainability by centralizing database configuration logic
- Add comprehensive test coverage for all scenarios

Addresses reviewer feedback from CodeRabbit and Gemini Code Assist

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 10:09:52 +00:00
claude[bot]andMervinPraison 3d56de7d64 fix: Resolve async client usage and improve error handling in DeepSeek fallback
- Fix critical async client usage by importing and using AsyncOpenAI for async methods
- Add proper exception chaining with 'from' clauses and detailed logging
- Implement JSON parsing error handling with specific exception types
- Replace hardcoded JSON structure prompts with dynamic schema generation
- Enhance fallback mechanism robustness for DeepSeek compatibility

Fixes identified issues:
- Lines 168 & 181: Async methods now use proper AsyncOpenAI clients
- Lines 124 & 164: Exception handling now chains errors properly
- Lines 158-160 & 192-194: JSON parsing wrapped in try-catch blocks
- Lines 119 & 145: Dynamic prompt generation from Pydantic schemas

Co-authored-by: MervinPraison <[email protected]>
2025-05-30 09:48:12 +00:00
Mervin Praison 6008f5c9b6 Merge pull request #541 from MervinPraison/claude/issue-337-20250528_160403
feat: Add SearxNG search tool for privacy-focused web searches
2025-05-30 10:38:22 +01:00
Mervin Praison 6b9a186751 Merge pull request #539 from DhivyaBharathy-web/add-advanced-react-agent-notebook
Add Advanced ReAct Agent notebook to cookbooks
2025-05-30 09:16:32 +01:00
Mervin Praison ce0e46b7f4 Merge pull request #538 from DhivyaBharathy-web/add-joke-agent-notebook
Add Joke Agent notebook to cookbooks
2025-05-30 09:15:46 +01:00
DhivyaBharathy-web 74563a2fbb Add Advanced ReAct Agent notebook to cookbooks 2025-05-29 13:46:48 +00:00
DhivyaBharathy-web 92324d55d2 Add Joke Agent notebook to cookbooks 2025-05-29 07:32:17 +00:00
Mervin Praison 280c66ad94 Merge pull request #535 from MervinPraison/claude/issue-332-20250528_161954
fix: improve API key validation for LM Studio and local servers
2025-05-29 05:45:14 +01:00
claude[bot]andMervinPraison dc9faba1c9 refactor: improve API key validation with constants and enhanced local server detection
- Add LOCAL_SERVER_API_KEY_PLACEHOLDER constant for consistency
- Improve error messages to clearly distinguish default OpenAI vs local servers
- Enhance local server detection with urlparse for localhost/127.0.0.1 handling
- Remove hardcoded "nokey" fallback that masked missing API key issues
- Maintain full backward compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: MervinPraison <[email protected]>
2025-05-29 04:41:10 +00:00
Mervin Praison 10d9f00fd5 Update models.py 2025-05-29 05:30:08 +01:00
Mervin Praison 5c9ac6f222 Merge branch 'main' into claude/issue-332-20250528_161954 2025-05-29 05:27:36 +01:00
Mervin Praisonandgemini-code-assist[bot] 8ea7c3289f Update src/praisonai/praisonai/inc/models.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-05-29 05:22:03 +01:00
Mervin Praison 2ac83534d8 Merge pull request #536 from MervinPraison/claude/issue-307-20250528_161908
fix: resolve asyncio event loop error in memory callbacks
2025-05-29 05:21:20 +01:00
claude[bot]andMervinPraison ba5af94dcb fix: add fallback mechanism for conda run failure in training
- Add robust conda environment detection and setup validation
- Implement fallback to direct Python execution when conda fails
- Maintain backward compatibility while improving reliability
- Handle cases where conda is not available or environment setup fails

Fixes #140

Co-authored-by: MervinPraison <[email protected]>
2025-05-29 04:20:59 +00:00
Mervin Praison b1d9ef567b Merge pull request #534 from MervinPraison/develop
2.2.18
2025-05-29 04:52:31 +01:00
MervinPraison e633ad959d chore: update PraisonAI version and enhance environment variable handling
- Upgraded PraisonAI version from 2.2.17 to 2.2.18 across multiple Dockerfiles and configuration files for improved functionality.
- Enhanced GitHub Actions workflow by exporting additional environment variables related to the OpenAI API, improving integration and debugging capabilities.

These changes aim to ensure better performance and reliability in the workflow and API interactions.
2025-05-29 04:51:53 +01:00
MervinPraison 27065af652 chore: enhance API key handling and testing in workflows
- Introduced a dummy class for SecretStr to handle cases where Pydantic is not available, ensuring compatibility in minimal contexts.
- Added a utility function to display API key values more securely, improving the visibility of sensitive information during testing.
- Updated test cases to ensure the OPENAI_API_KEY is available for subprocess commands, enhancing the reliability of tests that require real API interactions.

These changes aim to improve the robustness of API key handling and enhance the testing framework's functionality.
2025-05-29 04:38:51 +01:00
claude[bot]andMervinPraison 46585a88e6 fix: Add FORCE_SQLITE environment variable to bypass production database detection
- Add FORCE_SQLITE flag in both db.py and sql_alchemy.py
- When FORCE_SQLITE=true, ignores DATABASE_URL and SUPABASE_DATABASE_URL
- Forces SQLite usage to prevent accidental production database connections
- Maintains backward compatibility with default behavior (FORCE_SQLITE=false)
- Resolves database conflict issues in production environments

Fixes #335

Co-authored-by: MervinPraison <[email protected]>
2025-05-29 03:36:38 +00:00
claude[bot]andMervinPraison ecb4974333 fix: improve API key validation for LM Studio and local servers
- Replace hardcoded "xxxx" fallback in main.py with proper validation
- Replace hardcoded "nokey" fallback in models.py with proper validation  
- Add automatic detection and support for local servers (LM Studio, etc.)
- Provide clear error messages when API keys are missing
- Support multiple base URL environment variable patterns

Fixes #332

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:23:06 +00:00
claude[bot]andMervinPraison 955a05be59 feat: add camera integration examples and documentation
- Add comprehensive camera integration examples in examples/python/camera/
  - camera-basic.py: Single frame capture and analysis
  - camera-continuous.py: Continuous monitoring system
  - camera-multi-agent.py: Multi-agent parallel analysis
  - camera-video-analysis.py: Video recording and temporal analysis
  - README.md: Complete documentation for all examples

- Add camera integration documentation in docs/features/camera-integration.mdx
  - Complete integration patterns and use cases
  - Configuration options and best practices
  - Troubleshooting guide and security considerations
  - Links to example code

- Update docs/mint.json to include camera integration in navigation

Addresses issue #238 regarding multimodel agent camera input

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:22:02 +00:00
claude[bot]andMervinPraison f6526ab127 fix: resolve asyncio event loop error in memory callbacks
- Replace deprecated asyncio.get_event_loop() with asyncio.get_running_loop()
- Add proper RuntimeError handling for non-main threads (Streamlit)
- Fix memory dependency checking to use warnings instead of exceptions
- Improves compatibility with Python 3.10+ and threaded environments

Fixes #307

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:20:44 +00:00
claude[bot]andMervinPraison 66dc2e3c48 fix: Add DeepSeek compatibility fallback for hierarchical process mode
- Add JSON fallback when structured output fails
- Implements minimal code change solution for issue #240  
- Maintains compatibility with both OpenAI and DeepSeek APIs
- Uses try-catch to gracefully fallback from structured output to JSON mode
- Enhanced prompts for better JSON parsing reliability

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:14:48 +00:00
claude[bot]andMervinPraison 90c3a90752 feat: Add SearxNG search tool for privacy-focused web searches
- Implement SearxNG search tool following PraisonAI patterns
- Add comprehensive documentation with setup instructions
- Include working examples for both tools and agents
- Support configurable SearxNG URL and search parameters
- Integrate with existing tools registry and documentation
- Provide privacy-focused alternative to traditional search engines

Addresses issue #337

Co-authored-by: MervinPraison <[email protected]>
2025-05-28 16:07:30 +00:00
52 changed files with 6953 additions and 2306 deletions

No files matched your search

+34 -9
View File
@@ -133,39 +133,61 @@ jobs:
import sys
sys.path.insert(0, '.')
# Attempt to import SecretStr, otherwise use a dummy class
try:
from pydantic.types import SecretStr
except ImportError:
class SecretStr: # Dummy class if pydantic is not available in this minimal context
def __init__(self, value): self._value = value
def get_secret_value(self): return self._value
def get_key_display_value(key_value):
if isinstance(key_value, SecretStr):
return key_value.get_secret_value()[:10] if key_value.get_secret_value() else 'EMPTY_SECRET'
elif isinstance(key_value, str):
return key_value[:10] if key_value != 'nokey' and key_value != 'NOT_SET' else key_value
return 'INVALID_TYPE'
print('🔧 Direct PraisonAI API Key Check:')
print(f'Environment OPENAI_API_KEY: {os.environ.get(\"OPENAI_API_KEY\", \"NOT_SET\")[:10]}...')
env_api_key = os.environ.get(\\"OPENAI_API_KEY\\", \\"NOT_SET\\")
print(f'Environment OPENAI_API_KEY: {get_key_display_value(env_api_key)}...')
# Test PraisonAI initialization
from praisonai import PraisonAI
praisonai = PraisonAI()
print(f'PraisonAI config_list: {praisonai.config_list}')
api_key_from_config = praisonai.config_list[0].get('api_key', 'NOT_SET')
print(f'API key from PraisonAI config: {api_key_from_config[:10] if api_key_from_config != \"NOT_SET\" else \"NOT_SET\"}...')
print(f'API key from PraisonAI config: {get_key_display_value(api_key_from_config)}...')
# Test PraisonAIModel with explicit API key (the way CrewAI will use it)
from praisonai.inc.models import PraisonAIModel
print('\\n🧪 Testing PraisonAIModel with explicit API key (CrewAI method):')
print('\\\\n🧪 Testing PraisonAIModel with explicit API key (CrewAI method):')
model_with_explicit_key = PraisonAIModel(
model='openai/gpt-4o-mini',
base_url=praisonai.config_list[0].get('base_url'),
api_key=praisonai.config_list[0].get('api_key')
api_key=api_key_from_config # This will be a string from praisonai.config_list
)
print(f' Model: {model_with_explicit_key.model}')
print(f' Model name: {model_with_explicit_key.model_name}')
print(f' API key var: {model_with_explicit_key.api_key_var}')
print(f' API key (explicit): {model_with_explicit_key.api_key[:10] if model_with_explicit_key.api_key != \"nokey\" else \"NOT_SET\"}...')
# model_with_explicit_key.api_key is now a string, or 'nokey'
print(f' API key (explicitly passed to PraisonAIModel): {get_key_display_value(model_with_explicit_key.api_key)}...')
print(f' Base URL: {model_with_explicit_key.base_url}')
# Test if the model can be created without errors
try:
llm_instance = model_with_explicit_key.get_model()
print(f' ✅ LLM instance created successfully: {type(llm_instance).__name__}')
print(f' LLM instance API key: {getattr(llm_instance, \"openai_api_key\", \"NOT_FOUND\")[:10] if hasattr(llm_instance, \"openai_api_key\") else \"NO_API_KEY_ATTR\"}...')
# langchain_openai.ChatOpenAI stores the key in openai_api_key as SecretStr
llm_api_key_attr = getattr(llm_instance, 'openai_api_key', 'NOT_FOUND')
if llm_api_key_attr != 'NOT_FOUND':
print(f' LLM instance API key: {get_key_display_value(llm_api_key_attr)}...')
else:
print(f' LLM instance API key attribute not found.')
except Exception as e:
print(f' ❌ Failed to create LLM instance: {e}')
import traceback
traceback.print_exc()
"
continue-on-error: true
@@ -234,6 +256,9 @@ jobs:
echo " OPENAI_API_KEY set: $([ -n "$OPENAI_API_KEY" ] && echo 'YES' || echo 'NO')"
echo " OPENAI_API_KEY length: ${#OPENAI_API_KEY}"
echo " OPENAI_API_KEY starts with sk-: $(echo "$OPENAI_API_KEY" | grep -q '^sk-' && echo 'YES' || echo 'NO')"
export OPENAI_API_KEY="$OPENAI_API_KEY"
export OPENAI_API_BASE="$OPENAI_API_BASE"
export OPENAI_MODEL_NAME="$OPENAI_MODEL_NAME"
cd src/praisonai && python -m pytest tests/test.py -v --tb=short --disable-warnings
- name: Upload Coverage Reports
+53
View File
@@ -0,0 +1,53 @@
# PraisonAI Docker Environment Configuration
# Copy this file to .env and update with your values
# Required: OpenAI API Key
OPENAI_API_KEY=your_openai_api_key_here
# Required: Chainlit Authentication Secret (generate a random string)
CHAINLIT_AUTH_SECRET=your_chainlit_secret_here
# Optional: Additional API Keys
ANTHROPIC_API_KEY=your_anthropic_api_key_here
GOOGLE_API_KEY=your_google_api_key_here
GROQ_API_KEY=your_groq_api_key_here
COHERE_API_KEY=your_cohere_api_key_here
# Optional: Service Configuration
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
# Optional: Port Configuration (modify if you have conflicts)
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
# Optional: Database Configuration
DATABASE_URL=sqlite:///root/.praison/database.sqlite
# Optional: Logging Configuration
LOG_LEVEL=INFO
PYTHONPATH=/app
# Optional: Development Settings
DEV_MODE=false
DEBUG=false
# Optional: Model Configuration
DEFAULT_MODEL=gpt-4
DEFAULT_TEMPERATURE=0.7
MAX_TOKENS=4000
# Optional: Security Settings
CORS_ORIGINS=http://localhost:3000,http://localhost:8082,http://localhost:8083
RATE_LIMIT=100
# Optional: Feature Flags
ENABLE_CHAT=true
ENABLE_UI=true
ENABLE_API=true
ENABLE_REALTIME=true
# Optional: Volume Paths (for custom mounting)
PRAISON_DATA_PATH=~/.praison
EXAMPLES_PATH=./examples
+40 -1
View File
@@ -1,6 +1,45 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
flask \
"praisonai>=2.2.20" \
"praisonai[api]" \
gunicorn \
markdown
# Copy application code
COPY . .
RUN pip install flask praisonai==2.2.17 gunicorn markdown
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Create health check API if api.py doesn't exist
RUN if [ ! -f api.py ]; then \
echo "from flask import Flask\n\
app = Flask(__name__)\n\
\n\
@app.route('/health')\n\
def health():\n\
return {'status': 'healthy'}, 200\n\
\n\
if __name__ == '__main__':\n\
app.run()" > api.py; \
fi
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+12 -3
View File
@@ -7,18 +7,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.17" \
"praisonai>=2.2.20" \
"praisonai[chat]" \
"embedchain[github,youtube]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "chat"]
+23 -4
View File
@@ -9,20 +9,39 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
portaudio19-dev \
python3-pyaudio \
curl \
git \
vim \
nano \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.17" \
"praisonai>=2.2.20" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
"embedchain[github,youtube]"
"praisonai[code]" \
"embedchain[github,youtube]" \
jupyter \
jupyterlab \
notebook \
pytest \
pytest-asyncio \
mkdocs \
mkdocs-material
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
+32
View File
@@ -0,0 +1,32 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Copy praisonaiagents source
COPY src/praisonai-agents/ /app/praisonaiagents/
WORKDIR /app/praisonaiagents
# Install praisonaiagents with all features (non-editable for production)
RUN pip install --no-cache-dir ".[all]"
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
WORKDIR /app
# Default command
CMD ["python", "-c", "import praisonaiagents; print('PraisonAI Agents installed successfully')"]
+12 -3
View File
@@ -7,18 +7,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonaiagents>=0.0.4 \
praisonai_tools \
"praisonai==2.2.17" \
"praisonai>=2.2.20" \
"praisonai[ui]" \
"praisonai[crewai]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
+265
View File
@@ -0,0 +1,265 @@
# PraisonAI Docker Setup
This directory contains Docker configurations for running PraisonAI services in containerized environments. The setup addresses directory management issues and provides comprehensive multi-service deployment options.
## 🐳 Available Services
### Core Services
- **UI Service** (`port 8082`) - Chainlit-based web interface
- **Chat Service** (`port 8083`) - Dedicated chat interface
- **API Service** (`port 8080`) - REST API endpoint
- **Agents Service** - Standalone PraisonAI Agents runtime
### Docker Files
- `Dockerfile` - Basic API service
- `Dockerfile.ui` - UI service with web interface
- `Dockerfile.chat` - Chat-focused service
- `Dockerfile.dev` - Development environment with tools
- `Dockerfile.praisonaiagents` - Standalone agents framework
- `docker-compose.yml` - Multi-service orchestration
## 🚀 Quick Start
### Single Service
```bash
# Run UI service
docker run -p 8082:8082 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:ui
# Run Chat service
docker run -p 8083:8083 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:chat
# Run API service
docker run -p 8080:8080 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:api
```
### Multi-Service with Docker Compose
```bash
# Create environment file
cat > .env << EOF
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=your_secret_here
EOF
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down
```
## 📁 Directory Management
### Problem Solved
The original issue was that files like `chainlit.md`, `.chainlit` directory, and `public` folder were cluttering the root directory.
### Solution Implemented
All PraisonAI configuration and runtime files are now stored in `~/.praison/`:
```bash
~/.praison/
├── database.sqlite # Chainlit database
├── chainlit.md # Chainlit configuration
├── .chainlit/ # Chainlit runtime files
└── config/ # PraisonAI configuration
```
### Environment Variables
```bash
PRAISON_CONFIG_DIR=/root/.praison # Main config directory
CHAINLIT_CONFIG_DIR=/root/.praison # Chainlit config location
CHAINLIT_DB_DIR=/root/.praison # Database location
```
## 🔧 Service Configuration
### UI Service (Port 8082)
```yaml
environment:
- CHAINLIT_PORT=8082
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
- CHAINLIT_AUTH_SECRET=${CHAINLIT_AUTH_SECRET}
```
### Chat Service (Port 8083)
```yaml
environment:
- CHAINLIT_PORT=8083
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
### API Service (Port 8080)
```yaml
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
## 🎯 Service Endpoints
| Service | Port | Endpoint | Description |
|---------|------|----------|-------------|
| UI | 8082 | http://localhost:8082 | Web interface |
| Chat | 8083 | http://localhost:8083 | Chat interface |
| API | 8080 | http://localhost:8080 | REST API |
| API Health | 8080 | http://localhost:8080/health | Health check |
## 🔍 Health Checks
All services include health checks:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:PORT"]
interval: 10s
timeout: 5s
retries: 3
```
## 📦 Package Versions
All Docker images use consistent, up-to-date versions:
- PraisonAI: `>=2.2.20`
- PraisonAI Agents: `>=0.0.92`
- Python: `3.11-slim`
## 🔒 Security Features
- Non-root user execution where possible
- Minimal base image (python:3.11-slim)
- No unnecessary packages installed
- Environment variable-based configuration
- Volume mounting for persistent data
## 🛠 Development
### Development Environment
```bash
# Use development Dockerfile with additional tools
docker build -f Dockerfile.dev -t praisonai:dev .
docker run -it -v $(pwd):/app praisonai:dev bash
```
### Custom Configuration
```bash
# Mount custom config directory
docker run -v ~/.praison:/root/.praison praisonai:ui
```
## 📊 Monitoring
### Docker Compose Monitoring
```bash
# View service status
docker-compose ps
# View resource usage
docker-compose top
# View logs for specific service
docker-compose logs ui
docker-compose logs chat
docker-compose logs api
```
## 🚨 Troubleshooting
### Common Issues
1. **Port conflicts**
```bash
# Check port usage
netstat -tlnp | grep :8082
# Use different ports
docker run -p 9082:8082 praisonai:ui
```
2. **Environment variables not loading**
```bash
# Verify .env file
cat .env
# Set variables directly
docker run -e OPENAI_API_KEY=your_key praisonai:ui
```
3. **Permission issues**
```bash
# Check volume permissions
ls -la ~/.praison/
# Fix permissions
sudo chown -R $(id -u):$(id -g) ~/.praison/
```
4. **Service won't start**
```bash
# Check logs
docker-compose logs service_name
# Restart service
docker-compose restart service_name
```
## 🔄 Updates
### Pulling Latest Images
```bash
# Pull latest images
docker-compose pull
# Restart with new images
docker-compose up -d
```
### Version Pinning
To use specific versions, update the Dockerfile:
```dockerfile
RUN pip install "praisonai==2.2.20" "praisonaiagents==0.0.92"
```
## 🌐 Production Deployment
### Recommended Production Setup
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
ui:
image: ghcr.io/mervinpraison/praisonai:ui
restart: unless-stopped
environment:
- CHAINLIT_HOST=0.0.0.0
- CHAINLIT_PORT=8082
volumes:
- praison_data:/root/.praison
networks:
- praison_network
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
```
### Load Balancer Configuration
For production environments, consider using nginx or similar:
```nginx
upstream praisonai {
server localhost:8082;
server localhost:8083;
}
server {
listen 80;
location / {
proxy_pass http://praisonai;
}
}
```
This Docker setup provides a clean, organized, and scalable way to deploy PraisonAI services while solving the directory management issues mentioned in the original request.
+184
View File
@@ -0,0 +1,184 @@
version: '3.8'
# Development Docker Compose Configuration
# Extends the main docker-compose.yml with development-specific settings
services:
# Development UI Service with hot reload
ui-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:ro
ports:
- "${UI_PORT:-8082}:8082"
environment:
FLASK_ENV: development
CHAINLIT_PORT: "8082"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai ui --host 0.0.0.0 --reload
networks:
- praison_network
depends_on:
- database
# Development Chat Service
chat-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${CHAT_PORT:-8083}:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai chat --host 0.0.0.0 --port 8083 --reload
networks:
- praison_network
depends_on:
- database
# Development API Service with hot reload
api-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${API_PORT:-8080}:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
FLASK_ENV: development
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: python -m flask --app api run --host 0.0.0.0 --port 8080 --debug
networks:
- praison_network
depends_on:
- database
# Development Jupyter Notebook Service
jupyter:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:rw
ports:
- "8888:8888"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
JUPYTER_ENABLE_LAB: "yes"
PYTHONPATH: /app/src
command: jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root --LabApp.token='' --LabApp.password=''
networks:
- praison_network
# Development Testing Service
test:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../src:/app/src:rw
- ../tests:/app/tests:rw
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
PYTHONPATH: /app/src
PYTEST_CURRENT_TEST: "true"
command: python -m pytest tests/ -v
networks:
- praison_network
profiles:
- test
# Standalone database for development
database:
image: postgres:15-alpine
environment:
POSTGRES_DB: praisonai_dev
POSTGRES_USER: praisonai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-praisonai_dev_pass}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- praison_network
profiles:
- database
# Redis for caching and session management
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- praison_network
profiles:
- cache
# Development documentation server
docs:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../docs:/app/docs:rw
- ../README.md:/app/README.md:ro
ports:
- "8000:8000"
command: sh -c "cd docs && mkdocs serve --dev-addr 0.0.0.0:8000"
networks:
- praison_network
profiles:
- docs
volumes:
praison_data:
driver: local
postgres_data:
driver: local
redis_data:
driver: local
networks:
praison_network:
driver: bridge
+81 -3
View File
@@ -1,9 +1,13 @@
version: '3.8'
services:
# PraisonAI UI Service
ui:
build:
context: .
dockerfile: Dockerfile.ui
context: ..
dockerfile: docker/Dockerfile.ui
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8082:8082"
@@ -13,9 +17,83 @@ services:
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai ui --host 0.0.0.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8082"]
interval: 10s
timeout: 5s
retries: 3
retries: 3
networks:
- praison_network
# PraisonAI Chat Service
chat:
build:
context: ..
dockerfile: docker/Dockerfile.chat
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8083:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai chat --host 0.0.0.0 --port 8083
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8083"]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI API Service
api:
build:
context: ..
dockerfile: docker/Dockerfile
volumes:
- praison_data:/root/.praison
ports:
- "8080:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI Agents Service (standalone)
agents:
build:
context: ..
dockerfile: docker/Dockerfile.praisonaiagents
volumes:
- praison_data:/root/.praison
- ./examples:/app/examples
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
networks:
- praison_network
volumes:
praison_data:
driver: local
networks:
praison_network:
driver: bridge
+289
View File
@@ -0,0 +1,289 @@
#!/bin/bash
# PraisonAI Docker Quick Start Script
# This script helps users quickly get started with PraisonAI using Docker
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if Docker is installed
check_docker() {
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed. Please install Docker first:"
echo " - Visit: https://docs.docker.com/get-docker/"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
print_error "Docker Compose is not available. Please install Docker Compose:"
echo " - Visit: https://docs.docker.com/compose/install/"
exit 1
fi
print_success "Docker and Docker Compose are available"
}
# Check if .env file exists, create from template if not
setup_environment() {
if [ ! -f ".env" ]; then
print_status "Creating .env file from template..."
if [ -f ".env.template" ]; then
cp .env.template .env
else
cat > .env << EOF
# PraisonAI Docker Environment Configuration
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "your_chainlit_secret_here")
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
EOF
fi
print_warning "Please edit .env file and add your API keys before continuing"
print_status "Required: OPENAI_API_KEY"
read -p "Press Enter to continue after updating .env file..."
else
print_success ".env file already exists"
fi
}
# Function to show menu
show_menu() {
echo ""
echo "=== PraisonAI Docker Quick Start ==="
echo ""
echo "1) Start UI Service (Web Interface) - Port 8082"
echo "2) Start Chat Service - Port 8083"
echo "3) Start API Service - Port 8080"
echo "4) Start All Services (Recommended)"
echo "5) Start Development Environment"
echo "6) View Service Status"
echo "7) View Logs"
echo "8) Stop All Services"
echo "9) Pull Latest Images"
echo "10) Clean Up (Remove containers and volumes)"
echo "11) Show Service URLs"
echo "0) Exit"
echo ""
}
# Start specific service
start_service() {
local service=$1
print_status "Starting $service service..."
docker-compose up -d $service
print_success "$service service started"
}
# Start all services
start_all_services() {
print_status "Starting all PraisonAI services..."
docker-compose up -d
print_success "All services started"
show_service_urls
}
# Start development environment
start_dev_environment() {
print_status "Starting development environment..."
if [ -f "docker-compose.dev.yml" ]; then
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ui-dev chat-dev api-dev
print_success "Development environment started"
echo ""
echo "Development services available:"
echo " - UI (with hot reload): http://localhost:${UI_PORT:-8082}"
echo " - Chat (with hot reload): http://localhost:${CHAT_PORT:-8083}"
echo " - API (with hot reload): http://localhost:${API_PORT:-8080}"
echo " - Jupyter Lab: http://localhost:8888"
echo ""
else
print_warning "Development configuration not found, starting regular services"
start_all_services
fi
}
# Show service status
show_status() {
print_status "Service Status:"
docker-compose ps
}
# Show logs
show_logs() {
echo ""
echo "Which service logs would you like to view?"
echo "1) All services"
echo "2) UI service"
echo "3) Chat service"
echo "4) API service"
echo "5) Agents service"
read -p "Enter choice (1-5): " log_choice
case $log_choice in
1) docker-compose logs -f ;;
2) docker-compose logs -f ui ;;
3) docker-compose logs -f chat ;;
4) docker-compose logs -f api ;;
5) docker-compose logs -f agents ;;
*) print_error "Invalid choice" ;;
esac
}
# Stop all services
stop_services() {
print_status "Stopping all services..."
docker-compose down
print_success "All services stopped"
}
# Pull latest images
pull_images() {
print_status "Pulling latest images..."
docker-compose pull
print_success "Images updated"
}
# Clean up
cleanup() {
print_warning "This will remove all containers, networks, and volumes"
read -p "Are you sure? (y/N): " confirm
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
print_status "Cleaning up..."
docker-compose down -v --remove-orphans
docker system prune -f
print_success "Cleanup completed"
fi
}
# Show service URLs
show_service_urls() {
local ui_port=${UI_PORT:-8082}
local chat_port=${CHAT_PORT:-8083}
local api_port=${API_PORT:-8080}
echo ""
echo "=== Service URLs ==="
echo "UI Service: http://localhost:$ui_port"
echo "Chat Service: http://localhost:$chat_port"
echo "API Service: http://localhost:$api_port"
echo "API Health: http://localhost:$api_port/health"
echo ""
}
# Check system requirements
check_requirements() {
print_status "Checking system requirements..."
# Check available memory
if command -v free &> /dev/null; then
available_ram=$(free -m | awk 'NR==2{printf "%.1f", $7/1024}')
print_status "Available RAM: ${available_ram}GB"
if (( $(echo "$available_ram < 2.0" | bc -l 2>/dev/null || echo "1") )); then
print_warning "Low available memory. Recommended: 2GB+ available RAM"
fi
fi
# Check disk space
if command -v df &> /dev/null; then
available_space=$(df -BG . | awk 'NR==2{print $4}' | sed 's/G//')
print_status "Available disk space: ${available_space}GB"
if [ "$available_space" -lt 5 ]; then
print_warning "Low disk space. Recommended: 5GB+ available space"
fi
fi
}
# Main function
main() {
clear
echo "🤖 Welcome to PraisonAI Docker Setup!"
echo ""
# Check prerequisites
check_docker
check_requirements
setup_environment
# Main loop
while true; do
show_menu
read -p "Enter your choice (0-11): " choice
case $choice in
1)
start_service "ui"
echo "UI Service: http://localhost:${UI_PORT:-8082}"
;;
2)
start_service "chat"
echo "Chat Service: http://localhost:${CHAT_PORT:-8083}"
;;
3)
start_service "api"
echo "API Service: http://localhost:${API_PORT:-8080}"
;;
4)
start_all_services
;;
5)
start_dev_environment
;;
6)
show_status
;;
7)
show_logs
;;
8)
stop_services
;;
9)
pull_images
;;
10)
cleanup
;;
11)
show_service_urls
;;
0)
print_success "Thank you for using PraisonAI!"
exit 0
;;
*)
print_error "Invalid choice. Please try again."
;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
# Run main function
main
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.17 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==2.2.20 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+1 -1
View File
@@ -27,7 +27,7 @@ WORKDIR /app
COPY . .
RUN pip install flask praisonai==2.2.17 watchdog
RUN pip install flask praisonai==2.2.20 watchdog
EXPOSE 5555
+423
View File
@@ -0,0 +1,423 @@
---
title: Camera Integration
description: How to integrate camera feeds with PraisonAI multimodal agents for real-time visual analysis
---
# Camera Integration
PraisonAI supports camera integration for real-time visual analysis through multimodal agents. While there's no built-in camera capture, you can easily integrate camera feeds by capturing frames or videos and passing them to vision agents.
## Overview
Camera integration works by:
1. **Capturing frames/videos** from camera using OpenCV
2. **Saving temporarily** to disk
3. **Passing file paths** to agents via the `images` parameter
4. **Cleaning up** temporary files after analysis
## Quick Start
### Prerequisites
```bash
pip install praisonaiagents opencv-python
export OPENAI_API_KEY=your_openai_api_key
```
### Basic Camera Capture
```python
import cv2
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_and_analyze():
# Create vision agent
vision_agent = Agent(
name="CameraAnalyst",
role="Camera Feed Analyzer",
goal="Analyze camera captures in real-time",
backstory="Expert in real-time visual analysis",
llm="gpt-4o-mini"
)
# Capture from camera
cap = cv2.VideoCapture(0) # 0 for default camera
ret, frame = cap.read()
if ret:
# Save frame temporarily
cv2.imwrite("temp_capture.jpg", frame)
cap.release()
# Create analysis task
task = Task(
description="Analyze what you see in this camera feed",
expected_output="Detailed analysis of camera content",
agent=vision_agent,
images=["temp_capture.jpg"]
)
# Run analysis
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task]
)
return agents.start()
# Run analysis
result = capture_and_analyze()
```
## Integration Patterns
### 1. Single Frame Analysis
Perfect for quick snapshots and one-time analysis:
```python
def single_frame_analysis():
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if ret:
image_path = "snapshot.jpg"
cv2.imwrite(image_path, frame)
# Analyze with your agent
# ... (agent setup and task creation)
cap.release()
```
### 2. Continuous Monitoring
Ideal for security systems and real-time monitoring:
```python
def continuous_monitoring(interval=10):
vision_agent = Agent(
name="SecurityMonitor",
role="Security Camera Analyst",
goal="Monitor for security events",
backstory="Expert security analyst",
llm="gpt-4o-mini"
)
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if ret:
timestamp = int(time.time())
filename = f"capture_{timestamp}.jpg"
cv2.imwrite(filename, frame)
# Analyze frame
task = Task(
description="Monitor for unusual activities",
agent=vision_agent,
images=[filename]
)
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task]
)
result = agents.start()
print(f"Analysis: {result}")
time.sleep(interval)
```
### 3. Multi-Agent Analysis
Use multiple specialized agents for comprehensive analysis:
```python
def multi_agent_camera_analysis():
# Create specialized agents
security_agent = Agent(
name="SecurityExpert",
role="Security Specialist",
goal="Identify security threats",
backstory="Expert in surveillance and threat detection",
llm="gpt-4o-mini"
)
object_detector = Agent(
name="ObjectDetector",
role="Object Recognition Specialist",
goal="Identify and catalog objects",
backstory="Computer vision expert",
llm="gpt-4o-mini"
)
scene_analyst = Agent(
name="SceneAnalyst",
role="Scene Understanding Expert",
goal="Provide comprehensive scene analysis",
backstory="Environmental analysis expert",
llm="gpt-4o-mini"
)
# Capture frame
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release()
if ret:
cv2.imwrite("analysis_frame.jpg", frame)
# Create specialized tasks
security_task = Task(
description="Analyze for security threats",
agent=security_agent,
images=["analysis_frame.jpg"]
)
object_task = Task(
description="Identify all objects in scene",
agent=object_detector,
images=["analysis_frame.jpg"]
)
scene_task = Task(
description="Provide comprehensive scene analysis",
agent=scene_analyst,
images=["analysis_frame.jpg"]
)
# Run parallel analysis
agents = PraisonAIAgents(
agents=[security_agent, object_detector, scene_analyst],
tasks=[security_task, object_task, scene_task],
process="parallel"
)
return agents.start()
```
### 4. Video Recording & Analysis
Analyze temporal events and activities:
```python
def record_and_analyze_video(duration=10):
cap = cv2.VideoCapture(0)
# Setup video writer
fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('recording.mp4', fourcc, fps, (width, height))
# Record video
start_time = time.time()
while (time.time() - start_time) < duration:
ret, frame = cap.read()
if ret:
out.write(frame)
cap.release()
out.release()
# Analyze video
video_agent = Agent(
name="VideoAnalyst",
role="Video Content Analyzer",
goal="Analyze video content for activities",
backstory="Expert in video analysis",
llm="gpt-4o-mini"
)
task = Task(
description="Analyze this video for activities and events",
agent=video_agent,
images=["recording.mp4"] # Video files work too!
)
agents = PraisonAIAgents(
agents=[video_agent],
tasks=[task]
)
return agents.start()
```
## Supported Input Types
PraisonAI accepts various visual input formats:
- ✅ **Local Images**: `"camera_shot.jpg"`, `"webcam_capture.png"`
- ✅ **Local Videos**: `"security_feed.mp4"`, `"recording.avi"`
- ✅ **Image URLs**: `"https://example.com/live_feed.jpg"`
- ✅ **Multiple Sources**: `["cam1.jpg", "cam2.jpg", "video.mp4"]`
## Configuration Options
### Camera Selection
```python
# Different camera sources
camera_id = 0 # Default camera
camera_id = 1 # External USB camera
camera_id = "rtsp://camera-ip/stream" # IP camera
```
### Analysis Intervals
```python
# For continuous monitoring
analysis_interval = 5 # Every 5 seconds (high frequency)
analysis_interval = 30 # Every 30 seconds (moderate)
analysis_interval = 300 # Every 5 minutes (low frequency)
```
### Video Recording
```python
# Recording duration
duration = 10 # 10 seconds
duration = 60 # 1 minute
duration = 300 # 5 minutes
```
## Use Cases
### Security Monitoring
- Real-time threat detection
- Unauthorized access alerts
- Suspicious activity identification
- Perimeter monitoring
### Retail Analytics
- Customer behavior analysis
- Inventory monitoring
- Queue management
- Theft prevention
### Industrial Automation
- Quality control inspection
- Safety compliance monitoring
- Equipment status verification
- Process optimization
### Smart Home
- Activity recognition
- Elderly care monitoring
- Pet monitoring
- Energy usage optimization
## Best Practices
### Performance Optimization
1. **Frame Rate Management**
```python
# Reduce analysis frequency for better performance
time.sleep(5) # Analyze every 5 seconds instead of continuously
```
2. **Image Size Optimization**
```python
# Resize frames for faster processing
frame = cv2.resize(frame, (640, 480))
```
3. **Parallel Processing**
```python
# Use parallel process for multiple agents
process="parallel"
```
### Memory Management
1. **Clean Up Temporary Files**
```python
import os
if os.path.exists(image_path):
os.remove(image_path)
```
2. **Limit Video Duration**
```python
# Keep recordings short to avoid large files
max_duration = 30 # seconds
```
### Security Considerations
1. **Camera Permissions**
- Ensure application has camera access
- Handle permission errors gracefully
2. **Privacy Protection**
- Implement data retention policies
- Secure transmission of camera data
- User consent for recording
3. **Access Control**
- Authenticate camera access
- Implement role-based permissions
## Troubleshooting
### Camera Not Found
```python
# Check available cameras
for i in range(4):
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Camera {i} is available")
cap.release()
else:
print(f"Camera {i} not available")
```
### Permission Issues
**Linux:**
```bash
# Add user to video group
sudo usermod -a -G video $USER
```
**macOS:**
- Grant camera permissions in System Preferences > Security & Privacy
**Windows:**
- Check camera privacy settings in Windows Settings
### Performance Issues
```python
# Reduce frame size
frame = cv2.resize(frame, (320, 240))
# Lower analysis frequency
time.sleep(10) # Analyze every 10 seconds
# Use threading for non-blocking capture
import threading
```
## Examples
Complete working examples are available in the repository:
- [`camera-basic.py`](../../examples/python/camera/camera-basic.py) - Basic single frame capture
- [`camera-continuous.py`](../../examples/python/camera/camera-continuous.py) - Continuous monitoring
- [`camera-multi-agent.py`](../../examples/python/camera/camera-multi-agent.py) - Multi-agent analysis
- [`camera-video-analysis.py`](../../examples/python/camera/camera-video-analysis.py) - Video recording & analysis
## Related Features
- [Multimodal Agents](./multimodal) - Core multimodal capabilities
- [Image Generation](./image-generation) - Creating images with AI
- [Agent Memory](../concepts/memory) - Persistent agent memory
- [Task Management](../concepts/tasks) - Task configuration and execution
+2 -1
View File
@@ -143,7 +143,8 @@
"features/mathagent",
"features/structured",
"features/callbacks",
"features/chat-with-pdf"
"features/chat-with-pdf",
"features/camera-integration"
]
},
{
+248
View File
@@ -0,0 +1,248 @@
---
title: "SearxNG Search"
description: "Search the web using your local SearxNG instance for privacy-focused web searches"
---
# SearxNG Search Tool
The SearxNG search tool allows you to perform web searches using your local SearxNG instance, providing privacy-focused search capabilities as an alternative to traditional search engines like Google or DuckDuckGo.
## Overview
SearxNG is a privacy-respecting metasearch engine that aggregates results from multiple search engines without storing your data. This tool integrates with your local SearxNG instance to provide secure, private web searches for your AI agents.
## Installation
First, install the required dependency:
```bash
pip install requests
```
You'll also need a running SearxNG instance. You can set it up using Docker:
```bash
# Run SearxNG locally on port 32768
docker run -d --name searxng -p 32768:8080 searxng/searxng
```
## Usage
### Basic Usage
```python
from praisonaiagents.tools import searxng_search
# Basic search
results = searxng_search("AI news")
# With custom parameters
results = searxng_search(
query="Python programming",
max_results=10,
searxng_url="http://localhost:32768/search"
)
# Display results
for result in results:
print(f"Title: {result['title']}")
print(f"URL: {result['url']}")
print(f"Snippet: {result['snippet']}")
print("-" * 50)
```
### Using the Alias
```python
from praisonaiagents.tools import searxng
# Same functionality as searxng_search
results = searxng("machine learning tutorials")
```
### Integration with Agents
```python
from praisonaiagents import Agent, Task
from praisonaiagents.tools import searxng_search
# Create an agent with SearxNG search capability
search_agent = Agent(
name="Search Agent",
instructions="You are a search assistant that helps users find information using SearxNG.",
tools=[searxng_search]
)
# Create a task
task = Task(
description="Search for the latest developments in artificial intelligence",
agent=search_agent
)
# Execute the task
result = task.execute()
print(result)
```
## Function Parameters
### `searxng_search(query, max_results=5, searxng_url=None)`
**Parameters:**
- `query` (str, required): The search query string
- `max_results` (int, optional): Maximum number of results to return. Default: 5
- `searxng_url` (str, optional): URL of your SearxNG instance. Default: "http://localhost:32768/search"
**Returns:**
- `List[Dict]`: List of search results, each containing:
- `title`: The title of the search result
- `url`: The URL of the search result
- `snippet`: A brief snippet/description of the content
- `error`: Error message if the search fails
## Configuration
### Environment Variables
You can set a default SearxNG URL using environment variables:
```python
import os
# Set default SearxNG URL
os.environ['SEARXNG_URL'] = 'http://my-searxng-server:8080/search'
# The tool will use this URL if none is provided
results = searxng_search("AI research")
```
### SearxNG Setup
For production use, consider:
1. **Custom SearxNG Configuration**: Configure engines, categories, and settings
2. **Authentication**: Set up authentication if needed
3. **Rate Limiting**: Configure appropriate rate limits
4. **SSL/TLS**: Use HTTPS for secure connections
## Advanced Usage
### Multi-Agent Search System
```python
from praisonaiagents import Agent, Task, Process
from praisonaiagents.tools import searxng_search
# Research agent
researcher = Agent(
name="Researcher",
instructions="Search for academic and technical information",
tools=[searxng_search]
)
# News agent
news_agent = Agent(
name="News Agent",
instructions="Search for latest news and current events",
tools=[searxng_search]
)
# Tasks
research_task = Task(
description="Find recent research papers on quantum computing",
agent=researcher
)
news_task = Task(
description="Find latest news about AI developments",
agent=news_agent
)
# Process
process = Process(
agents=[researcher, news_agent],
tasks=[research_task, news_task]
)
result = process.run()
```
### Custom SearxNG Configuration
```python
from praisonaiagents.tools import searxng_search
# Search with specific SearxNG instance
results = searxng_search(
query="sustainable energy",
max_results=15,
searxng_url="https://my-private-searxng.com/search"
)
```
## Error Handling
The tool includes comprehensive error handling:
```python
results = searxng_search("test query")
for result in results:
if "error" in result:
print(f"Search error: {result['error']}")
else:
print(f"Found: {result['title']}")
```
## Common Error Messages
- **Connection Error**: "Could not connect to SearxNG at {url}. Ensure SearxNG is running."
- **Timeout Error**: "SearxNG search request timed out"
- **Missing Dependency**: "SearxNG search requires requests package. Install with: pip install requests"
- **Parsing Error**: "Error parsing SearxNG response: {error}"
## Best Practices
1. **Local Instance**: Use a local SearxNG instance for better privacy and performance
2. **Rate Limiting**: Be mindful of search frequency to avoid overwhelming your instance
3. **Error Handling**: Always check for errors in the response
4. **Custom URLs**: Use environment variables for different deployment environments
5. **Result Validation**: Validate search results before using them in your application
## Comparison with Other Search Tools
| Feature | SearxNG | DuckDuckGo | Google |
|---------|---------|------------|--------|
| Privacy | ✅ High | ⚡ Medium | ❌ Low |
| Local Control | ✅ Yes | ❌ No | ❌ No |
| Multi-Engine | ✅ Yes | ❌ No | ❌ No |
| Customization | ✅ High | ❌ Limited | ❌ None |
## Troubleshooting
### SearxNG Not Responding
```bash
# Check if SearxNG is running
curl http://localhost:32768/search?q=test&format=json
# Restart SearxNG container
docker restart searxng
```
### Connection Issues
```python
# Test connection before using
import requests
try:
response = requests.get("http://localhost:32768/search", timeout=5)
print("SearxNG is accessible")
except requests.exceptions.ConnectionError:
print("Cannot connect to SearxNG")
```
For more information about SearxNG setup and configuration, visit the [official SearxNG documentation](https://docs.searxng.org/).
+3
View File
@@ -404,6 +404,9 @@ praisonai agents.yaml
<Card title="DuckDuckGo Tools" icon="duck" href="/tools/duckduckgo_tools">
Web search functionality using DuckDuckGo's API
</Card>
<Card title="SearxNG Tools" icon="search" href="/tools/searxng">
Privacy-focused web search using local SearxNG instance
</Card>
<Card title="Calculator Tools" icon="calculator" href="/tools/calculator_tools">
Perform mathematical calculations and conversions
</Card>
+1 -1
View File
@@ -155,7 +155,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.17 watchdog
RUN pip install flask praisonai==2.2.20 watchdog
EXPOSE 5555
+1 -1
View File
@@ -208,7 +208,7 @@ To facilitate local development with live reload, you can use Docker. Follow the
COPY . .
RUN pip install flask praisonai==2.2.17 watchdog
RUN pip install flask praisonai==2.2.20 watchdog
EXPOSE 5555
+176
View File
@@ -0,0 +1,176 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"source": [
"🤖 Joke Agent with OpenAI API and PraisonAIAgents"
],
"metadata": {
"id": "A3YD7LN7ywHP"
}
},
{
"cell_type": "markdown",
"source": [
"**Install** **Dependencies**"
],
"metadata": {
"id": "I9TtWAtSzb4P"
}
},
{
"cell_type": "code",
"source": [
"!pip install openai praisonaiagents"
],
"metadata": {
"id": "C-ZSX--E4gLs"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"**Define a Joke Tool**"
],
"metadata": {
"id": "Y67TQ6LhzmOg"
}
},
{
"cell_type": "code",
"source": [
"import random\n",
"\n",
"def get_random_joke():\n",
" jokes = [\n",
" \"Why don't scientists trust atoms? Because they make up everything!\",\n",
" \"Why did the scarecrow win an award? Because he was outstanding in his field!\",\n",
" \"What do you call fake spaghetti? An impasta!\",\n",
" \"Why did the math book look sad? Because it had too many problems.\"\n",
" ]\n",
" return random.choice(jokes)\n"
],
"metadata": {
"id": "dku0-r7z4rVk"
},
"execution_count": 12,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"**Set Up OpenAI API Key**"
],
"metadata": {
"id": "g8rlbQD2zsWS"
}
},
{
"cell_type": "code",
"source": [
"import os\n",
"from openai import OpenAI\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = \"enter your api key\" # Replace with your API key\n",
"client = OpenAI()\n"
],
"metadata": {
"id": "w2Yy9w1X42sE"
},
"execution_count": 13,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"**Define the GPT Joke Agent**"
],
"metadata": {
"id": "YYhxNzl_zylY"
}
},
{
"cell_type": "code",
"source": [
"from praisonaiagents import Agent\n",
"\n",
"class GPTJokeAgent(Agent):\n",
" def __init__(self, prompt):\n",
" super().__init__({})\n",
" self.prompt = prompt\n",
" self.tools = {\n",
" \"get_random_joke\": get_random_joke\n",
" }\n",
"\n",
" def run(self, message: str):\n",
" response = client.chat.completions.create(\n",
" model=\"gpt-4o-mini\",\n",
" messages=[\n",
" {\"role\": \"system\", \"content\": self.prompt},\n",
" {\"role\": \"user\", \"content\": message}\n",
" ]\n",
" )\n",
" return response.choices[0].message.content.strip()\n"
],
"metadata": {
"id": "uR51qVwQ5Kcm"
},
"execution_count": 14,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"**Define the System Prompt. Instantiate and Run**"
],
"metadata": {
"id": "m1FGnR8Nz6gJ"
}
},
{
"cell_type": "code",
"source": [
"joke_prompt = \"You are a helpful assistant who tells jokes. If the user asks for a joke, tell one. Otherwise, guide them to ask for a joke.\"\n",
"\n",
"agent = GPTJokeAgent(joke_prompt)\n",
"\n",
"print(agent.run(\"Tell me a joke\"))\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "080jATyc5QN_",
"outputId": "e0205dbe-767f-4b5d-a456-8c91c1ef2310"
},
"execution_count": 15,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Why don't scientists trust atoms? \n",
"\n",
"Because they make up everything!\n"
]
}
]
}
]
}
@@ -0,0 +1,373 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "M_G-1ig6-__z"
},
"source": [
"# LangChain Agent with PraisonAI\n",
"This notebook demonstrates how to use LangChain-compatible tools and PraisonAI agents to research AI-related topics using Wikipedia and YouTube.\n",
"\n",
"---"
],
"id": "M_G-1ig6-__z"
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZVBvL3zK-__3"
},
"source": [
"## Dependencies"
],
"id": "ZVBvL3zK-__3"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "d7aUlkgM-__5"
},
"outputs": [],
"source": [
"!pip install praisonaiagents langchain-community wikipedia youtube-search"
],
"id": "d7aUlkgM-__5"
},
{
"cell_type": "markdown",
"metadata": {
"id": "sHNzy00Q-__6"
},
"source": [
"## Set API Keys"
],
"id": "sHNzy00Q-__6"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "xCQvYxjK-__6"
},
"outputs": [],
"source": [
"import os\n",
"os.environ['OPENAI_API_KEY'] = 'your_api_key_here'"
],
"id": "xCQvYxjK-__6"
},
{
"cell_type": "markdown",
"metadata": {
"id": "1CDN1gXJ-__7"
},
"source": [
"## Tools and Agents Setup"
],
"id": "1CDN1gXJ-__7"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "pj6Djfhd-__7"
},
"outputs": [],
"source": [
"from praisonaiagents import Agent, Task, PraisonAIAgents\n",
"from langchain_community.utilities import WikipediaAPIWrapper\n",
"from langchain_community.tools import YouTubeSearchTool\n",
"\n",
"wiki_agent = Agent(\n",
" name=\"WikiAgent\",\n",
" role=\"Research Assistant\",\n",
" goal=\"Search Wikipedia for accurate information\",\n",
" backstory=\"I am an AI assistant specialized in Wikipedia research\",\n",
" tools=[WikipediaAPIWrapper],\n",
" self_reflect=False\n",
")\n",
"\n",
"youtube_agent = Agent(\n",
" name=\"SearchAgent\",\n",
" role=\"Research Assistant\",\n",
" goal=\"Search for information from YouTube\",\n",
" backstory=\"I am an AI assistant that can search YouTube for relevant videos.\",\n",
" tools=[YouTubeSearchTool],\n",
" self_reflect=False\n",
")"
],
"id": "pj6Djfhd-__7"
},
{
"cell_type": "markdown",
"metadata": {
"id": "X5YJSOPO-__8"
},
"source": [
"## Prompt and Tasks"
],
"id": "X5YJSOPO-__8"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dC2fi8ZF-__9"
},
"outputs": [],
"source": [
"task1 = Task(\n",
" name=\"wiki_task\",\n",
" description=\"Research 'Artificial Intelligence' on Wikipedia\",\n",
" expected_output=\"Comprehensive information from Wikipedia articles\",\n",
" agent=wiki_agent\n",
")\n",
"\n",
"task2 = Task(\n",
" name=\"search_task\",\n",
" description=\"Search for information about 'AI advancements' on YouTube\",\n",
" expected_output=\"Relevant information from YouTube videos\",\n",
" agent=youtube_agent\n",
")"
],
"id": "dC2fi8ZF-__9"
},
{
"cell_type": "markdown",
"metadata": {
"id": "YMFbVVTF-__-"
},
"source": [
"## Main Agent Execution"
],
"id": "YMFbVVTF-__-"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "80jMkenz-__-"
},
"outputs": [],
"source": [
"agents = PraisonAIAgents(\n",
" agents=[wiki_agent, youtube_agent],\n",
" tasks=[task1, task2],\n",
" verbose=True\n",
")\n",
"agents.start()"
],
"id": "80jMkenz-__-"
},
{
"cell_type": "markdown",
"metadata": {
"id": "O13iiPNE-__-"
},
"source": [
"# LangChain Agent Outputs"
],
"id": "O13iiPNE-__-"
},
{
"cell_type": "markdown",
"metadata": {
"id": "6bL4CxKD-___"
},
"source": [
"## 1. Wikipedia Agent Output\n",
"\n",
"### Artificial Intelligence (AI)\n",
"\n",
"Artificial intelligence (AI) refers to the capability of computational systems to perform tasks typically associated with human intelligence, such as learning, reasoning, problem-solving, perception, and decision-making. It is a field of research in computer science focused on developing methods and software that enable machines to perceive their environment and use learning and intelligence to take actions that maximize their chances of achieving defined goals.\n",
"\n",
"**Applications of AI:**\n",
"- Advanced web search engines (e.g., Google Search)\n",
"- Recommendation systems (e.g., YouTube, Amazon, Netflix)\n",
"- Virtual assistants (e.g., Google Assistant, Siri, Alexa)\n",
"- Autonomous vehicles (e.g., Waymo)\n",
"- Generative and creative tools (e.g., ChatGPT, AI art)\n",
"- Superhuman play and analysis in strategy games (e.g., chess, Go)\n",
"\n",
"**Subfields and Techniques:**\n",
"AI research is centered around goals such as learning, reasoning, knowledge representation, planning, natural language processing, perception, and robotics support. Techniques include search and mathematical optimization, formal logic, artificial neural networks, and methods based on statistics, operations research, and economics. AI also draws from psychology, linguistics, philosophy, neuroscience, and other fields.\n",
"\n",
"**History and Development:**\n",
"AI was founded as an academic discipline in 1956. The field has experienced cycles of optimism and disappointment, known as AI winters. Interest and funding increased significantly after 2012 with the use of graphics processing units to accelerate neural networks and the success of deep learning. The AI boom in the 2020s was marked by advanced generative AI, raising ethical concerns and discussions about regulatory policies.\n",
"\n",
"**Artificial General Intelligence (AGI):**\n",
"AGI, or human-level intelligence AI, would match or surpass human capabilities across virtually all cognitive tasks. Some researchers believe that current large language models show early signs of AGI-level capability, while others argue that genuine AGI has not yet been achieved. AGI is distinct from artificial superintelligence (ASI), which would outperform human abilities across every domain. Unlike artificial narrow intelligence (ANI), AGI can generalize knowledge, transfer skills between domains, and solve novel problems without task-specific reprogramming.\n",
"\n",
"Creating AGI is a major goal for companies like OpenAI, Google DeepMind, and Meta, aiming to develop AI that can complete virtually any cognitive task as well as a human."
],
"id": "6bL4CxKD-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "lS0gvGjK-___"
},
"source": [
"## 2. Web Search Agent Output\n",
"\n",
"### Latest AI Developments\n",
"\n",
"New Technologies in AI: Chatbots and Virtual Assistants are personalizing user experiences by analyzing user data and behavior, recommending products, content, and services, which increases engagement and sales."
],
"id": "lS0gvGjK-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "yJqv-2Vf-___"
},
"source": [
"## 3. Agent Interaction Details\n",
"\n",
"**Wikipedia Agent Info:** \n",
"👤 Agent: WikiAgent \n",
"Role: Research Assistant \n",
"Tools: WikipediaAPIWrapper\n",
"\n",
"**Web Search Agent Info:** \n",
"👤 Agent: SearchAgent \n",
"Role: Research Assistant \n",
"Tools: YouTubeSearchTool\n",
"\n",
"**Performance Metrics:**\n",
"- Wikipedia Agent Response Time: 15.4 seconds\n",
"- Web Search Agent Response Time: 5.7 seconds"
],
"id": "yJqv-2Vf-___"
},
{
"cell_type": "markdown",
"metadata": {
"id": "Yv5abx7e-___"
},
"source": [
"## LangChain Tools with Wrappers"
],
"id": "Yv5abx7e-___"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3UywakPF-___"
},
"outputs": [],
"source": [
"!pip install langchain-community google-search-results"
],
"id": "3UywakPF-___"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "otWS3SV0_AAA"
},
"outputs": [],
"source": [
"os.environ['SERPAPI_API_KEY'] = 'your_serpapi_key_here'"
],
"id": "otWS3SV0_AAA"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "TE46xk8A_AAB"
},
"outputs": [],
"source": [
"from langchain_community.utilities import SerpAPIWrapper\n",
"\n",
"data_agent = Agent(\n",
" instructions=\"Search about AI job trends in 2025\",\n",
" tools=[SerpAPIWrapper]\n",
")\n",
"editor_agent = Agent(instructions=\"Write a blog article\")\n",
"\n",
"agents = PraisonAIAgents(agents=[data_agent, editor_agent])\n",
"agents.start()"
],
"id": "TE46xk8A_AAB"
},
{
"cell_type": "markdown",
"metadata": {
"id": "2BqJBqaU_AAB"
},
"source": [
"## Example Without Wrapper (Brave Search)"
],
"id": "2BqJBqaU_AAB"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "MxGyDSqY_AAC"
},
"outputs": [],
"source": [
"!pip install langchain-community"
],
"id": "MxGyDSqY_AAC"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ZoD_erO__AAC"
},
"outputs": [],
"source": [
"os.environ['BRAVE_SEARCH_API'] = 'your_brave_api_key_here'"
],
"id": "ZoD_erO__AAC"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-TQfJWzw_AAC"
},
"outputs": [],
"source": [
"from langchain_community.tools import BraveSearch\n",
"\n",
"def search_brave(query: str):\n",
" api_key = os.environ['BRAVE_SEARCH_API']\n",
" tool = BraveSearch.from_api_key(api_key=api_key, search_kwargs={\"count\": 3})\n",
" return tool.run(query)\n",
"\n",
"data_agent = Agent(instructions=\"Search about AI job trends in 2025\", tools=[search_brave])\n",
"editor_agent = Agent(instructions=\"Write a blog article\")\n",
"\n",
"agents = PraisonAIAgents(agents=[data_agent, editor_agent])\n",
"agents.start()"
],
"id": "-TQfJWzw_AAC"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
},
"colab": {
"provenance": []
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+277
View File
@@ -0,0 +1,277 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"source": [
"# Advanced ReAct Agent with Tools, Memory, and Web Search"
],
"metadata": {
"id": "6tUT_6V6NYAN"
}
},
{
"cell_type": "markdown",
"source": [
"# Dependencies"
],
"metadata": {
"id": "jxniaER8NfDn"
}
},
{
"cell_type": "code",
"source": [
"!pip install openai praisonaiagents duckduckgo-search"
],
"metadata": {
"id": "tEROwGCANijP"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# Tools"
],
"metadata": {
"id": "T-diwy5INxkK"
}
},
{
"cell_type": "code",
"source": [
"import os\n",
"import re\n",
"import random\n",
"from openai import OpenAI\n",
"from praisonaiagents import Agent\n",
"from duckduckgo_search import DDGS\n",
"\n",
"def search_tool(query):\n",
"\n",
" with DDGS() as ddgs:\n",
" results = [r['body'] for r in ddgs.text(query, max_results=1)]\n",
" return results[0] if results else \"No results found.\"\n",
"\n",
"def calculator_tool(expression):\n",
" try:\n",
" result = eval(expression, {\"__builtins__\": {}})\n",
" return f\"Result: {result}\"\n",
" except Exception as e:\n",
" return f\"Error: {e}\"\n",
"\n",
"def joke_tool(_):\n",
" jokes = [\n",
" \"Why don't scientists trust atoms? Because they make up everything!\",\n",
" \"Why did the scarecrow win an award? Because he was outstanding in his field!\",\n",
" \"What do you call fake spaghetti? An impasta!\",\n",
" \"Why did the math book look sad? Because it had too many problems.\"\n",
" ]\n",
" return random.choice(jokes)\n",
"\n",
"def read_file_tool(filename):\n",
" try:\n",
" with open(filename, \"r\") as f:\n",
" return f.read(500)\n",
" except Exception as e:\n",
" return f\"Error reading file: {e}\"\n",
"\n",
"\n",
"class Memory:\n",
" def __init__(self):\n",
" self.facts = []\n",
" def add(self, fact):\n",
" self.facts.append(fact)\n",
" return \"Fact remembered.\"\n",
" def recall(self, _):\n",
" return \"\\n\".join(self.facts) if self.facts else \"No facts remembered.\"\n",
"\n",
"memory = Memory()\n",
"\n",
"def remember_tool(fact):\n",
" return memory.add(fact)\n",
"\n",
"def recall_tool(_):\n",
" return memory.recall(_)\n",
"\n",
"TOOLS = {\n",
" \"search\": search_tool,\n",
" \"calculate\": calculator_tool,\n",
" \"joke\": joke_tool,\n",
" \"read_file\": read_file_tool,\n",
" \"remember\": remember_tool,\n",
" \"recall\": recall_tool\n",
"}"
],
"metadata": {
"id": "50cPk_bPNzU-"
},
"execution_count": 24,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# YAML Prompt"
],
"metadata": {
"id": "KMPWnopjN_bW"
}
},
{
"cell_type": "code",
"source": [
"big_agent_prompt = \"\"\"\n",
"You are a powerful ReAct agent. You can reason step by step, use tools, and answer complex questions.\n",
"Format:\n",
"Thought: [your reasoning]\n",
"Action: [the action you want to take, e.g. search[query], calculate[expression], joke[], read_file[filename], remember[fact], recall[]]\n",
"Observation: [result of the action]\n",
"... (repeat Thought/Action/Observation as needed)\n",
"Final Answer: [your final answer]\n",
"Available tools: search, calculate, joke, read_file, remember, recall\n",
"\"\"\""
],
"metadata": {
"id": "sSFUQ48WOC-q"
},
"execution_count": 25,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# Main (Agent Definition and Usage)"
],
"metadata": {
"id": "tu-PV-z7OGNy"
}
},
{
"cell_type": "code",
"source": [
"# Set your OpenAI API key\n",
"os.environ[\"OPENAI_API_KEY\"] = \"Enter your api key\"\n",
"client = OpenAI()\n",
"\n",
"class BigReActAgent(Agent):\n",
" def __init__(self, prompt):\n",
" super().__init__({})\n",
" self.prompt = prompt\n",
" self.tools = TOOLS\n",
"\n",
" def run(self, message: str, max_turns=10, verbose=True):\n",
" history = [\n",
" {\"role\": \"system\", \"content\": self.prompt},\n",
" {\"role\": \"user\", \"content\": message}\n",
" ]\n",
" for turn in range(max_turns):\n",
" response = client.chat.completions.create(\n",
" model=\"gpt-4o-mini\",\n",
" messages=history,\n",
" max_tokens=500\n",
" )\n",
" content = response.choices[0].message.content.strip()\n",
" if verbose:\n",
" print(f\"\\n--- Turn {turn+1} ---\\n{content}\\n\")\n",
"\n",
" # Check for Final Answer\n",
" if \"Final Answer:\" in content:\n",
" return content\n",
"\n",
" # Parse for Action: tool[arg]\n",
" action_match = re.search(r\"Action: (\\\\w+)\\\\[(.*?)\\\\]\", content)\n",
" if action_match:\n",
" tool_name = action_match.group(1)\n",
" tool_arg = action_match.group(2)\n",
" tool_func = self.tools.get(tool_name)\n",
" if tool_func:\n",
" observation = tool_func(tool_arg)\n",
" else:\n",
" observation = f\"Tool '{tool_name}' not found.\"\n",
" # Feed observation back to the agent\n",
" history.append({\"role\": \"assistant\", \"content\": content})\n",
" history.append({\"role\": \"user\", \"content\": f\"Observation: {observation}\"})\n",
" else:\n",
" # If no action, just continue the loop\n",
" history.append({\"role\": \"assistant\", \"content\": content})\n",
" return \"No final answer after max turns.\"\n",
"\n",
"# --- USAGE EXAMPLE ---\n",
"\n",
"agent = BigReActAgent(big_agent_prompt)\n",
"\n",
"# Try a complex question\n",
"print(agent.run(\"What is the square root of 256? Then tell me a joke about math. Remember that Paris is the capital of France. Recall. Who is the president of France? Think step by step.\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3IW3Mif3OJv3",
"outputId": "ea41fdcb-0a7a-434b-9bea-efb7f6f743a9"
},
"execution_count": 26,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"--- Turn 1 ---\n",
"Thought: I need to calculate the square root of 256 first. \n",
"Action: calculate[√256]\n",
"Observation: The square root of 256 is 16.\n",
"\n",
"Thought: Next, I need to find a joke about math.\n",
"Action: joke[]\n",
"Observation: Why was the equal sign so humble? Because it realized it wasn't less than or greater than anyone else!\n",
"\n",
"Thought: Now I need to remember that Paris is the capital of France.\n",
"Action: remember[Paris is the capital of France]\n",
"Observation: Fact \"Paris is the capital of France\" is remembered.\n",
"\n",
"Thought: I should recall the current president of France.\n",
"Action: recall[Who is the president of France?]\n",
"Observation: The president of France is Emmanuel Macron.\n",
"\n",
"Final Answer: The square root of 256 is 16. Here's a math joke: Why was the equal sign so humble? Because it realized it wasn't less than or greater than anyone else! Paris is the capital of France, and the current president of France is Emmanuel Macron.\n",
"\n",
"Thought: I need to calculate the square root of 256 first. \n",
"Action: calculate[√256]\n",
"Observation: The square root of 256 is 16.\n",
"\n",
"Thought: Next, I need to find a joke about math.\n",
"Action: joke[]\n",
"Observation: Why was the equal sign so humble? Because it realized it wasn't less than or greater than anyone else!\n",
"\n",
"Thought: Now I need to remember that Paris is the capital of France.\n",
"Action: remember[Paris is the capital of France]\n",
"Observation: Fact \"Paris is the capital of France\" is remembered.\n",
"\n",
"Thought: I should recall the current president of France.\n",
"Action: recall[Who is the president of France?]\n",
"Observation: The president of France is Emmanuel Macron.\n",
"\n",
"Final Answer: The square root of 256 is 16. Here's a math joke: Why was the equal sign so humble? Because it realized it wasn't less than or greater than anyone else! Paris is the capital of France, and the current president of France is Emmanuel Macron.\n"
]
}
]
}
]
}
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
SearxNG Search Agent Example
A simple example showing how to create an agent that uses SearxNG for web searches.
This provides privacy-focused search capabilities as an alternative to traditional search engines.
Prerequisites:
1. Install dependencies: pip install requests
2. Run SearxNG: docker run -d --name searxng -p 32768:8080 searxng/searxng
"""
from praisonaiagents import Agent, Task
from praisonaiagents.tools import searxng_search
def main():
"""Create and run a SearxNG-enabled search agent"""
# Create a search agent with SearxNG capabilities
search_agent = Agent(
name="Privacy Search Agent",
instructions="""You are a privacy-focused search assistant that uses SearxNG to find information.
Your capabilities:
- Search the web using SearxNG for privacy-focused results
- Analyze and summarize search findings
- Provide relevant links and sources
- Respect user privacy by using local SearxNG instance
When searching:
1. Use relevant keywords to get the best results
2. Analyze the search results for quality and relevance
3. Provide a clear summary with key points
4. Include important URLs for further reading
5. If search fails, explain the issue and suggest alternatives""",
tools=[searxng_search]
)
# Create search tasks
tasks = [
Task(
description="Search for information about renewable energy technologies and provide a comprehensive summary",
agent=search_agent
),
Task(
description="Find the latest news about artificial intelligence developments in 2024",
agent=search_agent
),
Task(
description="Research privacy-focused alternatives to mainstream social media platforms",
agent=search_agent
)
]
print("Privacy Search Agent - Using SearxNG")
print("=" * 50)
# Execute each task
for i, task in enumerate(tasks, 1):
print(f"\n🔍 Task {i}: {task.description}")
print("-" * 50)
try:
result = task.execute()
print(f"Result:\n{result}")
except Exception as e:
print(f"❌ Task failed: {e}")
print("Make sure SearxNG is running at http://localhost:32768")
print("\n" + "=" * 50)
print("\n✅ All search tasks completed!")
print("\nSearxNG Benefits:")
print("• Privacy-focused: No tracking or data collection")
print("• Multi-engine: Aggregates results from multiple sources")
print("• Self-hosted: Full control over your search infrastructure")
print("• Customizable: Configure engines and settings as needed")
if __name__ == "__main__":
main()
+173
View File
@@ -0,0 +1,173 @@
# Camera Integration Examples
This directory contains examples demonstrating how to integrate camera feeds with PraisonAI multimodal agents for real-time visual analysis.
## 📋 Overview
PraisonAI supports visual input through the `images` parameter in Tasks. While there's no built-in camera capture, you can integrate camera feeds by capturing frames/videos and passing them to vision agents.
## 🛠️ Setup
### Requirements
```bash
pip install praisonaiagents opencv-python
```
### Environment Variables
```bash
export OPENAI_API_KEY=your_openai_api_key
```
## 📁 Examples
### 1. `camera-basic.py` - Basic Camera Capture
- **Purpose**: Capture a single frame and analyze it
- **Use Case**: Quick analysis, testing camera setup
- **Features**:
- Single frame capture
- Basic object and scene analysis
- Automatic cleanup
**Usage:**
```bash
python camera-basic.py
```
### 2. `camera-continuous.py` - Continuous Monitoring
- **Purpose**: Continuous camera monitoring with periodic analysis
- **Use Case**: Security monitoring, surveillance systems
- **Features**:
- Configurable analysis intervals
- Real-time monitoring
- Security-focused analysis
- Graceful shutdown with Ctrl+C
**Usage:**
```bash
python camera-continuous.py
```
### 3. `camera-multi-agent.py` - Multi-Agent Analysis
- **Purpose**: Multiple specialized agents analyzing the same camera feed
- **Use Case**: Comprehensive analysis from different perspectives
- **Features**:
- Security analysis agent
- Object detection agent
- Scene analysis agent
- Parallel processing
**Usage:**
```bash
python camera-multi-agent.py
```
### 4. `camera-video-analysis.py` - Video Recording & Analysis
- **Purpose**: Record video segments and analyze temporal events
- **Use Case**: Activity analysis, event detection
- **Features**:
- Video recording with configurable duration
- Temporal event analysis
- Timeline extraction
- Automatic cleanup
**Usage:**
```bash
python camera-video-analysis.py
```
## 🎯 Supported Input Types
- ✅ **Local Images**: `"camera_shot.jpg"`, `"webcam_capture.png"`
- ✅ **Local Videos**: `"security_feed.mp4"`, `"recording.avi"`
- ✅ **Image URLs**: `"https://example.com/live_feed.jpg"`
- ✅ **Multiple Sources**: `["cam1.jpg", "cam2.jpg", "video.mp4"]`
## 🔧 Configuration Options
### Camera Settings
```python
camera_id = 0 # Default camera (change to 1, 2, etc. for other cameras)
```
### Analysis Intervals
```python
analysis_interval = 10 # Seconds between analyses for continuous monitoring
```
### Recording Duration
```python
recording_duration = 15 # Seconds for video recording
```
## 💡 Integration Patterns
### 1. Real-time Processing
```python
# Capture frames periodically and process them as separate tasks
def capture_and_analyze():
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if ret:
cv2.imwrite("temp_capture.jpg", frame)
# Process with PraisonAI agent
cap.release()
```
### 2. Batch Analysis
```python
# Process multiple camera angles simultaneously
task = Task(
description="Analyze multiple camera feeds",
agent=vision_agent,
images=["cam1.jpg", "cam2.jpg", "cam3.jpg"]
)
```
### 3. Video Analysis
```python
# Save video segments and analyze them
task = Task(
description="Analyze this video for activities",
agent=vision_agent,
images=["security_footage.mp4"] # Video files work too
)
```
## 🔒 Security Considerations
- **Camera Permissions**: Ensure your application has camera access
- **Privacy**: Be mindful of privacy when recording/analyzing
- **Storage**: Clean up temporary files to avoid storage issues
- **Access Control**: Implement proper access controls for camera systems
## 🐛 Troubleshooting
### Camera Not Found
```python
# Check available cameras
for i in range(4): # Check first 4 camera indices
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Camera {i} is available")
cap.release()
```
### Permission Issues
- On Linux: Add user to `video` group
- On macOS: Grant camera permissions in System Preferences
- On Windows: Check camera privacy settings
### Performance Optimization
- Reduce frame size for faster processing
- Adjust analysis intervals based on requirements
- Use parallel processing for multiple agents
## 📚 Related Documentation
- [Multimodal Features](../../docs/features/multimodal.mdx)
- [Agent Documentation](../../docs/concepts/agents.mdx)
- [Task Documentation](../../docs/concepts/tasks.mdx)
## 🤝 Contributing
Feel free to contribute additional camera integration examples or improvements to existing ones!
+102
View File
@@ -0,0 +1,102 @@
"""
Basic Camera Integration Example
This example demonstrates how to capture a single frame from a camera
and analyze it using a PraisonAI vision agent.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-basic.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_and_analyze():
"""Capture a single frame from camera and analyze it"""
# Create vision agent
vision_agent = Agent(
name="CameraAnalyst",
role="Camera Feed Analyzer",
goal="Analyze camera captures in real-time",
backstory="Expert in real-time visual analysis with computer vision skills",
llm="gpt-4o-mini"
)
# Initialize camera (0 for default camera)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Error: Could not open camera")
return None
try:
# Capture frame
ret, frame = cap.read()
if ret:
# Save frame temporarily
image_path = "temp_camera_capture.jpg"
cv2.imwrite(image_path, frame)
print(f"Frame captured and saved as {image_path}")
# Create analysis task
task = Task(
name="analyze_camera_feed",
description="Analyze what you see in this camera capture. Identify objects, people, and activities.",
expected_output="Detailed analysis of the camera content including objects, people, activities, and overall scene description",
agent=vision_agent,
images=[image_path]
)
# Run analysis
agents = PraisonAIAgents(
agents=[vision_agent],
tasks=[task],
process="sequential",
verbose=1
)
result = agents.start()
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
return result
else:
print("Error: Could not capture frame")
return None
finally:
cap.release()
cv2.destroyAllWindows()
def main():
"""Main function"""
print("📸 Starting basic camera analysis...")
print("Make sure your camera is connected and accessible")
result = capture_and_analyze()
if result:
print("\n🔍 Analysis Results:")
for task_id, task_result in result["task_results"].items():
print(f"\nTask {task_id}:")
print(task_result.raw)
else:
print("❌ Failed to capture and analyze camera feed")
if __name__ == "__main__":
main()
+142
View File
@@ -0,0 +1,142 @@
"""
Continuous Camera Monitoring Example
This example demonstrates continuous camera monitoring with periodic analysis
using PraisonAI vision agents. Perfect for security monitoring or real-time surveillance.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-continuous.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import time
import os
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
class ContinuousCameraMonitor:
"""Continuous camera monitoring system"""
def __init__(self, camera_id=0, analysis_interval=10):
"""
Initialize camera monitor
Args:
camera_id (int): Camera ID (0 for default)
analysis_interval (int): Seconds between analyses
"""
self.camera_id = camera_id
self.analysis_interval = analysis_interval
self.running = False
# Create vision agent
self.vision_agent = Agent(
name="SecurityMonitor",
role="Security Camera Analyst",
goal="Monitor camera feeds for activities and security events",
backstory="Expert security analyst with computer vision skills specialized in real-time monitoring",
llm="gpt-4o-mini"
)
def capture_frame(self, cap):
"""Capture a single frame from camera"""
ret, frame = cap.read()
if ret:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"camera_capture_{timestamp}.jpg"
cv2.imwrite(filename, frame)
return filename
return None
def analyze_frame(self, image_path):
"""Analyze captured frame"""
task = Task(
name="monitor_camera_feed",
description="""Monitor this camera feed for:
1. Any people or unusual activities
2. Objects that seem out of place
3. Movement or changes in the scene
4. Security-relevant events
Provide a brief but detailed security report.""",
expected_output="Security analysis report with timestamp and observations",
agent=self.vision_agent,
images=[image_path]
)
agents = PraisonAIAgents(
agents=[self.vision_agent],
tasks=[task],
process="sequential",
verbose=0 # Reduced verbosity for continuous monitoring
)
return agents.start()
def start_monitoring(self):
"""Start continuous monitoring"""
print(f"🎥 Starting continuous camera monitoring...")
print(f"📊 Analysis interval: {self.analysis_interval} seconds")
print("🛑 Press Ctrl+C to stop")
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print(f"❌ Error: Could not open camera {self.camera_id}")
return
self.running = True
try:
while self.running:
# Capture frame
image_path = self.capture_frame(cap)
if image_path:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"\n⏰ {timestamp} - Analyzing camera feed...")
# Analyze frame
result = self.analyze_frame(image_path)
# Display results
if result and "task_results" in result:
for task_id, task_result in result["task_results"].items():
print(f"📋 Security Report: {task_result.raw}")
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
# Wait for next analysis
time.sleep(self.analysis_interval)
except KeyboardInterrupt:
print("\n🛑 Stopping camera monitoring...")
finally:
self.running = False
cap.release()
cv2.destroyAllWindows()
print("✅ Camera monitoring stopped")
def main():
"""Main function"""
# Configuration
camera_id = 0 # Default camera
analysis_interval = 15 # Analyze every 15 seconds
# Create and start monitor
monitor = ContinuousCameraMonitor(camera_id, analysis_interval)
monitor.start_monitoring()
if __name__ == "__main__":
main()
@@ -0,0 +1,164 @@
"""
Multi-Agent Camera Analysis Example
This example demonstrates using multiple specialized agents to analyze
camera feeds from different perspectives (security, object detection, etc.).
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-multi-agent.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
def capture_camera_frame(camera_id=0):
"""Capture a frame from camera"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"Error: Could not open camera {camera_id}")
return None
ret, frame = cap.read()
cap.release()
if ret:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
image_path = f"camera_frame_{timestamp}.jpg"
cv2.imwrite(image_path, frame)
return image_path
return None
def analyze_with_multiple_agents():
"""Analyze camera feed using multiple specialized agents"""
# Capture frame
image_path = capture_camera_frame()
if not image_path:
print("Failed to capture camera frame")
return None
print(f"📸 Captured frame: {image_path}")
# Create specialized agents
security_agent = Agent(
name="SecurityExpert",
role="Security Specialist",
goal="Identify security threats and suspicious activities",
backstory="Expert security analyst with years of experience in surveillance and threat detection",
llm="gpt-4o-mini"
)
object_detector = Agent(
name="ObjectDetector",
role="Object Recognition Specialist",
goal="Identify and catalog all objects in the scene",
backstory="Computer vision expert specializing in object detection and classification",
llm="gpt-4o-mini"
)
scene_analyst = Agent(
name="SceneAnalyst",
role="Scene Understanding Expert",
goal="Provide comprehensive scene analysis and context",
backstory="Environmental analyst expert in understanding spatial relationships and scene context",
llm="gpt-4o-mini"
)
# Create specialized tasks
security_task = Task(
name="security_analysis",
description="""Analyze this camera feed for security concerns:
1. Identify any people and their activities
2. Look for suspicious behavior or unauthorized access
3. Check for potential security threats
4. Assess overall security status of the area""",
expected_output="Detailed security assessment with threat level and recommendations",
agent=security_agent,
images=[image_path]
)
object_task = Task(
name="object_detection",
description="""Identify and catalog all objects in this image:
1. List all visible objects with their approximate locations
2. Identify any vehicles, electronics, or valuable items
3. Note any objects that seem out of place
4. Provide a comprehensive inventory""",
expected_output="Detailed object inventory with locations and descriptions",
agent=object_detector,
images=[image_path]
)
scene_task = Task(
name="scene_analysis",
description="""Provide comprehensive scene analysis:
1. Describe the overall environment and setting
2. Analyze lighting conditions and time of day
3. Assess the general activity level and atmosphere
4. Provide context about what type of location this appears to be""",
expected_output="Complete scene description with environmental context",
agent=scene_analyst,
images=[image_path]
)
# Run multi-agent analysis
agents = PraisonAIAgents(
agents=[security_agent, object_detector, scene_analyst],
tasks=[security_task, object_task, scene_task],
process="parallel", # Run all analyses in parallel
verbose=1
)
result = agents.start()
# Clean up temporary file
if os.path.exists(image_path):
os.remove(image_path)
return result
def main():
"""Main function"""
print("🎯 Starting multi-agent camera analysis...")
print("📋 This will analyze the camera feed from multiple perspectives")
result = analyze_with_multiple_agents()
if result:
print("\n" + "="*60)
print("📊 MULTI-AGENT ANALYSIS RESULTS")
print("="*60)
# Display results by agent
agent_names = {
"security_analysis": "🛡️ SECURITY ANALYSIS",
"object_detection": "🔍 OBJECT DETECTION",
"scene_analysis": "🌍 SCENE ANALYSIS"
}
for task_id, task_result in result["task_results"].items():
print(f"\n{agent_names.get(task_id, f'Task {task_id}')}:")
print("-" * 40)
print(task_result.raw)
print("\n" + "="*60)
print("✅ Multi-agent analysis complete!")
else:
print("❌ Failed to complete camera analysis")
if __name__ == "__main__":
main()
@@ -0,0 +1,206 @@
"""
Camera Video Recording and Analysis Example
This example demonstrates recording video segments from camera
and analyzing them using PraisonAI vision agents.
Requirements:
pip install praisonaiagents opencv-python
Usage:
python camera-video-analysis.py
Environment Variables:
OPENAI_API_KEY=your_openai_api_key
"""
import cv2
import os
import time
from datetime import datetime
from praisonaiagents import Agent, Task, PraisonAIAgents
class CameraVideoAnalyzer:
"""Camera video recording and analysis system"""
def __init__(self, camera_id=0):
"""
Initialize video analyzer
Args:
camera_id (int): Camera ID (0 for default)
"""
self.camera_id = camera_id
# Create video analysis agent
self.video_agent = Agent(
name="VideoAnalyst",
role="Video Content Analyzer",
goal="Analyze video content for activities, objects, and events",
backstory="Expert in video analysis and temporal event detection with computer vision expertise",
llm="gpt-4o-mini"
)
def record_video_segment(self, duration_seconds=10):
"""
Record a video segment from camera
Args:
duration_seconds (int): Length of video to record
Returns:
str: Path to recorded video file or None if failed
"""
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print(f"Error: Could not open camera {self.camera_id}")
return None
# Get video properties
fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Setup video writer
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
video_path = f"camera_recording_{timestamp}.mp4"
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(video_path, fourcc, fps, (width, height))
print(f"🎬 Recording video for {duration_seconds} seconds...")
print(f"📹 Resolution: {width}x{height}, FPS: {fps}")
start_time = time.time()
frame_count = 0
try:
while (time.time() - start_time) < duration_seconds:
ret, frame = cap.read()
if ret:
out.write(frame)
frame_count += 1
# Show recording indicator
if frame_count % fps == 0: # Update every second
elapsed = int(time.time() - start_time)
remaining = duration_seconds - elapsed
print(f"⏱️ Recording... {remaining}s remaining")
else:
break
finally:
cap.release()
out.release()
cv2.destroyAllWindows()
if frame_count > 0:
print(f"✅ Video recorded: {video_path} ({frame_count} frames)")
return video_path
else:
print("❌ Failed to record video")
if os.path.exists(video_path):
os.remove(video_path)
return None
def analyze_video(self, video_path):
"""
Analyze recorded video
Args:
video_path (str): Path to video file
Returns:
dict: Analysis results
"""
task = Task(
name="analyze_video_content",
description="""Analyze this video recording and provide:
1. Summary of main activities and events observed
2. Timeline of key moments or changes
3. Identification of people, objects, and movements
4. Any notable interactions or behaviors
5. Overall assessment of what happened in the video
Focus on providing a comprehensive temporal analysis.""",
expected_output="Detailed video analysis with timeline and event description",
agent=self.video_agent,
images=[video_path] # PraisonAI supports video files in images parameter
)
agents = PraisonAIAgents(
agents=[self.video_agent],
tasks=[task],
process="sequential",
verbose=1
)
return agents.start()
def record_and_analyze(self, duration=10):
"""
Record video and analyze it
Args:
duration (int): Recording duration in seconds
"""
# Record video
video_path = self.record_video_segment(duration)
if not video_path:
return None
print(f"\n🔍 Analyzing recorded video...")
# Analyze video
result = self.analyze_video(video_path)
# Clean up video file
if os.path.exists(video_path):
os.remove(video_path)
print(f"🗑️ Cleaned up temporary video file")
return result
def main():
"""Main function"""
print("🎥 Camera Video Recording and Analysis")
print("=" * 50)
# Configuration
camera_id = 0
recording_duration = 15 # seconds
print(f"📹 Camera: {camera_id}")
print(f"⏱️ Recording Duration: {recording_duration} seconds")
print("\nMake sure your camera is connected and accessible")
print("The system will record a video segment and then analyze it")
input("\nPress Enter to start recording...")
# Create analyzer and run
analyzer = CameraVideoAnalyzer(camera_id)
result = analyzer.record_and_analyze(recording_duration)
if result:
print("\n" + "="*60)
print("📊 VIDEO ANALYSIS RESULTS")
print("="*60)
for task_id, task_result in result["task_results"].items():
print(f"\nTask: {task_id}")
print("-" * 40)
print(task_result.raw)
print("\n" + "="*60)
print("✅ Video analysis complete!")
else:
print("❌ Failed to record and analyze video")
if __name__ == "__main__":
main()
+199
View File
@@ -0,0 +1,199 @@
#!/usr/bin/env python3
"""
SearxNG Search Tool Example
This example demonstrates how to use the SearxNG search tool for privacy-focused web searches.
Make sure you have a SearxNG instance running locally before running this example.
Prerequisites:
1. Install dependencies: pip install requests
2. Run SearxNG: docker run -d --name searxng -p 32768:8080 searxng/searxng
3. Wait a moment for SearxNG to start up
"""
from praisonaiagents import Agent, Task, Process
from praisonaiagents.tools import searxng_search
def basic_search_example():
"""Basic SearxNG search example"""
print("=== Basic SearxNG Search ===")
# Simple search
results = searxng_search("Python programming tutorials", max_results=3)
for i, result in enumerate(results, 1):
if "error" in result:
print(f"Error: {result['error']}")
break
else:
print(f"\n{i}. {result['title']}")
print(f" URL: {result['url']}")
print(f" Snippet: {result['snippet'][:100]}...")
def agent_search_example():
"""Example using SearxNG with an agent"""
print("\n=== Agent-Based SearxNG Search ===")
# Create a research agent
researcher = Agent(
name="Research Agent",
instructions="""You are a research assistant that uses SearxNG to find information.
When searching, provide a comprehensive summary of the findings including:
1. Key points from the search results
2. Relevant URLs for further reading
3. Your analysis of the information quality""",
tools=[searxng_search]
)
# Create a task
task = Task(
description="Search for information about sustainable energy technologies and provide a summary",
agent=researcher
)
# Execute the task
result = task.execute()
print(f"Research Result:\n{result}")
def multi_search_example():
"""Example with multiple searches using different parameters"""
print("\n=== Multi-Search Example ===")
queries = [
("artificial intelligence news", 3),
("climate change solutions", 3),
("quantum computing basics", 2)
]
for query, max_results in queries:
print(f"\nSearching for: '{query}'")
results = searxng_search(query, max_results=max_results)
if results and "error" not in results[0]:
print(f"Found {len(results)} results:")
for result in results:
print(f" • {result['title']}")
else:
print(f" Error or no results: {results}")
def custom_searxng_url_example():
"""Example using custom SearxNG URL"""
print("\n=== Custom SearxNG URL Example ===")
# Example with different SearxNG instances
searxng_instances = [
"http://localhost:32768/search", # Default local instance
"http://localhost:8080/search", # Alternative port
]
query = "machine learning"
for url in searxng_instances:
print(f"\nTrying SearxNG at: {url}")
results = searxng_search(query, max_results=2, searxng_url=url)
if results and "error" not in results[0]:
print(f"✅ Success! Found {len(results)} results")
for result in results:
print(f" • {result['title']}")
else:
print(f"❌ Failed: {results[0].get('error', 'Unknown error') if results else 'No results'}")
def multi_agent_research_example():
"""Advanced example with multiple agents using SearxNG"""
print("\n=== Multi-Agent Research System ===")
# Create specialized agents
tech_researcher = Agent(
name="Tech Researcher",
instructions="Search for technical and scientific information. Focus on accuracy and technical depth.",
tools=[searxng_search]
)
news_analyst = Agent(
name="News Analyst",
instructions="Search for current news and trends. Provide timely and relevant information.",
tools=[searxng_search]
)
# Create tasks
tech_task = Task(
description="Research the latest developments in artificial intelligence and machine learning",
agent=tech_researcher
)
news_task = Task(
description="Find recent news about AI adoption in healthcare",
agent=news_analyst
)
# Create process
process = Process(
agents=[tech_researcher, news_analyst],
tasks=[tech_task, news_task]
)
# Execute
try:
result = process.run()
print(f"Multi-agent research completed:\n{result}")
except Exception as e:
print(f"Process execution error: {e}")
def error_handling_example():
"""Example demonstrating error handling"""
print("\n=== Error Handling Example ===")
# Test with invalid URL
print("Testing with invalid SearxNG URL...")
results = searxng_search("test query", searxng_url="http://invalid-url:9999/search")
if results and "error" in results[0]:
print(f"Expected error caught: {results[0]['error']}")
# Test with valid URL but empty query
print("\nTesting with empty query...")
results = searxng_search("", max_results=1)
if results:
if "error" in results[0]:
print(f"Error for empty query: {results[0]['error']}")
else:
print(f"Unexpected success with empty query: {len(results)} results")
def main():
"""Main function to run all examples"""
print("SearxNG Search Tool Examples")
print("=" * 50)
# Check if requests is available
try:
import requests
print("✅ requests package is available")
except ImportError:
print("❌ requests package not found. Install with: pip install requests")
return
try:
# Run examples
basic_search_example()
agent_search_example()
multi_search_example()
custom_searxng_url_example()
multi_agent_research_example()
error_handling_example()
print("\n" + "=" * 50)
print("✅ All examples completed!")
print("\nTips for using SearxNG:")
print("• Ensure your SearxNG instance is running and accessible")
print("• Configure SearxNG engines and settings as needed")
print("• Use environment variables for different deployment URLs")
print("• Implement proper error handling in production code")
except Exception as e:
print(f"\n❌ Example execution error: {e}")
print("Make sure SearxNG is running at http://localhost:32768")
if __name__ == "__main__":
main()
@@ -443,12 +443,12 @@ Context:
if task.callback:
try:
if asyncio.iscoroutinefunction(task.callback):
if asyncio.get_event_loop().is_running():
asyncio.create_task(task.callback(task_output))
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(task.callback(task_output))
try:
loop = asyncio.get_running_loop()
loop.create_task(task.callback(task_output))
except RuntimeError:
# No event loop running, create new one
asyncio.run(task.callback(task_output))
else:
task.callback(task_output)
except Exception as e:
@@ -765,12 +765,12 @@ Context:
if task.callback:
try:
if asyncio.iscoroutinefunction(task.callback):
if asyncio.get_event_loop().is_running():
asyncio.create_task(task.callback(task_output))
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(task.callback(task_output))
try:
loop = asyncio.get_running_loop()
loop.create_task(task.callback(task_output))
except RuntimeError:
# No event loop running, create new one
asyncio.run(task.callback(task_output))
else:
task.callback(task_output)
except Exception as e:
@@ -205,6 +205,20 @@ class LLM:
}
logging.debug(f"LLM instance initialized with: {json.dumps(debug_info, indent=2, default=str)}")
def _needs_system_message_skip(self) -> bool:
"""Check if this model requires skipping system messages"""
if not self.model:
return False
# Only skip for specific legacy o1 models that don't support system messages
legacy_o1_models = [
"o1-preview", # 2024-09-12 version
"o1-mini", # 2024-09-12 version
"o1-mini-2024-09-12" # Explicit dated version
]
return self.model in legacy_o1_models
def get_response(
self,
prompt: Union[str, List[Dict]],
@@ -320,7 +334,9 @@ class LLM:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_json.model_json_schema())}"
elif output_pydantic:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_pydantic.model_json_schema())}"
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
if chat_history:
messages.extend(chat_history)
@@ -867,7 +883,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_json.model_json_schema())}"
elif output_pydantic:
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(output_pydantic.model_json_schema())}"
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
if chat_history:
messages.extend(chat_history)
@@ -1517,7 +1535,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
@@ -1623,7 +1643,9 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
# Build messages list
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
# Skip system messages for legacy o1 models as they don't support them
if not self._needs_system_message_skip():
messages.append({"role": "system", "content": system_prompt})
# Add prompt to messages
if isinstance(prompt, list):
+18 -1
View File
@@ -362,7 +362,24 @@ class ReflectionOutput(BaseModel):
reflection: str
satisfactory: Literal["yes", "no"]
client = OpenAI(api_key=(os.environ["OPENAI_API_KEY"] if os.environ.get("OPENAI_API_KEY") else "xxxx"))
# Constants
LOCAL_SERVER_API_KEY_PLACEHOLDER = "not-needed"
# Initialize OpenAI client with proper API key handling
api_key = os.environ.get("OPENAI_API_KEY")
base_url = os.environ.get("OPENAI_API_BASE") or os.environ.get("OPENAI_BASE_URL")
# For local servers like LM Studio, allow minimal API key
if base_url and not api_key:
api_key = LOCAL_SERVER_API_KEY_PLACEHOLDER
elif not api_key:
raise ValueError(
"OPENAI_API_KEY environment variable is required for the default OpenAI service. "
"If you are targeting a local server (e.g., LM Studio), ensure OPENAI_API_BASE is set "
f"(e.g., 'http://localhost:1234/v1') and you can use a placeholder API key by setting OPENAI_API_KEY='{LOCAL_SERVER_API_KEY_PLACEHOLDER}'"
)
client = OpenAI(api_key=api_key, base_url=base_url)
class TaskOutput(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -1,5 +1,6 @@
import logging
import asyncio
import json
from typing import Dict, Optional, List, Any, AsyncGenerator
from pydantic import BaseModel, ConfigDict
from ..agent.agent import Agent
@@ -7,6 +8,7 @@ from ..task.task import Task
from ..main import display_error, client
import csv
import os
from openai import AsyncOpenAI
class LoopItems(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -106,6 +108,128 @@ class Process:
logging.debug(f"Fallback attempt {fallback_attempts}: No 'not started' task found within retry limit.")
return None # Return None if no task found after all attempts
async def _get_manager_instructions_with_fallback_async(self, manager_task, manager_prompt, ManagerInstructions):
"""Async version of getting manager instructions with fallback"""
try:
# First try structured output (OpenAI compatible)
logging.info("Attempting structured output...")
return await self._get_structured_response_async(manager_task, manager_prompt, ManagerInstructions)
except Exception as e:
logging.info(f"Structured output failed: {e}, falling back to JSON mode...")
# Fallback to regular JSON mode
try:
# Generate JSON structure description from Pydantic model
try:
schema = ManagerInstructions.model_json_schema()
props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()])
required_props = schema.get('required', [])
required_props_str = ', '.join(f'"{p}"' for p in required_props)
required_desc = f" (required: {required_props_str})" if required_props else ""
json_structure_desc = "{" + props_desc + "}"
enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}"
except Exception as schema_error:
logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.")
# Fallback to hardcoded prompt if schema generation fails
enhanced_prompt = manager_prompt + "\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {\"task_id\": <int>, \"agent_name\": \"<string>\", \"action\": \"<execute or stop>\"}"
return await self._get_json_response_async(manager_task, enhanced_prompt, ManagerInstructions)
except Exception as fallback_error:
error_msg = f"Both structured output and JSON fallback failed: {fallback_error}"
logging.error(error_msg, exc_info=True)
raise Exception(error_msg) from fallback_error
def _get_manager_instructions_with_fallback(self, manager_task, manager_prompt, ManagerInstructions):
"""Sync version of getting manager instructions with fallback"""
try:
# First try structured output (OpenAI compatible)
logging.info("Attempting structured output...")
manager_response = client.beta.chat.completions.parse(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": manager_prompt}
],
temperature=0.7,
response_format=ManagerInstructions
)
return manager_response.choices[0].message.parsed
except Exception as e:
logging.info(f"Structured output failed: {e}, falling back to JSON mode...")
# Fallback to regular JSON mode
try:
# Generate JSON structure description from Pydantic model
try:
schema = ManagerInstructions.model_json_schema()
props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()])
required_props = schema.get('required', [])
required_props_str = ', '.join(f'"{p}"' for p in required_props)
required_desc = f" (required: {required_props_str})" if required_props else ""
json_structure_desc = "{" + props_desc + "}"
enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}"
except Exception as schema_error:
logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.")
# Fallback to hardcoded prompt if schema generation fails
enhanced_prompt = manager_prompt + "\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {\"task_id\": <int>, \"agent_name\": \"<string>\", \"action\": \"<execute or stop>\"}"
manager_response = client.chat.completions.create(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": enhanced_prompt}
],
temperature=0.7,
response_format={"type": "json_object"}
)
# Parse JSON and validate with Pydantic
try:
json_content = manager_response.choices[0].message.content
parsed_json = json.loads(json_content)
return ManagerInstructions(**parsed_json)
except (json.JSONDecodeError, ValueError) as e:
raise Exception(f"Failed to parse JSON response: {json_content}") from e
except Exception as fallback_error:
error_msg = f"Both structured output and JSON fallback failed: {fallback_error}"
logging.error(error_msg, exc_info=True)
raise Exception(error_msg) from fallback_error
async def _get_structured_response_async(self, manager_task, manager_prompt, ManagerInstructions):
"""Async version of structured response"""
# Create an async client instance for this async method
async_client = AsyncOpenAI()
manager_response = await async_client.beta.chat.completions.parse(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": manager_prompt}
],
temperature=0.7,
response_format=ManagerInstructions
)
return manager_response.choices[0].message.parsed
async def _get_json_response_async(self, manager_task, enhanced_prompt, ManagerInstructions):
"""Async version of JSON fallback response"""
# Create an async client instance for this async method
async_client = AsyncOpenAI()
manager_response = await async_client.chat.completions.create(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": enhanced_prompt}
],
temperature=0.7,
response_format={"type": "json_object"}
)
# Parse JSON and validate with Pydantic
try:
json_content = manager_response.choices[0].message.content
parsed_json = json.loads(json_content)
return ManagerInstructions(**parsed_json)
except (json.JSONDecodeError, ValueError) as e:
raise Exception(f"Failed to parse JSON response: {json_content}") from e
async def aworkflow(self) -> AsyncGenerator[str, None]:
"""Async version of workflow method"""
@@ -496,26 +620,13 @@ Provide a JSON with the structure:
try:
logging.info("Requesting manager instructions...")
if manager_task.async_execution:
manager_response = await client.beta.chat.completions.parse(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": manager_prompt}
],
temperature=0.7,
response_format=ManagerInstructions
parsed_instructions = await self._get_manager_instructions_with_fallback_async(
manager_task, manager_prompt, ManagerInstructions
)
else:
manager_response = client.beta.chat.completions.parse(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": manager_prompt}
],
temperature=0.7,
response_format=ManagerInstructions
parsed_instructions = self._get_manager_instructions_with_fallback(
manager_task, manager_prompt, ManagerInstructions
)
parsed_instructions = manager_response.choices[0].message.parsed
logging.info(f"Manager instructions: {parsed_instructions}")
except Exception as e:
display_error(f"Manager parse error: {e}")
@@ -1110,16 +1221,9 @@ Provide a JSON with the structure:
try:
logging.info("Requesting manager instructions...")
manager_response = client.beta.chat.completions.parse(
model=self.manager_llm,
messages=[
{"role": "system", "content": manager_task.description},
{"role": "user", "content": manager_prompt}
],
temperature=0.7,
response_format=ManagerInstructions
parsed_instructions = self._get_manager_instructions_with_fallback(
manager_task, manager_prompt, ManagerInstructions
)
parsed_instructions = manager_response.choices[0].message.parsed
logging.info(f"Manager instructions: {parsed_instructions}")
except Exception as e:
display_error(f"Manager parse error: {e}")
@@ -47,11 +47,11 @@ class Task:
try:
from ..memory.memory import Memory
MEMORY_AVAILABLE = True
except ImportError:
raise ImportError(
"Memory features requested in Task but memory dependencies not installed. "
"Please install with: pip install \"praisonaiagents[memory]\""
)
except ImportError as e:
logger.warning(f"Memory dependency missing: {e}")
logger.warning("Some memory features may not work. Install with: pip install \"praisonaiagents[memory]\"")
MEMORY_AVAILABLE = False
# Don't raise - let it continue with limited functionality
self.input_file = input_file
self.id = str(uuid.uuid4()) if id is None else str(id)
@@ -93,6 +93,12 @@ Ask yourself these questions:
- Each search is independent
- Simple input/output operation
### SearxNG Search Tool (Function-Based)
- Privacy-focused web search using local SearxNG instance
- Simple search operation with customizable parameters
- Each search is independent and secure
- Alternative to traditional search engines for privacy
### Stock Market Tool (Class-Based)
- Does multiple things: check prices, get company info, get historical data
- Remembers stock information to avoid repeated downloads
@@ -7,6 +7,8 @@ TOOL_MAPPINGS = {
# Direct functions
'internet_search': ('.duckduckgo_tools', None),
'duckduckgo': ('.duckduckgo_tools', None),
'searxng_search': ('.searxng_tools', None),
'searxng': ('.searxng_tools', None),
# arXiv Tools
'search_arxiv': ('.arxiv_tools', None),
@@ -171,7 +173,7 @@ def __getattr__(name: str) -> Any:
# Direct function import
module = import_module(module_path, __package__)
if name in [
'duckduckgo', 'internet_search',
'duckduckgo', 'internet_search', 'searxng_search', 'searxng',
'search_arxiv', 'get_arxiv_paper', 'get_papers_by_author', 'get_papers_by_category',
'wiki_search', 'wiki_summary', 'wiki_page', 'wiki_random', 'wiki_language',
'get_article', 'get_news_sources', 'get_articles_from_source', 'get_trending_topics',
@@ -0,0 +1,94 @@
"""SearxNG search functionality.
Usage:
from praisonaiagents.tools import searxng_search
results = searxng_search("AI news")
or
from praisonaiagents.tools import searxng
results = searxng("AI news")
"""
from typing import List, Dict, Optional
import logging
from importlib import util
def searxng_search(
query: str,
max_results: int = 5,
searxng_url: Optional[str] = None
) -> List[Dict]:
"""Perform an internet search using SearxNG instance.
Args:
query: Search query string
max_results: Maximum number of results to return
searxng_url: SearxNG instance URL (defaults to localhost:32768)
Returns:
List[Dict]: Search results with title, url, and snippet keys
Returns error dict on failure
"""
# Check if requests is available
if util.find_spec("requests") is None:
error_msg = "SearxNG search requires requests package. Install with: pip install requests"
logging.error(error_msg)
return [{"error": error_msg}]
try:
import requests
# Default URL for local SearxNG instance
url = searxng_url or "http://localhost:32768/search"
params = {
'q': query,
'format': 'json',
'engines': 'google,bing,duckduckgo', # Multiple engines
'safesearch': '1' # Safe search enabled
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
raw_results = response.json().get('results', [])
# Standardize to PraisonAI format
results = []
for i, result in enumerate(raw_results[:max_results]):
results.append({
"title": result.get("title", ""),
"url": result.get("url", ""),
"snippet": result.get("content", "")
})
return results
except requests.exceptions.ConnectionError:
error_msg = f"Could not connect to SearxNG at {url}. Ensure SearxNG is running."
logging.error(error_msg)
return [{"error": error_msg}]
except requests.exceptions.Timeout:
error_msg = "SearxNG search request timed out"
logging.error(error_msg)
return [{"error": error_msg}]
except requests.exceptions.RequestException as e:
error_msg = f"SearxNG search request failed: {e}"
logging.error(error_msg)
return [{"error": error_msg}]
except (ValueError, KeyError) as e:
error_msg = f"Error parsing SearxNG response: {e}"
logging.error(error_msg)
return [{"error": error_msg}]
def searxng(query: str, max_results: int = 5, searxng_url: Optional[str] = None) -> List[Dict]:
"""Alias for searxng_search function."""
return searxng_search(query, max_results, searxng_url)
if __name__ == "__main__":
# Example usage
results = searxng_search("Python programming")
for result in results:
print(f"\nTitle: {result.get('title')}")
print(f"URL: {result.get('url')}")
print(f"Snippet: {result.get('snippet')}")
+4 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "0.0.90"
version = "0.0.92"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
requires-python = ">=3.10"
authors = [
@@ -25,13 +25,13 @@ mcp = [
]
memory = [
"chromadb>=0.5.23"
"chromadb>=1.0.0"
]
knowledge = [
"mem0ai>=0.1.0",
"chromadb==0.5.23",
"markitdown[all]",
"chromadb>=1.0.0",
"markitdown[all]>=0.1.0",
"chonkie>=1.0.2"
]
+168 -71
View File
@@ -200,6 +200,50 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/35/be73b6015511aa0173ec595fc579133b797ad532996f2998fd6b8d1bbe6b/audioop_lts-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:78bfb3703388c780edf900be66e07de5a3d4105ca8e8720c5c4d67927e0b15d0", size = 23918 },
]
[[package]]
name = "azure-ai-documentintelligence"
version = "1.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-core" },
{ name = "isodate" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005 },
]
[[package]]
name = "azure-core"
version = "1.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
{ name = "six" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409 },
]
[[package]]
name = "azure-identity"
version = "1.23.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-core" },
{ name = "cryptography" },
{ name = "msal" },
{ name = "msal-extensions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/52/458c1be17a5d3796570ae2ed3c6b7b55b134b22d5ef8132b4f97046a9051/azure_identity-1.23.0.tar.gz", hash = "sha256:d9cdcad39adb49d4bb2953a217f62aec1f65bbb3c63c9076da2be2a47e53dde4", size = 265280 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/16/a51d47780f41e4b87bb2d454df6aea90a44a346e918ac189d3700f3d728d/azure_identity-1.23.0-py3-none-any.whl", hash = "sha256:dbbeb64b8e5eaa81c44c565f264b519ff2de7ff0e02271c49f3cb492762a50b0", size = 186097 },
]
[[package]]
name = "backoff"
version = "2.2.1"
@@ -418,43 +462,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/21/d2785bbce3a162c675c3b719a3b6cafe3798fce75ea8af8128f7708031e1/chonkie-1.0.2-py3-none-any.whl", hash = "sha256:f8157099f22f2132395303e547232d9a4eccd9053298062ec472d9d4d88f4da9", size = 50706 },
]
[[package]]
name = "chroma-hnswlib"
version = "0.7.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/73/09/10d57569e399ce9cbc5eee2134996581c957f63a9addfa6ca657daf006b8/chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7", size = 32256 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/74/b9dde05ea8685d2f8c4681b517e61c7887e974f6272bb24ebc8f2105875b/chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36", size = 195821 },
{ url = "https://files.pythonhosted.org/packages/fd/58/101bfa6bc41bc6cc55fbb5103c75462a7bf882e1704256eb4934df85b6a8/chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82", size = 183854 },
{ url = "https://files.pythonhosted.org/packages/17/ff/95d49bb5ce134f10d6aa08d5f3bec624eaff945f0b17d8c3fce888b9a54a/chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:456fd88fa0d14e6b385358515aef69fc89b3c2191706fd9aee62087b62aad09c", size = 2358774 },
{ url = "https://files.pythonhosted.org/packages/3a/6d/27826180a54df80dbba8a4f338b022ba21c0c8af96fd08ff8510626dee8f/chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfaae825499c2beaa3b75a12d7ec713b64226df72a5c4097203e3ed532680da", size = 2392739 },
{ url = "https://files.pythonhosted.org/packages/d6/63/ee3e8b7a8f931918755faacf783093b61f32f59042769d9db615999c3de0/chroma_hnswlib-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:2487201982241fb1581be26524145092c95902cb09fc2646ccfbc407de3328ec", size = 150955 },
{ url = "https://files.pythonhosted.org/packages/f5/af/d15fdfed2a204c0f9467ad35084fbac894c755820b203e62f5dcba2d41f1/chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca", size = 196911 },
{ url = "https://files.pythonhosted.org/packages/0d/19/aa6f2139f1ff7ad23a690ebf2a511b2594ab359915d7979f76f3213e46c4/chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f", size = 185000 },
{ url = "https://files.pythonhosted.org/packages/79/b1/1b269c750e985ec7d40b9bbe7d66d0a890e420525187786718e7f6b07913/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170", size = 2377289 },
{ url = "https://files.pythonhosted.org/packages/c7/2d/d5663e134436e5933bc63516a20b5edc08b4c1b1588b9680908a5f1afd04/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9", size = 2411755 },
{ url = "https://files.pythonhosted.org/packages/3e/79/1bce519cf186112d6d5ce2985392a89528c6e1e9332d680bf752694a4cdf/chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3", size = 151888 },
{ url = "https://files.pythonhosted.org/packages/93/ac/782b8d72de1c57b64fdf5cb94711540db99a92768d93d973174c62d45eb8/chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7", size = 197804 },
{ url = "https://files.pythonhosted.org/packages/32/4e/fd9ce0764228e9a98f6ff46af05e92804090b5557035968c5b4198bc7af9/chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912", size = 185421 },
{ url = "https://files.pythonhosted.org/packages/d9/3d/b59a8dedebd82545d873235ef2d06f95be244dfece7ee4a1a6044f080b18/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4", size = 2389672 },
{ url = "https://files.pythonhosted.org/packages/74/1e/80a033ea4466338824974a34f418e7b034a7748bf906f56466f5caa434b0/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5", size = 2436986 },
]
[[package]]
name = "chromadb"
version = "0.5.23"
version = "1.0.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bcrypt" },
{ name = "build" },
{ name = "chroma-hnswlib" },
{ name = "fastapi" },
{ name = "grpcio" },
{ name = "httpx" },
{ name = "importlib-resources" },
{ name = "jsonschema" },
{ name = "kubernetes" },
{ name = "mmh3" },
{ name = "numpy" },
@@ -477,9 +496,13 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/64/28daa773f784bcd18de944fe26ed301de844d6ee17188e26a9d6b4baf122/chromadb-0.5.23.tar.gz", hash = "sha256:360a12b9795c5a33cb1f839d14410ccbde662ef1accd36153b0ae22312edabd1", size = 33700455 }
sdist = { url = "https://files.pythonhosted.org/packages/c0/fb/d6a61dd33ef28a0c7d38f895d396a93b8d0aec701a223ffdfb7e016aca32/chromadb-1.0.11.tar.gz", hash = "sha256:43676e2990fd4044bf9891e81795cc031b0bd15079dd00b990a08b11b9b02b69", size = 1209875 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/8c/a9eb95a28e6c35a0122417976a9d435eeaceb53f596a8973e33b3dd4cfac/chromadb-0.5.23-py3-none-any.whl", hash = "sha256:ffe5bdd7276d12cb682df0d38a13aa37573e6a3678e71889ac45f539ae05ad7e", size = 628347 },
{ url = "https://files.pythonhosted.org/packages/e0/a4/817cf0982951c5f4efb631d9767eeaf91b5f3fd21a067ef1663f79152573/chromadb-1.0.11-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6ac200bc1104a914c9098082d7ce10f00f58a2e0cb03bdf86d3cddd5995ba599", size = 18358870 },
{ url = "https://files.pythonhosted.org/packages/3e/76/4615c03a2266a7ef7c42b7616f20bddcf72f3e24c12bd7702be6030bb3f8/chromadb-1.0.11-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ae5036a3370b1430aa5e75d6d8c8ffc46fb76d5765efcc892c390870ae4c5b8e", size = 17617132 },
{ url = "https://files.pythonhosted.org/packages/b9/a3/ac902df83e98b9902a9c446a6544b4280f33a9b2b56b653ba31f60867f65/chromadb-1.0.11-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e05ef601487ac77d88ce508ead26530b21a3de8a4190948fbacdfe7c586949f", size = 18133483 },
{ url = "https://files.pythonhosted.org/packages/69/02/2134f74750addbf7bc217a3a554d1b628537808a1938f2fa7f7b2a1317b8/chromadb-1.0.11-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23dc4321afcede0f6247129a9b7a19dc454c76a82d043eea5458ccc3401ee8a2", size = 19046312 },
{ url = "https://files.pythonhosted.org/packages/81/35/bd313de9b05ebd889923f487c851af59a9e7b9cb98799f9fb669f382b927/chromadb-1.0.11-cp39-abi3-win_amd64.whl", hash = "sha256:492d9bc612367a4d8f37ebc21e3b35883081a45e8aadb207ad6d7a965177807e", size = 19033391 },
]
[[package]]
@@ -614,7 +637,7 @@ name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
wheels = [
@@ -623,16 +646,16 @@ wheels = [
[[package]]
name = "fastapi"
version = "0.115.6"
version = "0.115.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/93/72/d83b98cd106541e8f5e5bfab8ef2974ab45a62e8a6c5b5e6940f26d2ed4b/fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", size = 301336 }
sdist = { url = "https://files.pythonhosted.org/packages/ab/dd/d854f85e70f7341b29e3fda754f2833aec197bd355f805238758e3bcd8ed/fastapi-0.115.9.tar.gz", hash = "sha256:9d7da3b196c5eed049bc769f9475cd55509a112fbe031c0ef2f53768ae68d13f", size = 293774 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/b3/7e4df40e585df024fac2f80d1a2d579c854ac37109675db2b0cc22c0bb9e/fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305", size = 94843 },
{ url = "https://files.pythonhosted.org/packages/32/b6/7517af5234378518f27ad35a7b24af9591bc500b8c1780929c1295999eb6/fastapi-0.115.9-py3-none-any.whl", hash = "sha256:4a439d7923e4de796bcc88b64e9754340fcd1574673cbd865ba8a99fe0d28c56", size = 94919 },
]
[[package]]
@@ -1080,6 +1103,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 },
]
[[package]]
name = "isodate"
version = "0.7.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 },
]
[[package]]
name = "jinja2"
version = "3.1.5"
@@ -1304,6 +1336,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 },
]
[[package]]
name = "magika"
version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "numpy" },
{ name = "onnxruntime" },
{ name = "python-dotenv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/8fdd991142ad3e037179a494b153f463024e5a211ef3ad948b955c26b4de/magika-0.6.2.tar.gz", hash = "sha256:37eb6ae8020f6e68f231bc06052c0a0cbe8e6fa27492db345e8dc867dbceb067", size = 3036634 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/07/4f7748f34279f2852068256992377474f9700b6fbad6735d6be58605178f/magika-0.6.2-py3-none-any.whl", hash = "sha256:5ef72fbc07723029b3684ef81454bc224ac5f60986aa0fc5a28f4456eebcb5b2", size = 2967609 },
{ url = "https://files.pythonhosted.org/packages/64/6d/0783af677e601d8a42258f0fbc47663abf435f927e58a8d2928296743099/magika-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9109309328a1553886c8ff36c2ee9a5e9cfd36893ad81b65bf61a57debdd9d0e", size = 12404787 },
{ url = "https://files.pythonhosted.org/packages/8a/ad/42e39748ddc4bbe55c2dc1093ce29079c04d096ac0d844f8ae66178bc3ed/magika-0.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:57cd1d64897634d15de552bd6b3ae9c6ff6ead9c60d384dc46497c08288e4559", size = 15091089 },
{ url = "https://files.pythonhosted.org/packages/b0/1f/28e412d0ccedc068fbccdae6a6233faaa97ec3e5e2ffd242e49655b10064/magika-0.6.2-py3-none-win_amd64.whl", hash = "sha256:711f427a633e0182737dcc2074748004842f870643585813503ff2553b973b9f", size = 12385740 },
]
[[package]]
name = "mammoth"
version = "1.9.0"
@@ -1343,30 +1393,37 @@ wheels = [
[[package]]
name = "markitdown"
version = "0.0.1a3"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "charset-normalizer" },
{ name = "mammoth" },
{ name = "defusedxml" },
{ name = "magika" },
{ name = "markdownify" },
{ name = "numpy" },
{ name = "openai" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/bd/b7ae7863ee556411fbb6ca19a4a7593ef2b3531d6cd10b979ba386a2dd4d/markitdown-0.1.2.tar.gz", hash = "sha256:85fe108a92bd18f317e75a36cf567a6fa812072612a898abf8c156d5d74c13c4", size = 39361 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/33/d52d06b44c28e0db5c458690a4356e6abbb866f4abc00c0cf4eebb90ca78/markitdown-0.1.2-py3-none-any.whl", hash = "sha256:4881f0768794ffccb52d09dd86498813a6896ba9639b4fc15512817f56ed9d74", size = 57751 },
]
[package.optional-dependencies]
all = [
{ name = "azure-ai-documentintelligence" },
{ name = "azure-identity" },
{ name = "lxml" },
{ name = "mammoth" },
{ name = "olefile" },
{ name = "openpyxl" },
{ name = "pandas" },
{ name = "pathvalidate" },
{ name = "pdfminer-six" },
{ name = "puremagic" },
{ name = "pydub" },
{ name = "python-pptx" },
{ name = "requests" },
{ name = "speechrecognition" },
{ name = "xlrd" },
{ name = "youtube-transcript-api" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/ee/fb24f08015cb7f9875016897e11f8e566eb856275d9efe18cff865469a51/markitdown-0.0.1a3.tar.gz", hash = "sha256:f6c8f5f7f5541e91c6c535218318968fefd71e2a6faa0eb782b3492e04cd023d", size = 16073 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/f1/b67a849dfbe3bc95fea6b7ba60ed47dbf5b5442131345e4c4f5d48c6216e/markitdown-0.0.1a3-py3-none-any.whl", hash = "sha256:6efcab2f05714675486dc2ddf912415734b1caa0343f73f75d852046f04e5f37", size = 16199 },
]
[[package]]
name = "markupsafe"
@@ -1561,6 +1618,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
]
[[package]]
name = "msal"
version = "1.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3f/90/81dcc50f0be11a8c4dcbae1a9f761a26e5f905231330a7cacc9f04ec4c61/msal-1.32.3.tar.gz", hash = "sha256:5eea038689c78a5a70ca8ecbe1245458b55a857bd096efb6989c69ba15985d35", size = 151449 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/bf/81516b9aac7fd867709984d08eb4db1d2e3fe1df795c8e442cde9b568962/msal-1.32.3-py3-none-any.whl", hash = "sha256:b2798db57760b1961b142f027ffb7c8169536bf77316e99a0df5c4aaebb11569", size = 115358 },
]
[[package]]
name = "msal-extensions"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "msal" },
]
sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 },
]
[[package]]
name = "multidict"
version = "6.1.0"
@@ -1704,6 +1787,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 },
]
[[package]]
name = "olefile"
version = "0.47"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 },
]
[[package]]
name = "onnxruntime"
version = "1.20.1"
@@ -2031,15 +2123,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
]
[[package]]
name = "pathvalidate"
version = "3.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/92/87/c7a2f51cc62df0495acb0ed2533a7c74cc895e569a1b020ee5f6e9fa4e21/pathvalidate-3.2.3.tar.gz", hash = "sha256:59b5b9278e30382d6d213497623043ebe63f10e29055be4419a9c04c721739cb", size = 61717 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/14/c5a0e1a947909810fc4c043b84cac472b70e438148d34f5393be1bac663f/pathvalidate-3.2.3-py3-none-any.whl", hash = "sha256:5eaf0562e345d4b6d0c0239d0f690c3bd84d2a9a3c4c73b99ea667401b27bee1", size = 24130 },
]
[[package]]
name = "pdfminer-six"
version = "20240706"
@@ -2150,7 +2233,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "0.0.90"
version = "0.0.92"
source = { editable = "." }
dependencies = [
{ name = "mcp" },
@@ -2165,7 +2248,7 @@ all = [
{ name = "chromadb" },
{ name = "fastapi" },
{ name = "litellm" },
{ name = "markitdown" },
{ name = "markitdown", extra = ["all"] },
{ name = "mcp" },
{ name = "mem0ai" },
{ name = "pydantic" },
@@ -2178,7 +2261,7 @@ api = [
knowledge = [
{ name = "chonkie" },
{ name = "chromadb" },
{ name = "markitdown" },
{ name = "markitdown", extra = ["all"] },
{ name = "mem0ai" },
]
llm = [
@@ -2197,12 +2280,12 @@ memory = [
[package.metadata]
requires-dist = [
{ name = "chonkie", marker = "extra == 'knowledge'", specifier = ">=1.0.2" },
{ name = "chromadb", marker = "extra == 'knowledge'", specifier = "==0.5.23" },
{ name = "chromadb", marker = "extra == 'memory'", specifier = ">=0.5.23" },
{ name = "chromadb", marker = "extra == 'knowledge'", specifier = ">=1.0.0" },
{ name = "chromadb", marker = "extra == 'memory'", specifier = ">=1.0.0" },
{ name = "fastapi", marker = "extra == 'api'", specifier = ">=0.115.0" },
{ name = "fastapi", marker = "extra == 'mcp'", specifier = ">=0.115.0" },
{ name = "litellm", marker = "extra == 'llm'", specifier = ">=1.50.0" },
{ name = "markitdown", extras = ["all"], marker = "extra == 'knowledge'" },
{ name = "markitdown", extras = ["all"], marker = "extra == 'knowledge'", specifier = ">=0.1.0" },
{ name = "mcp", specifier = ">=1.6.0" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.6.0" },
{ name = "mem0ai", marker = "extra == 'knowledge'", specifier = ">=0.1.0" },
@@ -2306,15 +2389,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 },
]
[[package]]
name = "puremagic"
version = "1.28"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/09/2d/40599f25667733e41bbc3d7e4c7c36d5e7860874aa5fe9c584e90b34954d/puremagic-1.28.tar.gz", hash = "sha256:195893fc129657f611b86b959aab337207d6df7f25372209269ed9e303c1a8c0", size = 314945 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/53/200a97332d10ed3edd7afcbc5f5543920ac59badfe5762598327999f012e/puremagic-1.28-py3-none-any.whl", hash = "sha256:e16cb9708ee2007142c37931c58f07f7eca956b3472489106a7245e5c3aa1241", size = 43241 },
]
[[package]]
name = "pyasn1"
version = "0.6.1"
@@ -2465,6 +2539,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
]
[[package]]
name = "pyjwt"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
]
[package.optional-dependencies]
crypto = [
{ name = "cryptography" },
]
[[package]]
name = "pypika"
version = "0.48.9"
@@ -3448,6 +3536,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/d9/a8ba5e9507a9af1917285d118388c5eb7a81834873f45df213a6fe923774/wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371", size = 23592 },
]
[[package]]
name = "xlrd"
version = "2.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531 },
]
[[package]]
name = "xlsxwriter"
version = "3.2.0"
@@ -3537,15 +3634,15 @@ wheels = [
[[package]]
name = "youtube-transcript-api"
version = "0.6.3"
version = "1.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "defusedxml" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/55ff16f7198bdf5204fd7be3c49122e07092a3da47bf4e1560989a4c0255/youtube_transcript_api-0.6.3.tar.gz", hash = "sha256:4d1f6451ae508390a5279f98519efb45e091bf60d3cca5ea0bb122800ab6a011", size = 612052 }
sdist = { url = "https://files.pythonhosted.org/packages/b0/32/f60d87a99c05a53604c58f20f670c7ea6262b55e0bbeb836ffe4550b248b/youtube_transcript_api-1.0.3.tar.gz", hash = "sha256:902baf90e7840a42e1e148335e09fe5575dbff64c81414957aea7038e8a4db46", size = 2153252 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/80/d4/be6fd091d29ae49d93813e598769e7ab453419a4de640e1755bf20911cce/youtube_transcript_api-0.6.3-py3-none-any.whl", hash = "sha256:297a74c1863d9df88f6885229f33a7eda61493d73ecb13ec80e876b65423e9b4", size = 622293 },
{ url = "https://files.pythonhosted.org/packages/f0/44/40c03bb0f8bddfb9d2beff2ed31641f52d96c287ba881d20e0c074784ac2/youtube_transcript_api-1.0.3-py3-none-any.whl", hash = "sha256:d1874e57de65cf14c9d7d09b2b37c814d6287fa0e770d4922c4cd32a5b3f6c47", size = 2169911 },
]
[[package]]
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.17.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.17.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.20.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.20.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -3,4 +3,4 @@ import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["EC_TELEMETRY"] = "false"
from .cli import PraisonAI
from .version import __version__
from .version import __version__
+3 -3
View File
@@ -39,9 +39,6 @@ LOG_EVENT_TYPES = [
app = FastAPI()
if not OPENAI_API_KEY:
raise ValueError('Missing the OpenAI API key. Please set it in the .env file.')
# Set up logging
logger = logging.getLogger(__name__)
log_level = os.getenv("LOGLEVEL", "INFO").upper()
@@ -266,6 +263,9 @@ def setup_public_url(port):
def run_server(port: int, use_public: bool = False):
"""Run the FastAPI server using uvicorn."""
if not OPENAI_API_KEY:
raise ValueError('Missing the OpenAI API key. Please set it in the .env file or configure it through the GUI.')
if use_public:
setup_public_url(port)
else:
+69 -10
View File
@@ -54,7 +54,7 @@ except ImportError:
pass
try:
from crewai import Agent, Task, Crew
import crewai
CREWAI_AVAILABLE = True
except ImportError:
pass
@@ -147,6 +147,21 @@ class PraisonAI:
"""
return self.main()
def read_stdin_if_available(self):
"""
Read from stdin if it's available (when data is piped in).
Returns the stdin content or None if no piped input is available.
"""
try:
# Check if stdin is not a terminal (i.e., has piped input)
if not sys.stdin.isatty():
stdin_content = sys.stdin.read().strip()
return stdin_content if stdin_content else None
except Exception:
# If there's any error reading stdin, ignore it
pass
return None
def main(self):
"""
The main function of the PraisonAI object. It parses the command-line arguments,
@@ -164,21 +179,41 @@ class PraisonAI:
self.framework = args.framework or self.framework
# Check for piped input from stdin
stdin_input = self.read_stdin_if_available()
if args.command:
if args.command.startswith("tests.test") or args.command.startswith("tests/test"): # Argument used for testing purposes
print("test")
return "test"
else:
self.agent_file = args.command
# If stdin input is available, append it to the command
if stdin_input:
combined_prompt = f"{args.command} {stdin_input}"
result = self.handle_direct_prompt(combined_prompt)
print(result)
return result
else:
self.agent_file = args.command
elif hasattr(args, 'direct_prompt') and args.direct_prompt:
# Only handle direct prompt if agent_file wasn't explicitly set in constructor
if original_agent_file == "agents.yaml": # Default value, so safe to use direct prompt
result = self.handle_direct_prompt(args.direct_prompt)
# If stdin input is available, append it to the direct prompt
prompt = args.direct_prompt
if stdin_input:
prompt = f"{args.direct_prompt} {stdin_input}"
result = self.handle_direct_prompt(prompt)
print(result)
return result
else:
# Agent file was explicitly set, ignore direct prompt and use the file
pass
elif stdin_input:
# If only stdin input is provided (no command), use it as direct prompt
if original_agent_file == "agents.yaml": # Default value, so safe to use stdin as prompt
result = self.handle_direct_prompt(stdin_input)
print(result)
return result
# If no command or direct_prompt, preserve agent_file from constructor (don't overwrite)
if args.deploy:
@@ -248,17 +283,29 @@ class PraisonAI:
print("All packages installed")
return
# Check if conda is available and environment exists
conda_available = True
conda_env_exists = False
try:
result = subprocess.check_output(['conda', 'env', 'list'])
if 'praison_env' in result.decode('utf-8'):
print("Conda environment 'praison_env' found.")
conda_env_exists = True
else:
raise subprocess.CalledProcessError(1, 'grep')
except subprocess.CalledProcessError:
print("Conda environment 'praison_env' not found. Setting it up...")
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed.")
print("Conda environment 'praison_env' not found. Setting it up...")
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed.")
# Check again if environment was created successfully
try:
result = subprocess.check_output(['conda', 'env', 'list'])
conda_env_exists = 'praison_env' in result.decode('utf-8')
except subprocess.CalledProcessError:
conda_env_exists = False
except (subprocess.CalledProcessError, FileNotFoundError):
print("Conda not available or failed to check environment.")
conda_available = False
train_args = sys.argv[2:] # Get all arguments after 'train'
@@ -278,7 +325,18 @@ class PraisonAI:
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
stream_subprocess(['conda', 'run', '--no-capture-output', '--name', 'praison_env', 'python', '-u', train_script_path, 'train'], env=env)
# Try conda run first, fallback to direct Python execution
if conda_available and conda_env_exists:
try:
print("Attempting to run training using conda environment...")
stream_subprocess(['conda', 'run', '--no-capture-output', '--name', 'praison_env', 'python', '-u', train_script_path, 'train'], env=env)
except subprocess.CalledProcessError as e:
print(f"Conda run failed with error: {e}")
print("Falling back to direct Python execution...")
stream_subprocess([sys.executable, '-u', train_script_path, 'train'], env=env)
else:
print("Conda environment not available, using direct Python execution...")
stream_subprocess([sys.executable, '-u', train_script_path, 'train'], env=env)
return
if args.auto or self.auto:
@@ -500,6 +558,7 @@ class PraisonAI:
result = agent.start(prompt)
return result
elif CREWAI_AVAILABLE:
from crewai import Agent, Task, Crew
agent_config = {
"name": "DirectAgent",
"role": "Assistant",
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.2.17 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.2.20 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+24 -4
View File
@@ -1,9 +1,13 @@
# praisonai/inc/models.py
import os
import logging
from urllib.parse import urlparse
logger = logging.getLogger(__name__)
logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO').upper(), format='%(asctime)s - %(levelname)s - %(message)s')
# Constants
LOCAL_SERVER_API_KEY_PLACEHOLDER = "not-needed"
# Conditionally import modules based on availability
try:
from langchain_openai import ChatOpenAI # pip install langchain-openai
@@ -71,11 +75,27 @@ class PraisonAIModel:
self.model_name = self.model.replace("openrouter/", "")
else:
self.api_key_var = api_key_var or "OPENAI_API_KEY"
self.base_url = base_url or os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
self.base_url = base_url or os.environ.get("OPENAI_API_BASE") or os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1"
self.model_name = self.model
logger.debug(f"Initialized PraisonAIModel with model {self.model_name}, api_key_var {self.api_key_var}, and base_url {self.base_url}")
# Use explicit API key if provided, otherwise fall back to environment variable
self.api_key = api_key or os.environ.get(self.api_key_var, "nokey")
# Get API key from environment
self.api_key = api_key or os.environ.get(self.api_key_var)
# For local servers, allow placeholder API key if base_url is set to non-OpenAI endpoint
if not self.api_key and self.base_url:
parsed_url = urlparse(self.base_url)
is_local = (parsed_url.hostname in ["localhost", "127.0.0.1"] or
"api.openai.com" not in self.base_url)
if is_local:
self.api_key = LOCAL_SERVER_API_KEY_PLACEHOLDER
if not self.api_key:
raise ValueError(
f"{self.api_key_var} environment variable is required for the default OpenAI service. "
f"For local servers, set {self.api_key_var}='{LOCAL_SERVER_API_KEY_PLACEHOLDER}' and OPENAI_API_BASE to your local endpoint."
)
def get_model(self):
"""
@@ -127,4 +147,4 @@ class PraisonAIModel:
raise ImportError(
"Required Langchain Integration 'langchain-openai' not found. "
"Please install with 'pip install langchain-openai'"
)
)
@@ -0,0 +1,56 @@
"""
Database configuration utilities for PraisonAI UI components.
This module provides centralized database configuration functionality,
particularly for handling the FORCE_SQLITE environment variable.
"""
import os
from typing import Optional, Tuple
def should_force_sqlite() -> bool:
"""
Check if FORCE_SQLITE environment variable is set to true.
Returns:
bool: True if FORCE_SQLITE is set to "true" (case-insensitive), False otherwise.
"""
return os.getenv("FORCE_SQLITE", "false").lower() == "true"
def get_database_url_with_sqlite_override() -> Optional[str]:
"""
Get database URL respecting FORCE_SQLITE flag.
When FORCE_SQLITE=true, this function returns None to force SQLite usage.
Otherwise, it returns the appropriate database URL from environment variables.
Returns:
Optional[str]: Database URL if external database should be used, None for SQLite.
"""
if should_force_sqlite():
return None
database_url = os.getenv("DATABASE_URL")
supabase_url = os.getenv("SUPABASE_DATABASE_URL")
return supabase_url if supabase_url else database_url
def get_database_config_for_sqlalchemy() -> Tuple[Optional[str], Optional[str]]:
"""
Get database configuration for SQLAlchemy module respecting FORCE_SQLITE flag.
Returns:
Tuple[Optional[str], Optional[str]]: (DATABASE_URL, SUPABASE_DATABASE_URL)
Both will be None if FORCE_SQLITE=true, otherwise original values.
"""
if should_force_sqlite():
return None, None
else:
database_url = os.getenv("DATABASE_URL")
supabase_url = os.getenv("SUPABASE_DATABASE_URL")
# Apply Supabase override logic
if supabase_url:
database_url = supabase_url
return database_url, supabase_url
+3 -4
View File
@@ -9,6 +9,7 @@ from sqlalchemy.orm import sessionmaker
from sql_alchemy import SQLAlchemyDataLayer
import chainlit.data as cl_data
from chainlit.types import ThreadDict
from database_config import get_database_url_with_sqlite_override
def ensure_directories():
"""Ensure required directories exist"""
@@ -57,10 +58,8 @@ ensure_directories()
class DatabaseManager(SQLAlchemyDataLayer):
def __init__(self):
self.database_url = os.getenv("DATABASE_URL")
supabase_url = os.getenv("SUPABASE_DATABASE_URL")
if supabase_url:
self.database_url = supabase_url
# Check FORCE_SQLITE flag to bypass external database detection
self.database_url = get_database_url_with_sqlite_override()
if self.database_url:
self.conninfo = self.database_url
+3 -5
View File
@@ -29,16 +29,14 @@ from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from database_config import get_database_config_for_sqlalchemy
if TYPE_CHECKING:
from chainlit.element import Element
from chainlit.step import StepDict
DATABASE_URL = os.getenv("DATABASE_URL")
SUPABASE_DATABASE_URL = os.getenv("SUPABASE_DATABASE_URL")
if SUPABASE_DATABASE_URL:
# If a Supabase database URL is provided, use it.
DATABASE_URL = SUPABASE_DATABASE_URL
# Check FORCE_SQLITE flag to bypass external database detection
DATABASE_URL, SUPABASE_DATABASE_URL = get_database_config_for_sqlalchemy()
class SQLAlchemyDataLayer(BaseDataLayer):
def __init__(
+10 -5
View File
@@ -1,10 +1,10 @@
[project]
name = "PraisonAI"
version = "2.2.17"
version = "2.2.20"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
readme = "README.md"
license = ""
requires-python = ">=3.10,<3.13"
requires-python = ">=3.10"
authors = [
{ name = "Mervin Praison" }
]
@@ -12,13 +12,18 @@ dependencies = [
"rich>=13.7",
"markdown>=3.5",
"pyparsing>=3.0.0",
"praisonaiagents>=0.0.90",
"praisonaiagents>=0.0.91",
"python-dotenv>=0.19.0",
"instructor>=1.3.3",
"PyYAML>=6.0",
"mcp>=1.6.0",
]
[project.scripts]
praisonai = "praisonai.__main__:main"
setup-conda-env = "praisonai.setup.setup_conda_env:main"
praisonai-call = "praisonai.api.call:main"
[project.optional-dependencies]
ui = [
"chainlit==2.5.5",
@@ -89,7 +94,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.15", "crewai"]
[tool.poetry]
name = "PraisonAI"
version = "2.2.17"
version = "2.2.20"
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -107,7 +112,7 @@ python = ">=3.10,<3.13"
rich = ">=13.7"
markdown = ">=3.5"
pyparsing = ">=3.0.0"
praisonaiagents = ">=0.0.90"
praisonaiagents = ">=0.0.91"
python-dotenv = ">=0.19.0"
instructor = ">=1.3.3"
PyYAML = ">=6.0"
+25 -5
View File
@@ -1,6 +1,7 @@
import unittest
import subprocess
import os
import pytest
from praisonai.cli import PraisonAI
from .advanced_example import advanced
from .basic_example import main
@@ -12,32 +13,37 @@ import collections.abc
collections.MutableMapping = collections.abc.MutableMapping
class TestPraisonAIFramework(unittest.TestCase):
@pytest.mark.real
def test_main_with_agents_advanced(self):
praisonai = PraisonAI(agent_file="tests/agents-advanced.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@pytest.mark.real
def test_main_with_autogen_framework(self):
praisonai = PraisonAI(agent_file="tests/autogen-agents.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@pytest.mark.real
def test_main_with_custom_framework(self):
praisonai = PraisonAI(agent_file="tests/crewai-agents.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@pytest.mark.real
def test_main_with_internet_search_tool(self):
praisonai = PraisonAI(agent_file="tests/search-tool-agents.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@pytest.mark.real
def test_main_with_built_in_tool(self):
praisonai = PraisonAI(agent_file="tests/built-in-tools-agents.yaml")
praisonai = PraisonAI(agent_file="tests/inbuilt-tool-agents.yaml")
result = praisonai.run()
print(f"Result: {result}")
self.assertIsNotNone(result)
@@ -46,22 +52,35 @@ class TestPraisonAIFramework(unittest.TestCase):
class TestPraisonAICommandLine(unittest.TestCase):
def run_command(self, command):
"""Helper method to run CLI commands"""
result = subprocess.run(command, shell=True, capture_output=True, text=True)
# Ensure OPENAI_API_KEY is available to the subprocess if this test is marked as real
env = os.environ.copy()
if 'OPENAI_API_KEY' not in env:
# This is a fallback for local runs if the key isn't explicitly set for the main test process
# In CI, it should be set by the workflow
print("Warning: OPENAI_API_KEY not found in CLI test environment. API calls might fail if not mocked.")
result = subprocess.run(command, shell=True, capture_output=True, text=True, env=env)
return result.stdout + result.stderr
@pytest.mark.real
def test_praisonai_command(self):
# Test basic praisonai command
command = "praisonai --framework autogen --auto create a 2-agent team to write a simple python game"
command = "praisonai --framework autogen --auto \"create a 2-agent team to write a simple python game\""
result = self.run_command(command)
print(f"Result: {result}")
self.assertIn('TERMINATE', result)
@pytest.mark.real
def test_praisonai_init_command(self):
# Test praisonai --init command
command = "praisonai --framework autogen --init create a 2-agent team to write a simple python game"
# This command primarily creates files, but let's ensure it uses the real key if any underlying PraisonAI init involves API calls
command = "praisonai --framework autogen --init \"create a 2-agent team to write a simple python game\""
result = self.run_command(command)
print(f"Result: {result}")
self.assertIn('created successfully', result)
# Check for success indicator in the output
success_phrases = ['created successfully', 'agents.yaml created', 'File created']
found_success = any(phrase in result for phrase in success_phrases)
self.assertTrue(found_success, f"No success message found. Expected one of {success_phrases}. Last 1000 chars: {result[-1000:]}")
class TestExamples(unittest.TestCase):
def test_advanced_example(self):
@@ -69,6 +88,7 @@ class TestExamples(unittest.TestCase):
print(f"Result: {result}")
self.assertIsNotNone(result)
@pytest.mark.real
def test_auto_example(self):
result = auto()
print(f"Result: {result}")
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""
Test script for database_config module functionality.
"""
import sys
import os
# Import from the proper module path
from praisonai.ui.database_config import should_force_sqlite, get_database_url_with_sqlite_override, get_database_config_for_sqlalchemy
def test_database_config():
print("Testing database_config utilities...")
# Test 1: FORCE_SQLITE=false (default)
print('\nTest 1: Default behavior (FORCE_SQLITE not set)')
os.environ.pop('FORCE_SQLITE', None)
os.environ.pop('DATABASE_URL', None)
os.environ.pop('SUPABASE_DATABASE_URL', None)
print(f'should_force_sqlite(): {should_force_sqlite()}')
print(f'get_database_url_with_sqlite_override(): {get_database_url_with_sqlite_override()}')
print(f'get_database_config_for_sqlalchemy(): {get_database_config_for_sqlalchemy()}')
# Test 2: FORCE_SQLITE=true
print('\nTest 2: FORCE_SQLITE=true')
os.environ['FORCE_SQLITE'] = 'true'
os.environ['DATABASE_URL'] = 'postgres://test'
os.environ['SUPABASE_DATABASE_URL'] = 'postgres://supabase'
print(f'should_force_sqlite(): {should_force_sqlite()}')
print(f'get_database_url_with_sqlite_override(): {get_database_url_with_sqlite_override()}')
print(f'get_database_config_for_sqlalchemy(): {get_database_config_for_sqlalchemy()}')
# Test 3: FORCE_SQLITE=false with DATABASE_URL
print('\nTest 3: FORCE_SQLITE=false with DATABASE_URL')
os.environ['FORCE_SQLITE'] = 'false'
print(f'should_force_sqlite(): {should_force_sqlite()}')
print(f'get_database_url_with_sqlite_override(): {get_database_url_with_sqlite_override()}')
print(f'get_database_config_for_sqlalchemy(): {get_database_config_for_sqlalchemy()}')
print('\n✅ All tests completed - utility functions work correctly!')
if __name__ == "__main__":
test_database_config()
+2650 -2114
View File
File diff suppressed because it is too large. Load diff