mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 08:11:07 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56a42e6d95 | ||
|
|
d2b4affdd1 | ||
|
|
0d5c8b478f | ||
|
|
fdfad70319 | ||
|
|
fba6b2e2a0 | ||
|
|
f9bfb85f44 | ||
|
|
aab4b5b466 | ||
|
|
7ccadde004 | ||
|
|
22a9d5ff23 | ||
|
|
98a11209ea | ||
|
|
df3c97e73a | ||
|
|
27808964be | ||
|
|
b5daf16e0d | ||
|
|
a2cac05d16 | ||
|
|
ad9828d3bb | ||
|
|
85ddf04b47 | ||
|
|
1c1a1b719b | ||
|
|
a8e0914e05 | ||
|
|
ec6d883f4f | ||
|
|
d094cbfac5 | ||
|
|
14e3724aed | ||
|
|
92436277d9 | ||
|
|
3e0fd298e2 | ||
|
|
fad0b41b52 | ||
|
|
6c3e5ee187 | ||
|
|
f842ee7048 | ||
|
|
b0ff6f3f60 | ||
|
|
798f6dad14 | ||
|
|
629bf0d669 | ||
|
|
eaca447b82 | ||
|
|
bef3693627 | ||
|
|
aa62babed6 | ||
|
|
b73f181200 | ||
|
|
66d2998047 | ||
|
|
c5b6f8b91a | ||
|
|
823e46ca84 | ||
|
|
57040333a7 | ||
|
|
8c6800f876 | ||
|
|
0d701ae43c | ||
|
|
7a8c25621c | ||
|
|
218c5443b1 | ||
|
|
f56810341c | ||
|
|
4f601a573f | ||
|
|
43c03f4813 | ||
|
|
03856b74bf | ||
|
|
eadfc0cff0 | ||
|
|
f33c44d4c5 | ||
|
|
3f7885289d | ||
|
|
833c795e9a | ||
|
|
c460c9da60 | ||
|
|
656887c700 | ||
|
|
e0089cac41 | ||
|
|
bf5e069c7b | ||
|
|
94cf264b4c | ||
|
|
ab44fd4b27 | ||
|
|
7bad6cf7e9 | ||
|
|
4ffbe48c58 | ||
|
|
476eeb1184 | ||
|
|
550769017a | ||
|
|
cccd75ca75 | ||
|
|
395f8fb5ef | ||
|
|
e4893ad948 | ||
|
|
e717b625d2 | ||
|
|
84638ad759 | ||
|
|
626bc963f7 | ||
|
|
efa7110b38 | ||
|
|
539c10796c | ||
|
|
815becb6f2 | ||
|
|
87790695db | ||
|
|
00b25bb195 | ||
|
|
e46ca3248d | ||
|
|
04f6beaa56 | ||
|
|
3d99326192 | ||
|
|
70ced6acbb | ||
|
|
dc14c06e41 | ||
|
|
deb1148c8a | ||
|
|
3d6d7c0b02 | ||
|
|
1a5a25073e | ||
|
|
7b743e6982 | ||
|
|
77a3512779 | ||
|
|
2a4ec38077 | ||
|
|
25f59b5c26 | ||
|
|
7596ed252d | ||
|
|
14bea558fb | ||
|
|
b5b3d8abd0 | ||
|
|
6f69067450 | ||
|
|
0b06a1c3ed | ||
|
|
8907344649 | ||
|
|
1221bf26e4 | ||
|
|
92263bbb90 | ||
|
|
067769cefb | ||
|
|
947d301cb5 | ||
|
|
3c96d50fbd | ||
|
|
b744c4fbf5 | ||
|
|
2b79201f0f | ||
|
|
5bf86cd8bf | ||
|
|
0f2bb67e5f | ||
|
|
b58b7d6680 | ||
|
|
756f5ecebd | ||
|
|
4341fdda1c | ||
|
|
6261f574c3 | ||
|
|
8740a16f4a | ||
|
|
c183c211dc | ||
|
|
e42a8ec652 | ||
|
|
fd3511200d | ||
|
|
cdb1da765c | ||
|
|
b5fb9051ac | ||
|
|
8414c9d0d8 | ||
|
|
ef82448569 | ||
|
|
e5830de8ad | ||
|
|
fb4035437c | ||
|
|
2e3a37ed52 | ||
|
|
87513c2c44 | ||
|
|
542701f989 | ||
|
|
b5b1aa7dd7 | ||
|
|
2e0480543f | ||
|
|
b4991caa18 | ||
|
|
17436324db | ||
|
|
9c26d6d1dc | ||
|
|
f1842c3d08 | ||
|
|
6bac8cbafe | ||
|
|
6e0223a473 | ||
|
|
3e21d31899 | ||
|
|
1323f9711c | ||
|
|
51f01fa6c6 | ||
|
|
7df52d32cf | ||
|
|
6ddea1d5b6 | ||
|
|
ecb21e32ec | ||
|
|
4e23d5ca41 | ||
|
|
4c29c14c2e | ||
|
|
2a1549c8cd | ||
|
|
09c6c321e8 | ||
|
|
63700a0bbc | ||
|
|
a28bf4b063 | ||
|
|
ef87c1b643 | ||
|
|
7948791e5c | ||
|
|
8e4b57fb9f | ||
|
|
a6326ceb8a | ||
|
|
0fa794a3de | ||
|
|
cf812353b5 | ||
|
|
0eb9eb535b | ||
|
|
280a2a541f | ||
|
|
344ef7801a | ||
|
|
5846bbdf9a | ||
|
|
22a46e8dc0 | ||
|
|
ec0f638bf0 | ||
|
|
4ca8c51898 | ||
|
|
f6d44508c2 | ||
|
|
60c12b2352 | ||
|
|
d2f843dc36 | ||
|
|
2ab35f8be3 | ||
|
|
ca9d8ac104 | ||
|
|
ebc75db440 | ||
|
|
cf809fd4bb | ||
|
|
1539689025 | ||
|
|
d8d2f6597f | ||
|
|
d00aabd040 | ||
|
|
d5cd13e723 | ||
|
|
0dba16a63a | ||
|
|
b533e1dba5 | ||
|
|
a909fd3778 | ||
|
|
b6049f0581 | ||
|
|
ffb6af5000 | ||
|
|
f1f9956020 | ||
|
|
1cedc9c1b2 | ||
|
|
5d803b0acb | ||
|
|
5b932c8010 | ||
|
|
8479707865 | ||
|
|
801548a302 | ||
|
|
0e4b627834 | ||
|
|
d13d4c3c59 | ||
|
|
698b4937e5 | ||
|
|
64db00046a | ||
|
|
7277598f38 | ||
|
|
1f3a1de750 | ||
|
|
c783aa130c | ||
|
|
c980b12a5c | ||
|
|
e814d28d3c | ||
|
|
dd445b22a3 | ||
|
|
8ee381c1e4 | ||
|
|
089188853c | ||
|
|
2aab2e6dfd | ||
|
|
670dd6874e | ||
|
|
1796930739 | ||
|
|
b8fb078145 | ||
|
|
819b49f3dc | ||
|
|
2e432b1ec2 | ||
|
|
d86544c455 | ||
|
|
1a6ad078de | ||
|
|
8f8845dada | ||
|
|
a548f08b02 | ||
|
|
cfa1fb4125 | ||
|
|
c5a799acd4 | ||
|
|
945092f5c4 | ||
|
|
e6301f2078 | ||
|
|
4ffb3938ad | ||
|
|
c2fb733169 | ||
|
|
5d92d4f282 | ||
|
|
cc947d20c4 | ||
|
|
c654a0b7e8 | ||
|
|
08645ca90b | ||
|
|
c4e7b147cc | ||
|
|
9a95d63c4c | ||
|
|
a352b54b1b | ||
|
|
a3adf2f7a2 | ||
|
|
7443736ab7 | ||
|
|
c910060204 | ||
|
|
68a526d49f | ||
|
|
589c55c62a | ||
|
|
3ece193ba7 | ||
|
|
4737f621d2 | ||
|
|
2d2ffb7d08 | ||
|
|
3277de5151 | ||
|
|
c9c0ea1fc2 | ||
|
|
0938e7cbb7 | ||
|
|
c9aaf7fb2d | ||
|
|
6af1aa51ba | ||
|
|
d26c4b6012 | ||
|
|
ca0305d90b | ||
|
|
92c2a38128 | ||
|
|
7386e7c347 | ||
|
|
e3e123fef1 | ||
|
|
a1e9acdf77 | ||
|
|
5229d93867 | ||
|
|
bd7a79889a | ||
|
|
41e7ba8e98 | ||
|
|
3196d85519 | ||
|
|
7e0c8504b4 | ||
|
|
938f6ab60b | ||
|
|
a32b8d8b67 | ||
|
|
ac7377aa6b | ||
|
|
7d907a8fa4 | ||
|
|
05b3bcb623 | ||
|
|
489aede159 | ||
|
|
e93fc0288a | ||
|
|
40743b81fb | ||
|
|
87d6a14c79 | ||
|
|
649062beb2 | ||
|
|
aeb3489e82 | ||
|
|
60053a8d69 | ||
|
|
bce50bc90b | ||
|
|
ef31f766dc | ||
|
|
7437f999d7 | ||
|
|
b3cccfeaf6 | ||
|
|
33d4322296 | ||
|
|
109292b964 | ||
|
|
00bee22897 | ||
|
|
6372e51770 | ||
|
|
bdeb6b8c27 | ||
|
|
18542f6f99 | ||
|
|
9e7f80261f | ||
|
|
ea70b1a28d | ||
|
|
75b2afc2f3 | ||
|
|
c502a412a5 | ||
|
|
49958119fa | ||
|
|
268b5ed8b8 | ||
|
|
b707fd0b27 | ||
|
|
8c4ab718ae | ||
|
|
8cffa23da4 | ||
|
|
5b0f600180 | ||
|
|
2b3bf825ea | ||
|
|
7b816d7832 | ||
|
|
d6156895f6 | ||
|
|
8b72cec167 | ||
|
|
6db5e32063 | ||
|
|
7501c57c8e | ||
|
|
115a95a8e0 | ||
|
|
42752a8ed7 | ||
|
|
f0a2983a9f | ||
|
|
adccce2f3d | ||
|
|
29080b6e78 | ||
|
|
aeff0f63a4 | ||
|
|
edcfea1da0 | ||
|
|
d2925c3415 | ||
|
|
6962709186 | ||
|
|
de20247418 | ||
|
|
4e328c0504 | ||
|
|
232ba25c86 | ||
|
|
4bf2c36ffe | ||
|
|
442bae1fed | ||
|
|
151facab62 | ||
|
|
d4c2a9a2dd | ||
|
|
5c0a86fab0 | ||
|
|
8c1c67453f | ||
|
|
5375a65e60 | ||
|
|
5eb6090c01 | ||
|
|
704bcaa599 | ||
|
|
c4cbe88bbb | ||
|
|
720921f1ef | ||
|
|
285e6c5cc4 | ||
|
|
f58646510c | ||
|
|
07fb9a3e8c | ||
|
|
bef3359e9b | ||
|
|
760877ccce | ||
|
|
99c27533bc | ||
|
|
1d0bdd22c7 | ||
|
|
0aaf7cb866 | ||
|
|
da5483b47b | ||
|
|
f420ab5d5b | ||
|
|
a170749327 | ||
|
|
f608f90c51 | ||
|
|
be238c841e | ||
|
|
4472bfa9d4 | ||
|
|
abf12b1aab | ||
|
|
717476adab | ||
|
|
fc802aa254 | ||
|
|
ab9342933b | ||
|
|
060c322bfe | ||
|
|
392d3a8694 | ||
|
|
635483ec3d | ||
|
|
660248b95a | ||
|
|
f29adfe74c | ||
|
|
29f56e21a2 | ||
|
|
5378a37dd1 | ||
|
|
273aa48861 | ||
|
|
04be538472 | ||
|
|
e50c614835 | ||
|
|
9871768c3a | ||
|
|
1f9e2ae839 | ||
|
|
62af1fe7ab | ||
|
|
320aa968db | ||
|
|
bf8eb9b82e | ||
|
|
03d3cb544f | ||
|
|
92b769a73f | ||
|
|
fe23957f41 | ||
|
|
a9f7393809 | ||
|
|
df1f572d1c | ||
|
|
619f0173cc | ||
|
|
adb707b593 | ||
|
|
e2b95eb9b8 | ||
|
|
8889cb94c5 | ||
|
|
83e6eb1b2b | ||
|
|
3e14f4abb5 | ||
|
|
510d1eef56 | ||
|
|
ba672bfad1 | ||
|
|
04841330f7 | ||
|
|
ff28049a76 | ||
|
|
af0e6ff659 | ||
|
|
e1677d1f7c | ||
|
|
bfb98efee0 | ||
|
|
a5e69cb77e | ||
|
|
e207bd904e | ||
|
|
1985f73d60 |
No files matched your search
+1885
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": ["plugin:mdx/recommended"],
|
||||
// optional, if you want to lint code blocks at the same time
|
||||
"settings": {
|
||||
"mdx/code-blocks": true,
|
||||
// optional, if you want to disable language mapper, set it to `false`
|
||||
// if you want to override the default language mapper inside, you can provide your own
|
||||
"mdx/language-mapper": {}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Delayed Publish Docker
|
||||
name: Daily Publish Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '*/4 * * * *'
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
check-and-build:
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
name: Release Workflow
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to create release with (e.g., v2.0.10)'
|
||||
required: true
|
||||
fork_owner:
|
||||
description: 'GitHub username of the fork owner'
|
||||
required: true
|
||||
default: 'tosin2013'
|
||||
|
||||
jobs:
|
||||
test-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, ubuntu-24.04]
|
||||
framework: ['crewai', 'autogen']
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
# First checkout the upstream repo at the specified tag
|
||||
- name: Checkout upstream repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: MervinPraison/PraisonAI
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
# Get the commit hash from the tag
|
||||
- name: Get commit hash
|
||||
id: get_commit
|
||||
run: |
|
||||
COMMIT_HASH=$(git rev-parse HEAD)
|
||||
echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
|
||||
echo "Found commit: $COMMIT_HASH for tag ${{ github.event.inputs.tag }}"
|
||||
|
||||
# Now checkout the fork at main branch
|
||||
- name: Checkout fork
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.event.inputs.fork_owner }}/PraisonAI
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
clean: true
|
||||
|
||||
# Sync with upstream commit
|
||||
- name: Sync with upstream
|
||||
run: |
|
||||
git config --global user.name "GitHub Actions"
|
||||
git config --global user.email "[email protected]"
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/MervinPraison/PraisonAI.git
|
||||
|
||||
# Fetch from upstream
|
||||
git fetch upstream --tags --force
|
||||
|
||||
# Reset to upstream commit
|
||||
git reset --hard ${{ steps.get_commit.outputs.commit_hash }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
poetry config virtualenvs.in-project true
|
||||
poetry config virtualenvs.create true
|
||||
poetry --version
|
||||
poetry config --list
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
poetry lock #--no-update
|
||||
poetry install --all-extras
|
||||
poetry env info
|
||||
# Verify venv exists
|
||||
test -d .venv || { echo "Virtual environment not created!"; exit 1; }
|
||||
test -f .venv/bin/activate || { echo "Activation script not found!"; exit 1; }
|
||||
|
||||
- name: Run setup script
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
curl -OL https://raw.githubusercontent.com/tosin2013/PraisonAI/refs/heads/main/setup.sh
|
||||
chmod +x setup.sh
|
||||
./setup.sh --all --cicd || { echo "Setup script failed"; exit 1; }
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test Basic Functionality
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
lsof -ti:8084 | xargs -r kill -9 || true
|
||||
source .venv/bin/activate || { echo "Failed to activate virtual environment"; exit 1; }
|
||||
mkdir -p test_output || { echo "Failed to create test_output directory"; exit 1; }
|
||||
|
||||
echo "Testing basic CLI functionality..."
|
||||
|
||||
# Test auto generation with explicit framework
|
||||
praisonai --framework ${{ matrix.framework }} --auto "create a short test script" 2>&1 | tee test_output/auto_test.log
|
||||
|
||||
# Verify the output file exists and has content
|
||||
if [[ ! -s test_output/auto_test.log ]]; then
|
||||
echo "Error: No output generated from auto test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test auto generation without framework
|
||||
echo "Basic functionality tests passed!"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
LOGLEVEL: "debug"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test Framework-specific Features
|
||||
continue-on-error: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
lsof -ti:8084 | xargs -r kill -9 || true
|
||||
praisonai --framework ${{ matrix.framework }} --auto "create a movie script about Robots in Mars" 2>&1 | tee test_output/framework_output.txt || echo "Framework-specific test failed" >> test_output/errors.log
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test Core Functionality
|
||||
continue-on-error: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
echo "Testing core functionality..."
|
||||
|
||||
# Test package imports
|
||||
python3 -c "from praisonai.cli import PraisonAI; from praisonai.auto import AutoGenerator; from praisonai.agents_generator import AgentsGenerator" || exit 1
|
||||
|
||||
# Test configuration loading
|
||||
python3 -c "from praisonai.inc.config import generate_config; print(generate_config())" || exit 1
|
||||
|
||||
# Test tools loading
|
||||
python3 -c "from praisonai.inbuilt_tools import *" || exit 1
|
||||
|
||||
echo "Core functionality tests passed!"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
LOGLEVEL: "debug"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test Agent Generation
|
||||
continue-on-error: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
echo "Testing agent generation..."
|
||||
|
||||
# Create a test YAML file
|
||||
cat > test_agents.yaml <<EOF
|
||||
topic: "Test automation task"
|
||||
framework: "${{ matrix.framework }} "
|
||||
roles:
|
||||
- name: "Tester"
|
||||
goal: "Test the system"
|
||||
backstory: "Expert system tester"
|
||||
tasks:
|
||||
- description: "Verify system functionality"
|
||||
expected_output: "Test results"
|
||||
EOF
|
||||
|
||||
# Test YAML loading and agent generation
|
||||
python3 -c '
|
||||
from praisonai.agents_generator import AgentsGenerator
|
||||
config = [{"model": "llama-3.3-70b-versatile", "base_url": "https://api.groq.com/openai/v1/"}]
|
||||
generator = AgentsGenerator("test_agents.yaml", "${{ matrix.framework }} ", config)
|
||||
print("Agent generation test passed!")
|
||||
' || exit 1
|
||||
|
||||
echo "Agent generation tests passed!"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
LOGLEVEL: "debug"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test Custom Tools Integration
|
||||
continue-on-error: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
echo "Testing custom tools integration..."
|
||||
|
||||
# Create a test tool
|
||||
cat > test_tool.py <<EOF
|
||||
from duckduckgo_search import DDGS
|
||||
from praisonai_tools import BaseTool
|
||||
|
||||
class InternetSearchTool(BaseTool):
|
||||
name: str = "Internet Search Tool"
|
||||
description: str = "Search Internet for relevant information"
|
||||
|
||||
def _run(self, query: str):
|
||||
return {"status": "success", "query": query}
|
||||
|
||||
def test(self):
|
||||
"""Test method to verify tool setup"""
|
||||
test_query = "test query"
|
||||
result = self._run(test_query)
|
||||
assert result["status"] == "success"
|
||||
assert result["query"] == test_query
|
||||
return True
|
||||
EOF
|
||||
|
||||
# Test tool loading and initialization
|
||||
python3 -c '
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
# Load the test tool
|
||||
spec = importlib.util.spec_from_file_location("test_tool", "test_tool.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["test_tool"] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# Initialize and test the tool
|
||||
tool = module.InternetSearchTool()
|
||||
assert tool.test(), "Tool test failed"
|
||||
print("Custom tool test passed!")
|
||||
' || exit 1
|
||||
|
||||
echo "Custom tools integration test passed!"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
LOGLEVEL: "debug"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Test PraisonAI Agents Configuration
|
||||
continue-on-error: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
|
||||
# Random sleep between 60-120 seconds to avoid API rate limits
|
||||
SLEEP_TIME=$((RANDOM % 61 + 60))
|
||||
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
|
||||
sleep $SLEEP_TIME
|
||||
|
||||
echo "Testing agents configuration..."
|
||||
mkdir -p test_output || { echo "Failed to create test_output directory"; exit 1; }
|
||||
|
||||
# Create test script
|
||||
cat > test_agents.py <<EOF
|
||||
from langchain_openai import ChatOpenAI
|
||||
from crewai import Agent, Task, Crew
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
|
||||
logging.basicConfig(level="DEBUG")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def random_sleep(min_seconds=15, max_seconds=30):
|
||||
"""Sleep for a random duration to prevent API rate limiting"""
|
||||
duration = random.uniform(min_seconds, max_seconds)
|
||||
logger.info(f"Rate limit protection: Sleeping for {duration:.2f} seconds...")
|
||||
time.sleep(duration)
|
||||
|
||||
class RateLimitedChatOpenAI(ChatOpenAI):
|
||||
"""ChatOpenAI with built-in rate limiting"""
|
||||
def __call__(self, *args, **kwargs):
|
||||
random_sleep()
|
||||
return super().__call__(*args, **kwargs)
|
||||
|
||||
def run_test():
|
||||
logger.info("Initializing test components...")
|
||||
|
||||
# Initialize the LLM with rate limiting
|
||||
llm = RateLimitedChatOpenAI(
|
||||
model=os.getenv('OPENAI_MODEL_NAME', 'llama-3.3-70b-versatile'),
|
||||
base_url=os.getenv('OPENAI_API_BASE', 'https://api.groq.com/openai/v1/'),
|
||||
api_key=os.getenv('OPENAI_API_KEY'),
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
random_sleep()
|
||||
logger.info("Creating researcher agent...")
|
||||
researcher = Agent(
|
||||
role="Test Automation Expert",
|
||||
goal="Verify system functionality and performance",
|
||||
backstory="""You are an expert in test automation and system verification,
|
||||
skilled in identifying potential issues and validating functionality.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
llm=llm
|
||||
)
|
||||
|
||||
random_sleep()
|
||||
logger.info("Creating validator agent...")
|
||||
validator = Agent(
|
||||
role="Quality Assurance Specialist",
|
||||
goal="Validate test results and ensure system reliability",
|
||||
backstory="""You are a QA specialist focused on ensuring
|
||||
system reliability and proper functionality.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
llm=llm
|
||||
)
|
||||
|
||||
random_sleep()
|
||||
logger.info("Creating test tasks...")
|
||||
test_task = Task(
|
||||
description="Run a basic system verification test",
|
||||
expected_output="A report on system functionality verification",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
random_sleep()
|
||||
validation_task = Task(
|
||||
description="Validate the test results and compile findings",
|
||||
expected_output="A validation report of test findings",
|
||||
agent=validator
|
||||
)
|
||||
|
||||
random_sleep()
|
||||
logger.info("Initializing crew...")
|
||||
crew = Crew(
|
||||
agents=[researcher, validator],
|
||||
tasks=[test_task, validation_task],
|
||||
verbose=2
|
||||
)
|
||||
|
||||
logger.info("Starting crew execution...")
|
||||
result = crew.kickoff()
|
||||
logger.info("Test execution completed.")
|
||||
logger.info(f"Result: {result}")
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
run_test()
|
||||
except Exception as e:
|
||||
logger.error(f"Test failed with error: {str(e)}", exc_info=True)
|
||||
raise
|
||||
EOF
|
||||
|
||||
# Run the test script with increased timeout
|
||||
echo "Testing with Python API..."
|
||||
timeout 1800 python3 test_agents.py 2>&1 | tee test_output/agents_test.log
|
||||
|
||||
# Verify the output
|
||||
if [[ ! -s test_output/agents_test.log ]]; then
|
||||
echo "Error: No output generated from agents test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Agents configuration tests passed!"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
|
||||
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
|
||||
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
|
||||
LOGLEVEL: "debug"
|
||||
PRAISONAI_NON_INTERACTIVE: "true"
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: praisonai-output-${{ matrix.os }}-${{ matrix.framework }}
|
||||
path: test_output/
|
||||
retention-days: 14
|
||||
|
||||
create-release:
|
||||
needs: [test-ubuntu]
|
||||
runs-on: ubuntu-latest
|
||||
if: success()
|
||||
steps:
|
||||
# First checkout the fork's main branch
|
||||
- name: Checkout fork repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
repository: ${{ github.event.inputs.fork_owner }}/PraisonAI
|
||||
ref: main
|
||||
|
||||
# Add upstream and get commit from tag
|
||||
- name: Get upstream tag commit
|
||||
id: get_upstream_commit
|
||||
run: |
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/MervinPraison/PraisonAI.git
|
||||
|
||||
# Delete local tag if it exists
|
||||
git tag -d ${{ github.event.inputs.tag }} || true
|
||||
|
||||
# Fetch from upstream including tags with force
|
||||
git fetch upstream --tags --force
|
||||
|
||||
# Verify tag exists in upstream
|
||||
if ! git ls-remote --tags upstream | grep -q "refs/tags/${{ github.event.inputs.tag }}$"; then
|
||||
echo "::error::Tag ${{ github.event.inputs.tag }} not found in upstream repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the commit hash from the upstream tag
|
||||
UPSTREAM_COMMIT=$(git rev-list -n 1 refs/tags/${{ github.event.inputs.tag }})
|
||||
if [ -z "$UPSTREAM_COMMIT" ]; then
|
||||
echo "::error::Could not find commit for tag ${{ github.event.inputs.tag }}"
|
||||
exit 1
|
||||
fi
|
||||
echo "upstream_commit=$UPSTREAM_COMMIT" >> $GITHUB_OUTPUT
|
||||
echo "Found commit: $UPSTREAM_COMMIT for tag ${{ github.event.inputs.tag }}"
|
||||
|
||||
# Create tag at the same commit
|
||||
- name: Create tag at commit
|
||||
run: |
|
||||
git config --global user.name "GitHub Actions"
|
||||
git config --global user.email "[email protected]"
|
||||
|
||||
# Remove tag if it exists locally
|
||||
git tag -d ${{ github.event.inputs.tag }} || true
|
||||
|
||||
# Remove tag if it exists remotely
|
||||
git push origin :refs/tags/${{ github.event.inputs.tag }} || true
|
||||
|
||||
# Fetch the specific commit from upstream
|
||||
git fetch upstream ${{ steps.get_upstream_commit.outputs.upstream_commit }} --force
|
||||
|
||||
# Reset to upstream commit
|
||||
git reset --hard ${{ steps.get_upstream_commit.outputs.upstream_commit }}
|
||||
|
||||
# Create and push tag
|
||||
git tag -a ${{ github.event.inputs.tag }} -m "Release ${{ github.event.inputs.tag }} (synced from upstream MervinPraison/PraisonAI@${{ github.event.inputs.tag }})"
|
||||
git push origin ${{ github.event.inputs.tag }}
|
||||
env:
|
||||
GIT_ASKPASS: /bin/echo
|
||||
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
# Create the release
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.tag }}
|
||||
release_name: Release ${{ github.event.inputs.tag }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: |
|
||||
Release ${{ github.event.inputs.tag }} (synced from upstream)
|
||||
|
||||
This release is synchronized with MervinPraison/PraisonAI@${{ github.event.inputs.tag }}
|
||||
Commit: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
|
||||
|
||||
For installation:
|
||||
```bash
|
||||
pip install praisonai
|
||||
```
|
||||
|
||||
Changes from upstream v2.0.10:
|
||||
- Updated agent configuration tests
|
||||
- Added rate limiting protection
|
||||
- Improved test reliability
|
||||
|
||||
Forked from: MervinPraison/PraisonAI
|
||||
+21
-2
@@ -20,11 +20,13 @@ docs.sh
|
||||
other
|
||||
output
|
||||
|
||||
.chainlit
|
||||
.files
|
||||
threads.db
|
||||
threads.db-journal
|
||||
|
||||
.chainlit
|
||||
!praisonai/ui/config/.chainlit
|
||||
|
||||
site
|
||||
# Ignore Sphinx build directory
|
||||
docs/_build/
|
||||
@@ -45,6 +47,23 @@ praisonai/doc
|
||||
|
||||
# agentops
|
||||
trained_agents_data.pkl
|
||||
agentops.log
|
||||
|
||||
# crewAI
|
||||
crewAI
|
||||
crewAI
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
|
||||
# Credentials
|
||||
credentials.json
|
||||
|
||||
# node_modules
|
||||
node_modules
|
||||
|
||||
outputs
|
||||
agents/praisonaiagents/build
|
||||
agents/praisonaiagents/dist
|
||||
agents/praisonaiagents/praisonaiagents.egg-info
|
||||
.codegpt
|
||||
.praison
|
||||
@@ -0,0 +1,4 @@
|
||||
# praisonai/ui/context.py
|
||||
# /Users/praison/miniconda3/envs/praisonai-package/lib/python3.11/site-packages/llama_index/packs/code_hierarchy/code_hierarchy.py
|
||||
praisonai/ui/components/aicoder.py
|
||||
praisonai/ui/code.py
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
cookbooks
|
||||
tests
|
||||
docs
|
||||
docs
|
||||
praisonai/inbuilt_tools/*
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
#praisonai
|
||||
# praisonai/ui/context.py
|
||||
# /Users/praison/miniconda3/envs/praisonai-package/lib/python3.11/site-packages/llama_index/packs/code_hierarchy/code_hierarchy.py
|
||||
praisonai/setup
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install flask praisonai==0.0.56 gunicorn markdown
|
||||
RUN pip install flask praisonai==2.0.45 gunicorn markdown
|
||||
EXPOSE 8080
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
|
||||
@@ -0,0 +1,24 @@
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python packages
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonaiagents>=0.0.4 \
|
||||
praisonai_tools \
|
||||
"praisonai==2.0.18" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Default command (will be overridden by docker-compose)
|
||||
CMD ["praisonai", "chat"]
|
||||
@@ -0,0 +1,28 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
python3-dev \
|
||||
portaudio19-dev \
|
||||
python3-pyaudio \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python packages
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonaiagents>=0.0.4 \
|
||||
praisonai_tools \
|
||||
"praisonai==2.0.18" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Default command (will be overridden by docker-compose)
|
||||
CMD ["praisonai", "ui"]
|
||||
@@ -0,0 +1,24 @@
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python packages
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonaiagents>=0.0.4 \
|
||||
praisonai_tools \
|
||||
"praisonai==2.0.18" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Default command (will be overridden by docker-compose)
|
||||
CMD ["praisonai", "ui"]
|
||||
@@ -1,30 +1,167 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/images/praisonai-logo-large.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/images/praisonai-logo-black-large.png">
|
||||
<img alt="PraisonAI Logo" src="docs/images/praisonai-logo-black-large.png">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/logo/dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/logo/light.png" />
|
||||
<img alt="PraisonAI Logo" src="docs/logo/light.png" />
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://static.pepy.tech/badge/PraisonAI" alt="Total Downloads"></a>
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://img.shields.io/github/v/release/MervinPraison/PraisonAI" alt="Latest Stable Version"></a>
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"></a>
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://static.pepy.tech/badge/PraisonAI" alt="Total Downloads" /></a>
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://img.shields.io/github/v/release/MervinPraison/PraisonAI" alt="Latest Stable Version" /></a>
|
||||
<a href="https://github.com/MervinPraison/PraisonAI"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License" /></a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# Praison AI
|
||||
|
||||
<a href="https://trendshift.io/repositories/9130" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9130" alt="MervinPraison%2FPraisonAI | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
</div>
|
||||
|
||||
Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, represents a low-code, centralised framework designed to simplify the creation and orchestration of multi-agent systems for various LLM applications, emphasizing ease of use, customization, and human-agent interaction.
|
||||
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.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.praison.ai">
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/📚_Documentation-Visit_docs.praison.ai-blue?style=for-the-badge&logo=bookstack&logoColor=white" alt="Documentation" />
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Key Features
|
||||
|
||||
- 🤖 Automated AI Agents Creation
|
||||
- 🔄 Self Reflection AI Agents
|
||||
- 🧠 Reasoning AI Agents
|
||||
- 👁️ Multi Modal AI Agents
|
||||
- 🤝 Multi Agent Collaboration
|
||||
- ⚡ AI Agent Workflow
|
||||
- 🔄 Use CrewAI or AutoGen Framework
|
||||
- 💯 100+ LLM Support
|
||||
- 💻 Chat with ENTIRE Codebase
|
||||
- 🎨 Interactive UIs
|
||||
- 📄 YAML-based Configuration
|
||||
- 🛠️ Custom Tool Integration
|
||||
- 🔍 Internet Search Capability (using Crawl4AI and Tavily)
|
||||
- 🖼️ Vision Language Model (VLM) Support
|
||||
- 🎙️ Real-time Voice Interaction
|
||||
|
||||
## Using No Code
|
||||
|
||||
### Auto Mode:
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --auto create a movie script about Robots in Mars
|
||||
```
|
||||
|
||||
### Initialise Mode:
|
||||
```bash
|
||||
pip install praisonai
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
praisonai --init create a movie script about Robots in Mars
|
||||
praisonai
|
||||
```
|
||||
|
||||
## Using Coding
|
||||
|
||||
Light weight package dedicated for coding:
|
||||
```bash
|
||||
pip install praisonaiagents
|
||||
```
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
Create app.py file and add the code below:
|
||||
```python
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# 1. Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You are an expert at a technology research group,
|
||||
skilled in identifying trends and analyzing complex data.""",
|
||||
verbose=True,
|
||||
llm="gpt-4o",
|
||||
markdown=True
|
||||
)
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Tech Content Strategist",
|
||||
goal="Craft compelling content on tech advancements",
|
||||
backstory="""You are a content strategist known for
|
||||
making complex tech topics interesting and easy to understand.""",
|
||||
llm="gpt-4o",
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 2. Define Tasks
|
||||
task1 = Task(
|
||||
name="research_task",
|
||||
description="""Analyze 2024's AI advancements.
|
||||
Find major trends, new technologies, and their effects.""",
|
||||
expected_output="""A detailed report on 2024 AI advancements""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="writing_task",
|
||||
description="""Create a blog post about major AI advancements using the insights you have.
|
||||
Make it interesting, clear, and suited for tech enthusiasts.
|
||||
It should be at least 4 paragraphs long.""",
|
||||
expected_output="A blog post of at least 4 paragraphs",
|
||||
agent=writer,
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2],
|
||||
verbose=False,
|
||||
process="hierarchical",
|
||||
manager_llm="gpt-4o"
|
||||
)
|
||||
|
||||
result = agents.start()
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
## Ollama Integration
|
||||
```bash
|
||||
export OPENAI_BASE_URL=http://localhost:11434/v1
|
||||
```
|
||||
|
||||
## Groq Integration
|
||||
Replace xxxx with Groq API KEY:
|
||||
```bash
|
||||
export OPENAI_API_KEY=xxxxxxxxxxx
|
||||
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
|
||||
```
|
||||
|
||||
## Logging
|
||||
```bash
|
||||
export LOGLEVEL=info
|
||||
```
|
||||
|
||||
Advanced logging:
|
||||
```bash
|
||||
export LOGLEVEL=debug
|
||||
```
|
||||
|
||||
<div align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/images/architecture-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/images/architecture-light.png">
|
||||
<img alt="PraisonAI Architecture" src="docs/images/architecture-light.png">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/images/architecture-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="docs/images/architecture-light.png" />
|
||||
<img alt="PraisonAI Architecture" src="docs/images/architecture-light.png" />
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
@@ -35,29 +172,100 @@ Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, rep
|
||||
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praison.ai/ui/ui](https://docs.praison.ai/ui/ui) |
|
||||
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
|
||||
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
|
||||
| **Realtime** | Real-time voice interaction with AI | [https://docs.praison.ai/ui/realtime](https://docs.praison.ai/ui/realtime) |
|
||||
|
||||
| Other Features | Description | Docs |
|
||||
|---|---|---|
|
||||
| **Train** | Fine-tune LLMs using your custom data | [https://docs.praison.ai/train](https://docs.praison.ai/train) |
|
||||
|
||||
|
||||
## Google Colab Multi Agents
|
||||
|
||||
| | Cookbook | Open in Colab |
|
||||
| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Basic | PraisonAI | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a> |
|
||||
| Include Tools | PraisonAI Tools | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-tools-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a> |
|
||||
| Basic | PraisonAI | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a> |
|
||||
| Include Tools | PraisonAI Tools | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-tools-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" /></a> |
|
||||
|
||||
## Install
|
||||
## Installation Options
|
||||
|
||||
| PraisonAI | PraisonAI Code | PraisonAI Chat |
|
||||
| --- | --- | --- |
|
||||
| `pip install praisonai` | `pip install "praisonai[code]"` | `pip install "praisonai[chat]"` |
|
||||
### Using pip
|
||||
```bash
|
||||
pip install praisonai
|
||||
```
|
||||
|
||||
### Framework-specific Installation
|
||||
```bash
|
||||
# Install with CrewAI support
|
||||
pip install "praisonai[crewai]"
|
||||
|
||||
# Install with AutoGen support
|
||||
pip install "praisonai[autogen]"
|
||||
|
||||
# Install with both frameworks
|
||||
pip install "praisonai[crewai,autogen]"
|
||||
```
|
||||
|
||||
### UI and Additional Features
|
||||
```bash
|
||||
# Install UI support
|
||||
pip install "praisonai[ui]"
|
||||
|
||||
# Install Chat interface
|
||||
pip install "praisonai[chat]"
|
||||
|
||||
# Install Code interface
|
||||
pip install "praisonai[code]"
|
||||
|
||||
# Install Realtime voice interaction
|
||||
pip install "praisonai[realtime]"
|
||||
|
||||
# Install Call feature
|
||||
pip install "praisonai[call]"
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Set your OpenAI API key
|
||||
export OPENAI_API_KEY="Enter your API key"
|
||||
|
||||
# Initialize with CrewAI (default)
|
||||
praisonai --init "create a movie script about dog in moon"
|
||||
|
||||
# Or initialize with AutoGen
|
||||
praisonai --framework autogen --init "create a movie script about dog in moon"
|
||||
|
||||
# Run the agents
|
||||
praisonai
|
||||
```
|
||||
|
||||
## Full Automatic Mode
|
||||
|
||||
```bash
|
||||
# With CrewAI (default)
|
||||
praisonai --auto "create a movie script about Dog in Moon"
|
||||
|
||||
# With AutoGen
|
||||
praisonai --framework autogen --auto "create a movie script about Dog in Moon"
|
||||
```
|
||||
|
||||
## Framework-specific Features
|
||||
|
||||
### CrewAI
|
||||
When installing with `pip install "praisonai[crewai]"`, you get:
|
||||
- CrewAI framework support
|
||||
- PraisonAI tools integration
|
||||
- Task delegation capabilities
|
||||
- Sequential and parallel task execution
|
||||
|
||||
### AutoGen
|
||||
When installing with `pip install "praisonai[autogen]"`, you get:
|
||||
- AutoGen framework support
|
||||
- PraisonAI tools integration
|
||||
- Multi-agent conversation capabilities
|
||||
- Code execution environment
|
||||
|
||||
## Key Features
|
||||
|
||||
- Automated AI Agents Creation
|
||||
- Use CrewAI or AutoGen Framework
|
||||
- 100+ LLM Support
|
||||
- Chat with ENTIRE Codebase
|
||||
- Interactive UIs
|
||||
- YAML-based Configuration
|
||||
- Custom Tool Integration
|
||||
|
||||
## TL;DR Multi Agents
|
||||
|
||||
@@ -170,6 +378,14 @@ export OPENAI_API_KEY="Enter your API key"
|
||||
praisonai chat
|
||||
```
|
||||
|
||||
### Internet Search
|
||||
|
||||
Praison AI Chat and Praison AI Code now includes internet search capabilities using Crawl4AI and Tavily, allowing you to retrieve up-to-date information during your conversations.
|
||||
|
||||
### Vision Language Model Support
|
||||
|
||||
You can now upload images and ask questions based on them using Vision Language Models. This feature enables visual understanding and analysis within your chat sessions.
|
||||
|
||||
## Praison AI Code
|
||||
|
||||
```bash
|
||||
@@ -178,6 +394,10 @@ export OPENAI_API_KEY="Enter your API key"
|
||||
praisonai code
|
||||
```
|
||||
|
||||
### Internet Search
|
||||
|
||||
Praison AI Code also includes internet search functionality, enabling you to find relevant code snippets and programming information online.
|
||||
|
||||
## Create Custom Tools
|
||||
|
||||
- https://docs.praison.ai/tools/custom/
|
||||
@@ -257,30 +477,18 @@ if __name__ == "__main__":
|
||||
|
||||
## Commands to Install Dependencies:
|
||||
|
||||
1. **Install all dependencies, including dev dependencies:**
|
||||
### Using uv
|
||||
```bash
|
||||
# Install uv if you haven't already
|
||||
pip install uv
|
||||
|
||||
```sh
|
||||
poetry install
|
||||
```
|
||||
# Install from requirements
|
||||
uv pip install -r pyproject.toml
|
||||
|
||||
2. **Install only documentation dependencies:**
|
||||
|
||||
```sh
|
||||
poetry install --with docs
|
||||
```
|
||||
|
||||
3. **Install only test dependencies:**
|
||||
|
||||
```sh
|
||||
poetry install --with test
|
||||
```
|
||||
|
||||
4. **Install only dev dependencies:**
|
||||
```sh
|
||||
poetry install --with dev
|
||||
```
|
||||
|
||||
This configuration ensures that your development dependencies are correctly categorized and installed as needed.
|
||||
# Install with extras
|
||||
uv pip install -r pyproject.toml --extra code
|
||||
uv pip install -r pyproject.toml --extra "crewai,autogen"
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -299,3 +507,76 @@ This configuration ensures that your development dependencies are correctly cate
|
||||
## License
|
||||
|
||||
Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
## Video Tutorials
|
||||
|
||||
| Topic | Video |
|
||||
|-------|--------|
|
||||
| Introduction | [](https://www.youtube.com/watch?v=Fn1lQjC0GO0) |
|
||||
| Tools Overview | [](https://www.youtube.com/watch?v=XaQRgRpV7jo) |
|
||||
| Custom Tools | [](https://www.youtube.com/watch?v=JSU2Rndh06c) |
|
||||
| Firecrawl Integration | [](https://www.youtube.com/watch?v=UoqUDcLcOYo) |
|
||||
| User Interface | [](https://www.youtube.com/watch?v=tg-ZjNl3OCg) |
|
||||
| Crawl4AI Integration | [](https://www.youtube.com/watch?v=KAvuVUh0XU8) |
|
||||
| Chat Interface | [](https://www.youtube.com/watch?v=sw3uDqn2h1Y) |
|
||||
| Code Interface | [](https://www.youtube.com/watch?v=_5jQayO-MQY) |
|
||||
| Mem0 Integration | [](https://www.youtube.com/watch?v=KIGSgRxf1cY) |
|
||||
| Training | [](https://www.youtube.com/watch?v=aLawE8kwCrI) |
|
||||
| Realtime Voice Interface | [](https://www.youtube.com/watch?v=frRHfevTCSw) |
|
||||
| Call Interface | [](https://www.youtube.com/watch?v=m1cwrUG2iAk) |
|
||||
|
||||
## License
|
||||
|
||||
Praison AI is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
## Local Docker Development with Live Reload
|
||||
|
||||
To facilitate local development with live reload, you can use Docker. Follow the steps below:
|
||||
|
||||
1. **Create a `Dockerfile.dev`**:
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install flask praisonai==2.0.18 watchdog
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
ENV FLASK_ENV=development
|
||||
|
||||
CMD ["flask", "run", "--host=0.0.0.0"]
|
||||
```
|
||||
|
||||
2. **Create a `docker-compose.yml`**:
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "5555:5555"
|
||||
environment:
|
||||
FLASK_ENV: development
|
||||
command: flask run --host=0.0.0.0
|
||||
|
||||
watch:
|
||||
image: alpine:latest
|
||||
volumes:
|
||||
- .:/app
|
||||
command: sh -c "apk add --no-cache inotify-tools && while inotifywait -r -e modify,create,delete /app; do kill -HUP 1; done"
|
||||
```
|
||||
|
||||
3. **Run Docker Compose**:
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This setup will allow you to develop locally with live reload, making it easier to test and iterate on your code.
|
||||
+7
-3
@@ -1,18 +1,22 @@
|
||||
framework: autogen
|
||||
framework: praisonai
|
||||
process: sequential
|
||||
topic: create movie script about cat in mars
|
||||
memory: false
|
||||
roles:
|
||||
researcher:
|
||||
backstory: Skilled in research, with a focus on gathering accurate and relevant
|
||||
information.
|
||||
goal: Gather information about Mars and cats
|
||||
role: Researcher
|
||||
min_reflect: 1
|
||||
max_reflect: 2
|
||||
tasks:
|
||||
research_task:
|
||||
description: Research about Mars, its environment, and the feasibility of
|
||||
a cat being on Mars. Also, research about cat behavior and characteristics.
|
||||
expected_output: Document with research findings on Mars and cats.
|
||||
tools:
|
||||
- ''
|
||||
- search_tool
|
||||
narrative_designer:
|
||||
backstory: Skilled in narrative development, with a focus on creating engaging
|
||||
stories.
|
||||
@@ -37,4 +41,4 @@ roles:
|
||||
expected_output: Final movie script with dialogue and scene details.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
dependencies: []
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Make minimal code changes as possible
|
||||
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
|
||||
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
|
||||
4. Always Follow the principle LESS IS MORE
|
||||
5. Make it minimal change as possible
|
||||
6. Firstly try not to make any modification to the existing code as possible
|
||||
7. Only modify the existing code if its highly required, without that if it cant be done, then add new code section.
|
||||
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.env
|
||||
.venv
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.project
|
||||
.pydevproject
|
||||
.settings/
|
||||
|
||||
# Testing
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Documentation
|
||||
docs/_build/
|
||||
docs/generated/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
*.ipynb
|
||||
|
||||
# Logs and databases
|
||||
*.log
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.test
|
||||
praisonaiaagents.egg-info
|
||||
praisonaiagents.egg-info
|
||||
app.py
|
||||
outputs
|
||||
praisonaiagents/build
|
||||
praisonaiagents/dist
|
||||
praisonaiagents/praisonaiagents.egg-info
|
||||
dist
|
||||
build
|
||||
*.mp4
|
||||
*.png
|
||||
graph.py
|
||||
chroma_db/
|
||||
Whitespace-only changes.
@@ -0,0 +1,32 @@
|
||||
.cursorrules
|
||||
.gitignore
|
||||
.praisoncontext
|
||||
.praisonignore
|
||||
.windsurfrules
|
||||
async_example.py
|
||||
async_example_full.py
|
||||
async_example_full_multigroups.py
|
||||
auto_agents_example.py
|
||||
code_agents_example.py
|
||||
example_callback.py
|
||||
example_custom_tools.py
|
||||
example_sequential.py
|
||||
image.jpg
|
||||
langchain_example.py
|
||||
langchain_logs.py
|
||||
long_term.db
|
||||
mini_agents_example.py
|
||||
multimodal.py
|
||||
praisonaiagents/tools/*
|
||||
praisonaiagents/process/*
|
||||
pyproject.toml
|
||||
short_term.db
|
||||
structured_agents_example.py
|
||||
structured_response_example.py
|
||||
test.py
|
||||
tools_example.py
|
||||
uv.lock
|
||||
workflow_example_basic.py
|
||||
workflow_example_detailed.py
|
||||
__pycache__
|
||||
__pycache__/*
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Make minimal code changes as possible
|
||||
2. if you are creating function, make sure to name it in more easy, user friendly , optimised manner.
|
||||
3. Never copy the exact code if presented with a code from other location or file or codebase, just make it optimised
|
||||
4. Always Follow the principle LESS IS MORE
|
||||
5. Make it minimal change as possible
|
||||
6. Firstly try not to make any modification to the existing code as possible
|
||||
7. Only modify the existing code if its highly required, without that if it cant be done, then add new code section.
|
||||
8. If you are adding new code, make sure to add it in a way that it can be easily integrated with the existing codebase.
|
||||
@@ -0,0 +1,233 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from praisonaiagents.main import (
|
||||
display_error,
|
||||
display_interaction,
|
||||
display_tool_call,
|
||||
display_instruction,
|
||||
error_logs,
|
||||
Console
|
||||
)
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
console = Console()
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define both sync and async tools
|
||||
def sync_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Synchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
list: Search results
|
||||
"""
|
||||
display_tool_call(f"Running sync search for: {query}", console)
|
||||
time.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error during sync search: {e}"
|
||||
display_error(error_msg, console)
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
|
||||
async def async_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Asynchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
list: Search results
|
||||
"""
|
||||
display_tool_call(f"Running async search for: {query}", console)
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error during async search: {e}"
|
||||
display_error(error_msg, console)
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
|
||||
# 3. Define both sync and async callbacks
|
||||
def sync_callback(output: TaskOutput):
|
||||
display_interaction("Sync Callback", f"Processing output: {output.raw[:100]}...", markdown=True, console=console)
|
||||
time.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
display_tool_call(f"Processed JSON result: {output.json_dict}", console)
|
||||
elif output.output_format == "Pydantic":
|
||||
display_tool_call(f"Processed Pydantic result: {output.pydantic}", console)
|
||||
|
||||
async def async_callback(output: TaskOutput):
|
||||
display_interaction("Async Callback", f"Processing output: {output.raw[:100]}...", markdown=True, console=console)
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
display_tool_call(f"Processed JSON result: {output.json_dict}", console)
|
||||
elif output.output_format == "Pydantic":
|
||||
display_tool_call(f"Processed Pydantic result: {output.pydantic}", console)
|
||||
|
||||
# 4. Create agents with different tools
|
||||
sync_agent = Agent(
|
||||
name="SyncAgent",
|
||||
role="Synchronous Search Specialist",
|
||||
goal="Perform synchronous searches and return structured results",
|
||||
backstory="Expert in sync operations and data organization",
|
||||
tools=[sync_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
async_agent = Agent(
|
||||
name="AsyncAgent",
|
||||
role="Asynchronous Search Specialist",
|
||||
goal="Perform asynchronous searches and return structured results",
|
||||
backstory="Expert in async operations and data organization",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create tasks with different configurations
|
||||
sync_task = Task(
|
||||
name="sync_search",
|
||||
description="Search for 'Python programming' using sync tool and return structured results",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=sync_agent,
|
||||
async_execution=False,
|
||||
callback=sync_callback,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="Search for 'Async programming' using async tool and return structured results",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
# 6. Create workflow tasks
|
||||
workflow_sync_task = Task(
|
||||
name="workflow_sync",
|
||||
description="Workflow sync search for 'AI trends' with structured output",
|
||||
expected_output="SearchResult model with AI trends data",
|
||||
agent=sync_agent,
|
||||
async_execution=False,
|
||||
is_start=True,
|
||||
next_tasks=["workflow_async"],
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
workflow_async_task = Task(
|
||||
name="workflow_async",
|
||||
description="Workflow async search for 'Future of AI' with structured output",
|
||||
expected_output="SearchResult model with Future of AI data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
# 7. Example usage functions
|
||||
def run_sync_example():
|
||||
"""Run synchronous example"""
|
||||
display_instruction("\nRunning Synchronous Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent],
|
||||
tasks=[sync_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = agents.start()
|
||||
display_interaction("Sync Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_async_example():
|
||||
"""Run asynchronous example"""
|
||||
display_instruction("\nRunning Asynchronous Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=[async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Async Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_mixed_example():
|
||||
"""Run mixed sync/async example"""
|
||||
display_instruction("\nRunning Mixed Sync/Async Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent, async_agent],
|
||||
tasks=[sync_task, async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Mixed Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_workflow_example():
|
||||
"""Run workflow example with both sync and async tasks"""
|
||||
display_instruction("\nRunning Workflow Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent, async_agent],
|
||||
tasks=[workflow_sync_task, workflow_async_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Workflow Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
# 8. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
display_instruction("Starting PraisonAI Agents Examples...", console)
|
||||
|
||||
try:
|
||||
# Run sync example in a separate thread to not block the event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, run_sync_example)
|
||||
|
||||
# Run async examples
|
||||
await run_async_example()
|
||||
await run_mixed_example()
|
||||
await run_workflow_example()
|
||||
|
||||
if error_logs:
|
||||
display_error("\nErrors encountered during execution:", console)
|
||||
for error in error_logs:
|
||||
display_error(error, console)
|
||||
except Exception as e:
|
||||
display_error(f"Error in main execution: {e}", console)
|
||||
error_logs.append(str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,152 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define async tool
|
||||
async def async_search_tool(query: str) -> Dict:
|
||||
"""Perform asynchronous search and return structured results."""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
|
||||
return {
|
||||
"query": query,
|
||||
"results": results,
|
||||
"total_results": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error during async search: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# 3. Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
print(f"Processed JSON result: {output.json_dict}")
|
||||
elif output.output_format == "Pydantic":
|
||||
print(f"Processed Pydantic result: {output.pydantic}")
|
||||
|
||||
# 4. Create specialized agents
|
||||
async_agent = Agent(
|
||||
name="AsyncSearchAgent",
|
||||
role="Search Specialist",
|
||||
goal="Perform fast parallel searches with structured results",
|
||||
backstory="Expert in efficient data retrieval and parallel search operations",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
name="SummaryAgent",
|
||||
role="Research Synthesizer",
|
||||
goal="Create concise summaries from multiple search results",
|
||||
backstory="Expert in analyzing and synthesizing information from multiple sources",
|
||||
self_reflect=True,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create async tasks
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="Search for 'Async programming' and return results in JSON format with query, results array, and total_results count.",
|
||||
expected_output="SearchResult model with structured data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
)
|
||||
|
||||
async def run_parallel_tasks():
|
||||
"""Run multiple async tasks in parallel"""
|
||||
print("\nRunning Parallel Async Tasks...")
|
||||
|
||||
# Define different search topics
|
||||
search_topics = [
|
||||
"Latest AI Developments 2024",
|
||||
"Machine Learning Best Practices",
|
||||
"Neural Networks Architecture"
|
||||
]
|
||||
|
||||
# Create tasks for different topics
|
||||
parallel_tasks = [
|
||||
Task(
|
||||
name=f"search_task_{i}",
|
||||
description=f"Search for '{topic}' and return structured results with query details and findings.",
|
||||
expected_output="SearchResult model with search data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summarization task
|
||||
summary_task = Task(
|
||||
name="summary_task",
|
||||
description="Analyze all search results and create a concise summary highlighting key findings, patterns, and implications.",
|
||||
expected_output="Structured summary with key findings and insights",
|
||||
agent=summary_agent,
|
||||
async_execution=False,
|
||||
callback=async_callback,
|
||||
context=parallel_tasks
|
||||
)
|
||||
|
||||
# Create a single PraisonAIAgents instance with both agents
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent, summary_agent],
|
||||
tasks=parallel_tasks + [summary_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
results = await agents.astart()
|
||||
print(f"Tasks Results: {results}")
|
||||
|
||||
# Return results in a serializable format
|
||||
return {
|
||||
"search_results": {
|
||||
"task_status": {k: v for k, v in results["task_status"].items() if k != summary_task.id},
|
||||
"task_results": [str(results["task_results"][i]) if results["task_results"][i] else None
|
||||
for i in range(len(parallel_tasks))]
|
||||
},
|
||||
"summary": str(results["task_results"][summary_task.id]) if results["task_results"].get(summary_task.id) else None,
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
# 6. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
print("Starting Async AI Agents Examples...")
|
||||
|
||||
try:
|
||||
await run_parallel_tasks()
|
||||
except Exception as e:
|
||||
print(f"Error in main execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,248 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define async tool
|
||||
async def async_search_tool(query: str) -> Dict:
|
||||
"""
|
||||
Asynchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
dict: Search results in SearchResult model format
|
||||
"""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
|
||||
# Format response to match SearchResult model
|
||||
return {
|
||||
"query": query,
|
||||
"results": results,
|
||||
"total_results": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error during async search: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# 3. Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
print(f"Processed JSON result: {output.json_dict}")
|
||||
elif output.output_format == "Pydantic":
|
||||
print(f"Processed Pydantic result: {output.pydantic}")
|
||||
|
||||
# 4. Create specialized agents
|
||||
async_agent = Agent(
|
||||
name="AsyncSearchAgent",
|
||||
role="Asynchronous Search Specialist",
|
||||
goal="Perform fast and efficient asynchronous searches with structured results",
|
||||
backstory="Expert in parallel search operations and data retrieval",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
name="SummaryAgent",
|
||||
role="Research Synthesizer",
|
||||
goal="Create comprehensive summaries and identify patterns across multiple search results",
|
||||
backstory="""Expert in analyzing and synthesizing information from multiple sources.
|
||||
Skilled at identifying patterns, trends, and connections between different topics.
|
||||
Specializes in creating clear, structured summaries that highlight key insights.""",
|
||||
self_reflect=True, # Enable self-reflection for better summary quality
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create async tasks
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="""Search for 'Async programming' and return results in the following JSON format:
|
||||
{
|
||||
"query": "the search query",
|
||||
"results": [
|
||||
{
|
||||
"title": "result title",
|
||||
"url": "result url",
|
||||
"snippet": "result snippet"
|
||||
}
|
||||
],
|
||||
"total_results": number of results
|
||||
}""",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
)
|
||||
|
||||
# 6. Example usage functions
|
||||
async def run_single_task():
|
||||
"""Run single async task"""
|
||||
print("\nRunning Single Async Task...")
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=[async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
print(f"Single Task Result: {result}")
|
||||
|
||||
async def run_parallel_tasks():
|
||||
"""Run multiple async tasks in parallel"""
|
||||
print("\nRunning Parallel Async Tasks...")
|
||||
|
||||
# Define different search topics
|
||||
search_topics = [
|
||||
"Latest AI Developments 2024",
|
||||
"Machine Learning Best Practices",
|
||||
"Neural Networks Architecture"
|
||||
]
|
||||
|
||||
# Create tasks for different topics
|
||||
parallel_tasks = [
|
||||
Task(
|
||||
name=f"search_task_{i}",
|
||||
description=f"""Search for '{topic}' and return results in the following JSON format:
|
||||
{{
|
||||
"query": "{topic}",
|
||||
"results": [
|
||||
{{
|
||||
"title": "result title",
|
||||
"url": "result url",
|
||||
"snippet": "result snippet"
|
||||
}}
|
||||
],
|
||||
"total_results": number of results
|
||||
}}""",
|
||||
expected_output="SearchResult model with detailed information",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summarization task with the specialized summary agent
|
||||
summary_task = Task(
|
||||
name="summary_task",
|
||||
description="""As a Research Synthesizer, analyze the search results and create a comprehensive summary. Your task:
|
||||
|
||||
1. Analyze Results:
|
||||
- Review all search results thoroughly
|
||||
- Extract key findings from each topic
|
||||
- Identify main themes and concepts
|
||||
|
||||
2. Find Connections:
|
||||
- Identify relationships between topics
|
||||
- Spot common patterns or contradictions
|
||||
- Note emerging trends across sources
|
||||
|
||||
3. Create Structured Summary:
|
||||
- Main findings per topic
|
||||
- Cross-cutting themes
|
||||
- Emerging trends
|
||||
- Practical implications
|
||||
- Future directions
|
||||
|
||||
4. Quality Checks:
|
||||
- Ensure all topics are covered
|
||||
- Verify accuracy of connections
|
||||
- Confirm clarity of insights
|
||||
- Validate practical relevance
|
||||
|
||||
Present the summary in a clear, structured format with sections for findings, patterns, trends, and implications.""",
|
||||
expected_output="""A comprehensive research synthesis containing:
|
||||
- Detailed findings from each search topic
|
||||
- Cross-topic patterns and relationships
|
||||
- Emerging trends and their implications
|
||||
- Practical applications and future directions""",
|
||||
agent=summary_agent, # Use the specialized summary agent
|
||||
async_execution=False, # Run synchronously after search tasks
|
||||
callback=async_callback
|
||||
)
|
||||
|
||||
# First run parallel search tasks
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=parallel_tasks, # Only run search tasks first
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
search_results = await agents.astart()
|
||||
print(f"Search Tasks Results: {search_results}")
|
||||
|
||||
# Create task objects with results for context
|
||||
completed_tasks = []
|
||||
for i, topic in enumerate(search_topics):
|
||||
task = Task(
|
||||
name=f"search_task_{i}_result",
|
||||
description=f"Search results for: {topic}",
|
||||
expected_output="Search results from previous task",
|
||||
agent=async_agent,
|
||||
result=search_results["task_results"][i]
|
||||
)
|
||||
completed_tasks.append(task)
|
||||
|
||||
# Update summary task with context from search results
|
||||
summary_task.context = completed_tasks
|
||||
|
||||
# Run summarization task with summary agent
|
||||
summary_agents = PraisonAIAgents(
|
||||
agents=[summary_agent], # Use summary agent for synthesis
|
||||
tasks=[summary_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
summary_result = await summary_agents.astart()
|
||||
print(f"Summary Task Result: {summary_result}")
|
||||
|
||||
# Return results in a serializable format
|
||||
return {
|
||||
"search_results": {
|
||||
"task_status": search_results["task_status"],
|
||||
"task_results": [str(result) if result else None for result in search_results["task_results"]]
|
||||
},
|
||||
"summary": str(summary_result),
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
# 7. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
print("Starting Async AI Agents Examples...")
|
||||
|
||||
try:
|
||||
# Run different async patterns
|
||||
await run_single_task()
|
||||
await run_parallel_tasks()
|
||||
except Exception as e:
|
||||
print(f"Error in main execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,11 @@
|
||||
from praisonaiagents import AutoAgents
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agents = AutoAgents(
|
||||
instructions="Search for information about AI Agents",
|
||||
tools=[duckduckgo],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,59 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_environment_feedback():
|
||||
"""Simulates environment feedback based on time"""
|
||||
current_time = int(time.time())
|
||||
feedback = "positive" if current_time % 2 == 0 else "negative"
|
||||
print(f"Environment feedback: {feedback}")
|
||||
return feedback
|
||||
|
||||
# Create autonomous agent
|
||||
autonomous_agent = Agent(
|
||||
name="Autonomous Agent",
|
||||
role="Environment interactor",
|
||||
goal="Interact with environment and learn from feedback",
|
||||
instructions="Take actions based on environment state and adapt from feedback",
|
||||
tools=[get_environment_feedback]
|
||||
)
|
||||
|
||||
# Create tasks for the autonomous loop
|
||||
action_task = Task(
|
||||
name="take_action",
|
||||
description="Analyze environment and take appropriate action",
|
||||
expected_output="Action taken and its rationale",
|
||||
agent=autonomous_agent,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["process_feedback"],
|
||||
condition={
|
||||
"continue": ["process_feedback"], # Continue to get feedback
|
||||
"stop": [""] # Stop when goal is achieved
|
||||
}
|
||||
)
|
||||
|
||||
feedback_task = Task(
|
||||
name="process_feedback",
|
||||
description="Process environment feedback and adapt strategy",
|
||||
expected_output="Adapted strategy based on feedback",
|
||||
agent=autonomous_agent,
|
||||
next_tasks=["take_action"],
|
||||
context=[action_task] # Access to previous action for learning
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[autonomous_agent],
|
||||
tasks=[action_task, feedback_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run autonomous workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nAutonomous Agent Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,50 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import json
|
||||
from e2b_code_interpreter import Sandbox
|
||||
|
||||
def code_interpreter(code: str):
|
||||
"""
|
||||
A function to demonstrate running Python code dynamically using e2b_code_interpreter.
|
||||
"""
|
||||
print(f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}")
|
||||
exec_result = Sandbox().run_code(code)
|
||||
if exec_result.error:
|
||||
print("[Code Interpreter error]", exec_result.error)
|
||||
return {"error": str(exec_result.error)}
|
||||
else:
|
||||
results = []
|
||||
for result in exec_result.results:
|
||||
if hasattr(result, '__iter__'):
|
||||
results.extend(list(result))
|
||||
else:
|
||||
results.append(str(result))
|
||||
logs = {"stdout": list(exec_result.logs.stdout), "stderr": list(exec_result.logs.stderr)}
|
||||
return json.dumps({"results": results, "logs": logs})
|
||||
|
||||
code_agent = Agent(
|
||||
name="code_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in writing Python scripts",
|
||||
self_reflect=False
|
||||
)
|
||||
execution_agent = Agent(
|
||||
name="execution_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in executing Python scripts",
|
||||
self_reflect=False,
|
||||
tools=[code_interpreter]
|
||||
)
|
||||
|
||||
code_agent_task = Task(
|
||||
description="Write a simple Python script to print 'Hello, World!'",
|
||||
expected_output="A Python script that prints 'Hello, World!'",
|
||||
agent=code_agent
|
||||
)
|
||||
execution_agent_task = Task(
|
||||
description="Execute the Python script",
|
||||
expected_output="The output of the Python script",
|
||||
agent=execution_agent
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(agents=[code_agent, execution_agent], tasks=[code_agent_task, execution_agent_task])
|
||||
agents.start()
|
||||
@@ -0,0 +1,70 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create generator and evaluator agents
|
||||
generator = Agent(
|
||||
name="Generator",
|
||||
role="Solution generator",
|
||||
goal="Generate initial solutions and incorporate feedback",
|
||||
instructions=(
|
||||
"1. Look at the context from previous tasks.\n"
|
||||
"2. If you see that you have already produced 2 points, then add another 2 new points "
|
||||
" so that the total becomes 10.\n"
|
||||
"3. Otherwise, just produce the first 2 points.\n"
|
||||
"4. Return only the final list of points, with no extra explanation."
|
||||
)
|
||||
)
|
||||
|
||||
evaluator = Agent(
|
||||
name="Evaluator",
|
||||
role="Solution evaluator",
|
||||
goal="Evaluate solutions and provide improvement feedback",
|
||||
instructions=(
|
||||
"1. Count how many lines in the response start with a number and a period (like '1. ' or '2. ').\n"
|
||||
"2. If there are 10 or more, respond with 'done'.\n"
|
||||
"3. Otherwise, respond with 'more'.\n"
|
||||
"4. Return only the single word: 'done' or 'more'."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Create tasks for the feedback loop
|
||||
generate_task = Task(
|
||||
name="generate",
|
||||
description="Write 2 points about AI incuding if anything exiting from previous points",
|
||||
expected_output="2 points",
|
||||
agent=generator,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["evaluate"]
|
||||
)
|
||||
|
||||
evaluate_task = Task(
|
||||
name="evaluate",
|
||||
description="Check if there are 10 points about AI",
|
||||
expected_output="more or done",
|
||||
agent=evaluator,
|
||||
next_tasks=["generate"],
|
||||
context=[generate_task],
|
||||
task_type="decision",
|
||||
condition={
|
||||
"more": ["generate"], # Continue to generate
|
||||
"done": [""] # Exit when optimization complete
|
||||
}
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[generator, evaluator],
|
||||
tasks=[generate_task, evaluate_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run optimization workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nEvaluator-Optimizer Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,204 @@
|
||||
from praisonaiagents import (
|
||||
Agent,
|
||||
Task,
|
||||
PraisonAIAgents,
|
||||
error_logs,
|
||||
register_display_callback,
|
||||
sync_display_callbacks,
|
||||
async_display_callbacks
|
||||
)
|
||||
from duckduckgo_search import DDGS
|
||||
from rich.console import Console
|
||||
import json
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
filename='ai_interactions.log',
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Callback functions for different display types
|
||||
def interaction_callback(message=None, response=None, markdown=None, generation_time=None):
|
||||
"""Callback for display_interaction"""
|
||||
logging.info(f"""
|
||||
=== INTERACTION ===
|
||||
Time: {datetime.now()}
|
||||
Generation Time: {generation_time}s
|
||||
Message: {message}
|
||||
Response: {response}
|
||||
Markdown: {markdown}
|
||||
""")
|
||||
|
||||
def error_callback(message=None):
|
||||
"""Callback for display_error"""
|
||||
logging.error(f"""
|
||||
=== ERROR ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def tool_call_callback(message=None):
|
||||
"""Callback for display_tool_call"""
|
||||
logging.info(f"""
|
||||
=== TOOL CALL ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def instruction_callback(message=None):
|
||||
"""Callback for display_instruction"""
|
||||
logging.info(f"""
|
||||
=== INSTRUCTION ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def self_reflection_callback(message=None):
|
||||
"""Callback for display_self_reflection"""
|
||||
logging.info(f"""
|
||||
=== SELF REFLECTION ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def generating_callback(content=None, elapsed_time=None):
|
||||
"""Callback for display_generating"""
|
||||
logging.info(f"""
|
||||
=== GENERATING ===
|
||||
Time: {datetime.now()}
|
||||
Content: {content}
|
||||
Elapsed Time: {elapsed_time}
|
||||
""")
|
||||
|
||||
# Register all callbacks
|
||||
register_display_callback('interaction', interaction_callback)
|
||||
register_display_callback('error', error_callback)
|
||||
register_display_callback('tool_call', tool_call_callback)
|
||||
register_display_callback('instruction', instruction_callback)
|
||||
register_display_callback('self_reflection', self_reflection_callback)
|
||||
# register_display_callback('generating', generating_callback)
|
||||
|
||||
def task_callback(output):
|
||||
"""Callback for task completion"""
|
||||
logging.info(f"""
|
||||
=== TASK COMPLETED ===
|
||||
Time: {datetime.now()}
|
||||
Description: {output.description}
|
||||
Agent: {output.agent}
|
||||
Output: {output.raw[:200]}...
|
||||
""")
|
||||
|
||||
def internet_search_tool(query) -> list:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles and URLs.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
def main():
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You are an expert at a technology research group,
|
||||
skilled in identifying trends and analyzing complex data.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
tools=[internet_search_tool],
|
||||
llm="gpt-4o",
|
||||
markdown=True,
|
||||
reflect_llm="gpt-4o",
|
||||
min_reflect=2,
|
||||
max_reflect=4
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Tech Content Strategist",
|
||||
goal="Craft compelling content on tech advancements",
|
||||
backstory="""You are a content strategist known for
|
||||
making complex tech topics interesting and easy to understand.""",
|
||||
verbose=True,
|
||||
allow_delegation=True,
|
||||
llm="gpt-4o",
|
||||
tools=[],
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# Create tasks with callbacks
|
||||
task1 = Task(
|
||||
name="research_task",
|
||||
description="""Analyze 2024's AI advancements.
|
||||
Find major trends, new technologies, and their effects.""",
|
||||
expected_output="""A detailed report on 2024 AI advancements""",
|
||||
agent=researcher,
|
||||
tools=[internet_search_tool],
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="writing_task",
|
||||
description="""Create a blog post about major AI advancements using the insights you have.
|
||||
Make it interesting, clear, and suited for tech enthusiasts.
|
||||
It should be at least 4 paragraphs long.""",
|
||||
expected_output="A blog post of at least 4 paragraphs",
|
||||
agent=writer,
|
||||
context=[task1],
|
||||
callback=task_callback,
|
||||
tools=[]
|
||||
)
|
||||
|
||||
task3 = Task(
|
||||
name="json_task",
|
||||
description="""Create a json object with a title of "My Task" and content of "My content".""",
|
||||
expected_output="""JSON output with title and content""",
|
||||
agent=researcher,
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
task4 = Task(
|
||||
name="save_output_task",
|
||||
description="""Save the AI blog post to a file""",
|
||||
expected_output="""File saved successfully""",
|
||||
agent=writer,
|
||||
context=[task2],
|
||||
output_file='test.txt',
|
||||
create_directory=True,
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
# Create and run agents manager
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2, task3, task4],
|
||||
verbose=True,
|
||||
process="sequential",
|
||||
manager_llm="gpt-4o"
|
||||
)
|
||||
|
||||
agents.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,151 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from typing import List, Dict, Union
|
||||
from duckduckgo_search import DDGS
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
from langchain_community.utilities import WikipediaAPIWrapper
|
||||
|
||||
# 1. Tool
|
||||
def internet_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles, URLs, and snippets.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
def youtube_search_tool(query: str, inspect: bool = False, max_results: int = 2):
|
||||
"""
|
||||
Provide a custom wrapper around the YouTubeSearchTool.
|
||||
|
||||
Args:
|
||||
query (str): The search query for YouTube.
|
||||
inspect (bool): If True, returns tool inspection info instead of search results.
|
||||
max_results (int): Maximum number of results to return (default: 2).
|
||||
Returns:
|
||||
Union[List[str], dict]: List of YouTube video URLs or tool inspection info.
|
||||
"""
|
||||
yt = YouTubeSearchTool()
|
||||
|
||||
if inspect:
|
||||
inspection_info = {
|
||||
"type": type(yt),
|
||||
"attributes": [attr for attr in dir(yt) if not attr.startswith('_')],
|
||||
"methods": {
|
||||
"run": getattr(yt, 'run', None),
|
||||
"arun": getattr(yt, 'arun', None)
|
||||
},
|
||||
"properties": {
|
||||
"name": getattr(yt, 'name', 'youtube_search'),
|
||||
"description": getattr(yt, 'description', 'Search YouTube videos'),
|
||||
"return_direct": getattr(yt, 'return_direct', False)
|
||||
}
|
||||
}
|
||||
return inspection_info
|
||||
|
||||
# Format query with max_results
|
||||
formatted_query = f"{query}, {max_results}"
|
||||
return yt.run(formatted_query)
|
||||
|
||||
def wikipedia_search_tool(query: str, inspect: bool = False, max_chars: int = 4000, top_k: int = 3):
|
||||
"""
|
||||
Provide a custom wrapper around langchain_community's WikipediaAPIWrapper.
|
||||
|
||||
Args:
|
||||
query (str): A search query for Wikipedia.
|
||||
inspect (bool): If True, returns tool inspection info instead of search results.
|
||||
max_chars (int): Maximum characters to return (default: 4000).
|
||||
top_k (int): Number of top results to consider (default: 3).
|
||||
Returns:
|
||||
Union[str, dict]: Summary from Wikipedia or tool inspection info if inspect=True.
|
||||
"""
|
||||
w = WikipediaAPIWrapper(
|
||||
top_k_results=top_k,
|
||||
doc_content_chars_max=max_chars,
|
||||
lang='en'
|
||||
)
|
||||
|
||||
if inspect:
|
||||
inspection_info = {
|
||||
"type": type(w),
|
||||
"attributes": [attr for attr in dir(w) if not attr.startswith('_')],
|
||||
"methods": {
|
||||
"run": getattr(w, 'run', None),
|
||||
"arun": getattr(w, 'arun', None)
|
||||
},
|
||||
"properties": {
|
||||
"name": "wikipedia",
|
||||
"description": "Search and get summaries from Wikipedia",
|
||||
"top_k": w.top_k_results,
|
||||
"lang": w.lang,
|
||||
"max_chars": w.doc_content_chars_max
|
||||
}
|
||||
}
|
||||
return inspection_info
|
||||
|
||||
try:
|
||||
result = w.run(query)
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error searching Wikipedia: {str(e)}"
|
||||
|
||||
# 2. Agent
|
||||
data_agent = Agent(
|
||||
name="DataCollector",
|
||||
role="Search Specialist",
|
||||
goal="Perform internet searches to collect relevant information.",
|
||||
backstory="Expert in finding and organizing internet data from multiple sources.",
|
||||
tools=[internet_search_tool, youtube_search_tool, wikipedia_search_tool],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 3. Tasks
|
||||
collect_task = Task(
|
||||
description="Perform an internet search using the query: 'AI job trends in 2024'. Return results as a list of title, URL, and snippet.",
|
||||
expected_output="List of search results with titles, URLs, and snippets.",
|
||||
agent=data_agent,
|
||||
name="collect_data",
|
||||
is_start=True,
|
||||
next_tasks=["validate_data"]
|
||||
)
|
||||
|
||||
validate_task = Task(
|
||||
description="""Validate the collected data. Check if:
|
||||
1. At least 5 results are returned.
|
||||
2. Each result contains a title and a URL.
|
||||
Return validation_result as 'valid' or 'invalid' only no other text.""",
|
||||
expected_output="Validation result indicating if data is valid or invalid.",
|
||||
agent=data_agent,
|
||||
name="validate_data",
|
||||
task_type="decision",
|
||||
condition={
|
||||
"valid": [], # End the workflow on valid data
|
||||
"invalid": ["collect_data"] # Retry data collection on invalid data
|
||||
},
|
||||
)
|
||||
|
||||
# 4. Workflow
|
||||
agents = PraisonAIAgents(
|
||||
agents=[data_agent],
|
||||
tasks=[collect_task, validate_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,141 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, error_logs
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
def my_callback(output):
|
||||
print(f"Callback Task output: {output}")
|
||||
|
||||
def internet_search_tool(query) -> list:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles and URLs.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You are an expert at a technology research group,
|
||||
skilled in identifying trends and analyzing complex data.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
tools=[internet_search_tool],
|
||||
llm="gpt-4o",
|
||||
markdown=True,
|
||||
reflect_llm="gpt-4o",
|
||||
min_reflect=2,
|
||||
max_reflect=4
|
||||
)
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Tech Content Strategist",
|
||||
goal="Craft compelling content on tech advancements",
|
||||
backstory="""You are a content strategist known for
|
||||
making complex tech topics interesting and easy to understand.""",
|
||||
verbose=True,
|
||||
allow_delegation=True,
|
||||
llm="gpt-4o",
|
||||
tools=[],
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# Create tasks
|
||||
task1 = Task(
|
||||
name="research_task",
|
||||
description="""Analyze 2024's AI advancements.
|
||||
Find major trends, new technologies, and their effects.""",
|
||||
expected_output="""A detailed report on 2024 AI advancements""",
|
||||
agent=researcher,
|
||||
tools=[internet_search_tool]
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="writing_task",
|
||||
description="""Create a blog post about major AI advancements using the insights you have.
|
||||
Make it interesting, clear, and suited for tech enthusiasts.
|
||||
It should be at least 4 paragraphs long.
|
||||
Also, call the get_weather tool to get the weather in Paris.""",
|
||||
expected_output="A blog post of at least 4 paragraphs, and weather in Paris",
|
||||
agent=writer,
|
||||
context=[task1],
|
||||
callback=my_callback,
|
||||
tools=[]
|
||||
)
|
||||
|
||||
task3 = Task(
|
||||
name="json_task",
|
||||
description="""Create a json object with a title of "My Task" and content of "My content".""",
|
||||
expected_output="""JSON output with title and content""",
|
||||
agent=researcher,
|
||||
)
|
||||
|
||||
task4 = Task(
|
||||
name="save_output_task",
|
||||
description="""Save the AI blog post to a file""",
|
||||
expected_output="""File saved successfully""",
|
||||
agent=writer,
|
||||
context=[task2],
|
||||
output_file='outputs/ai_blog_post.txt',
|
||||
create_directory=True
|
||||
)
|
||||
|
||||
# Create and run agents manager
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2, task3, task4],
|
||||
verbose=False,
|
||||
process="sequential", # "sequential" or "hierarchical"
|
||||
manager_llm="gpt-4o"
|
||||
)
|
||||
|
||||
result = agents.start()
|
||||
|
||||
# Print results and error summary
|
||||
print("\n=== Task Results ===")
|
||||
for task_id, task_status in result['task_status'].items():
|
||||
print(f"Task {task_id}: {task_status}")
|
||||
if task_result := result['task_results'].get(task_id):
|
||||
print(f"Output: {task_result.raw[:200]}...") # Show first 200 chars
|
||||
|
||||
# Print task details
|
||||
print("\n=== Task Details ===")
|
||||
for i in range(4):
|
||||
print(agents.get_task_details(i))
|
||||
|
||||
# Print agent details
|
||||
print("\n=== Agent Details ===")
|
||||
print(agents.get_agent_details('Researcher'))
|
||||
print(agents.get_agent_details('Writer'))
|
||||
|
||||
# Print any errors
|
||||
if error_logs:
|
||||
print("\n=== Error Summary ===")
|
||||
for err in error_logs:
|
||||
print(f"- {err}")
|
||||
if "parsing self-reflection json" in err:
|
||||
print(" Reason: The self-reflection JSON response was not valid JSON.")
|
||||
elif "Error: Task with ID" in err:
|
||||
print(" Reason: Task ID referenced does not exist.")
|
||||
elif "saving task output to file" in err:
|
||||
print(" Reason: Possible file permissions or invalid path.")
|
||||
else:
|
||||
print(" Reason not identified")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,30 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
from langchain_community.utilities import WikipediaAPIWrapper
|
||||
|
||||
# Create an agent with both tools
|
||||
agent = Agent(
|
||||
name="SearchAgent",
|
||||
role="Research Assistant",
|
||||
goal="Search for information from multiple sources",
|
||||
backstory="I am an AI assistant that can search YouTube and Wikipedia.",
|
||||
tools=[YouTubeSearchTool, WikipediaAPIWrapper],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Create tasks to demonstrate both tools
|
||||
task = Task(
|
||||
name="search_task",
|
||||
description="Search for information about 'AI advancements' on both YouTube and Wikipedia",
|
||||
expected_output="Combined information from YouTube videos and Wikipedia articles",
|
||||
agent=agent
|
||||
)
|
||||
|
||||
# Create and start the workflow
|
||||
agents = PraisonAIAgents(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,73 @@
|
||||
from langchain_community.tools import YouTubeSearchTool
|
||||
from langchain_community.utilities import WikipediaAPIWrapper
|
||||
|
||||
def test_tools():
|
||||
"""Test both YouTube and Wikipedia tools"""
|
||||
|
||||
print("\n=== Testing YouTube Search ===")
|
||||
# Test YouTube
|
||||
yt = YouTubeSearchTool()
|
||||
|
||||
# Test basic search
|
||||
print("\nBasic YouTube Search:")
|
||||
query = "AI advancements 2024"
|
||||
print(f"Query: {query}")
|
||||
result = yt.run(query)
|
||||
print(f"Results: {result}")
|
||||
|
||||
# Test with max results
|
||||
print("\nYouTube Search with max results:")
|
||||
query = "AI advancements 2024, 3" # Format: query, max_results
|
||||
print(f"Query: {query}")
|
||||
result = yt.run(query)
|
||||
print(f"Results: {result}")
|
||||
|
||||
print("\n=== Testing Wikipedia Search ===")
|
||||
# Test Wikipedia
|
||||
wiki = WikipediaAPIWrapper(
|
||||
top_k_results=2,
|
||||
doc_content_chars_max=500
|
||||
)
|
||||
|
||||
# Test basic search
|
||||
print("\nBasic Wikipedia Search:")
|
||||
query = "Artificial Intelligence"
|
||||
print(f"Query: {query}")
|
||||
result = wiki.run(query)
|
||||
print(f"Results: {result[:200]}..." if result else "No result")
|
||||
|
||||
# Test with different parameters
|
||||
print("\nWikipedia Search with different language:")
|
||||
wiki_fr = WikipediaAPIWrapper(
|
||||
lang='fr',
|
||||
top_k_results=1,
|
||||
doc_content_chars_max=300
|
||||
)
|
||||
result = wiki_fr.run("Intelligence Artificielle")
|
||||
print(f"French Results: {result[:200]}..." if result else "No result")
|
||||
|
||||
def print_tool_info():
|
||||
"""Print detailed information about the tools"""
|
||||
|
||||
print("\n=== Tool Information ===")
|
||||
|
||||
# YouTube Tool Info
|
||||
print("\nYouTube Tool:")
|
||||
yt = YouTubeSearchTool()
|
||||
print("Type:", type(yt))
|
||||
print("Name:", getattr(yt, 'name', None))
|
||||
print("Description:", getattr(yt, 'description', None))
|
||||
print("Arguments:", getattr(yt, 'args', None))
|
||||
|
||||
# Wikipedia Tool Info
|
||||
print("\nWikipedia Tool:")
|
||||
wiki = WikipediaAPIWrapper()
|
||||
print("Type:", type(wiki))
|
||||
print("Available Settings:")
|
||||
print("- Language:", wiki.lang)
|
||||
print("- Top K Results:", wiki.top_k_results)
|
||||
print("- Max Characters:", wiki.doc_content_chars_max)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_tool_info()
|
||||
test_tools()
|
||||
@@ -0,0 +1,183 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import logging
|
||||
import os
|
||||
|
||||
def main():
|
||||
# Initialize memory config
|
||||
memory_config = {
|
||||
"provider": "rag",
|
||||
"use_embedding": True,
|
||||
"storage": {
|
||||
"type": "sqlite",
|
||||
"path": "./.praison/memory.db"
|
||||
},
|
||||
"rag_db_path": "./.praison/chroma_db"
|
||||
}
|
||||
|
||||
# Test facts
|
||||
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
|
||||
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
|
||||
fact3 = "The year the first Josinga was released is 2007"
|
||||
|
||||
# # Check if database exists
|
||||
# if os.path.exists("./memory.db"):
|
||||
# logger.info("Found existing memory database")
|
||||
# else:
|
||||
# logger.info("Creating new memory database")
|
||||
|
||||
# Create task config (without memory config since it's moved to PraisonAIAgents)
|
||||
task_config = {}
|
||||
|
||||
# Create agents with different roles
|
||||
researcher = Agent(
|
||||
role="Research Analyst",
|
||||
goal="Research and document key information about topics",
|
||||
backstory="Expert at analyzing and storing information in memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
retriever = Agent(
|
||||
role="Information Retriever",
|
||||
goal="Retrieve and verify stored information from memory",
|
||||
backstory="Specialist in searching and validating information from memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Task 1: Process the facts
|
||||
store_task = Task(
|
||||
description=f"""
|
||||
Process and analyze this information:
|
||||
1. {fact1}
|
||||
2. {fact2}
|
||||
3. {fact3}
|
||||
|
||||
Provide a clear summary of each fact.
|
||||
""",
|
||||
expected_output="""
|
||||
Clear statements summarizing each fact.
|
||||
Example format:
|
||||
1. [Summary of fact 1]
|
||||
2. [Summary of fact 2]
|
||||
3. [Summary of fact 3]
|
||||
""",
|
||||
agent=researcher
|
||||
)
|
||||
|
||||
# Task 2: Write essay about AI
|
||||
verify_task = Task(
|
||||
description="""
|
||||
write few points about AI
|
||||
""",
|
||||
expected_output="Points about AI",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Task 3: Query memory
|
||||
query_task = Task(
|
||||
description="""
|
||||
Using ONLY information found in memory:
|
||||
1. What is stored in memory about Hahanu?
|
||||
2. What ingredients for proloder are recorded in memory?
|
||||
3. What year is stored in memory for the Josinga release?
|
||||
|
||||
For each answer, cite the memory record you found.
|
||||
""",
|
||||
expected_output="Answers based solely on memory records with citations",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Task 4: Query both short-term and long-term memory
|
||||
query_both_task = Task(
|
||||
description="""
|
||||
Using ONLY information found in memory:
|
||||
1. What is stored in both short-term and long-term memory about Jujuha?
|
||||
2. What ingredients for proloder are recorded in both short-term and long-term memory?
|
||||
3. What year is stored in both short-term and long-term memory for the Josinga release?
|
||||
|
||||
For each answer, cite the memory record you found.
|
||||
""",
|
||||
expected_output="Answers based solely on memory records with citations",
|
||||
agent=retriever
|
||||
)
|
||||
|
||||
# Initialize PraisonAIAgents with memory configuration
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, retriever],
|
||||
tasks=[store_task, verify_task, query_task, query_both_task],
|
||||
verbose=True, # Use same verbose level as memory
|
||||
memory=True,
|
||||
embedder={
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-small"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# agents = PraisonAIAgents(
|
||||
# agents=[researcher, retriever],
|
||||
# tasks=[store_task, verify_task, query_task, query_both_task],
|
||||
# verbose=True, # Use same verbose level as memory
|
||||
# memory=True
|
||||
# )
|
||||
|
||||
# Execute tasks
|
||||
print("\nExecuting Memory Test Tasks...")
|
||||
print("-" * 50)
|
||||
agents.start()
|
||||
|
||||
# Use shared memory for final checks
|
||||
memory = agents.shared_memory
|
||||
|
||||
# Test memory retrieval with different quality thresholds
|
||||
if memory:
|
||||
print("\nFinal Memory Check:")
|
||||
print("-" * 50)
|
||||
|
||||
queries = ["Jujuha", "proloder", "Josinga"]
|
||||
for query in queries:
|
||||
print(f"\nSearching memory for: {query}")
|
||||
|
||||
# Search in both short-term and long-term memory
|
||||
print("\nShort-term memory results:")
|
||||
stm_results = memory.search_short_term(query)
|
||||
if stm_results:
|
||||
for item in stm_results:
|
||||
print(f"Content: {item.get('content', '')[:200]}")
|
||||
if 'meta' in item:
|
||||
print(f"Metadata: {item['meta']}")
|
||||
print("-" * 20)
|
||||
else:
|
||||
print("No results found in short-term memory")
|
||||
|
||||
print("\nLong-term memory results:")
|
||||
ltm_results = memory.search_long_term(query)
|
||||
if ltm_results:
|
||||
for item in ltm_results:
|
||||
print(f"Content: {item.get('text', '')[:200]}")
|
||||
if 'metadata' in item:
|
||||
print(f"Metadata: {item['metadata']}")
|
||||
print("-" * 20)
|
||||
else:
|
||||
print("No results found in long-term memory")
|
||||
|
||||
# Also check ChromaDB if using RAG
|
||||
if memory.use_rag and hasattr(memory, "chroma_col"):
|
||||
print("\nChromaDB results:")
|
||||
try:
|
||||
all_items = memory.chroma_col.get()
|
||||
print(f"Found {len(all_items['ids'])} items in ChromaDB")
|
||||
for i in range(len(all_items['ids'])):
|
||||
print(f"ID: {all_items['ids'][i]}")
|
||||
print(f"Content: {all_items['documents'][i][:200]}")
|
||||
print(f"Metadata: {all_items['metadatas'][i]}")
|
||||
print("-" * 20)
|
||||
except Exception as e:
|
||||
print(f"Error querying ChromaDB: {e}")
|
||||
|
||||
print("-" * 30)
|
||||
else:
|
||||
print("\nNo memory available for final checks")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
from praisonaiagents.agents.agents import Agent, Task, PraisonAIAgents
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
# Test facts
|
||||
fact1 = "The capital city of Jujuha is Hahanu and its population is 102300"
|
||||
fact2 = "Three main ingredients in a classic proloder are eggs, sugar, and flour"
|
||||
fact3 = "The year the first Josinga was released is 2007"
|
||||
|
||||
fact_agent = Agent(
|
||||
name="Fact Agent",
|
||||
instructions="You are a fact agent, you store and retrieve facts in memory",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
research_agent = Agent(
|
||||
name="Research Agent",
|
||||
instructions="You are a research analyst, you research and document key points about topics",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
blog_agent = Agent(
|
||||
name="Blog Agent",
|
||||
instructions="You are a blog writer, you write a blog post about the research",
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
fact_task = Task(
|
||||
description="Store the following facts in memory: " + fact1 + ", " + fact2 + ", " + fact3,
|
||||
agent=fact_agent
|
||||
)
|
||||
|
||||
research_task = Task(
|
||||
description="Research and document 2 key points about AI",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task2 = Task(
|
||||
description="Research and document 2 key points about AI in healthcare",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task3 = Task(
|
||||
description="Research and document 2 key points about AI in education",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
research_task4 = Task(
|
||||
description="Research and document 2 key points about AI in finance",
|
||||
agent=research_agent
|
||||
)
|
||||
|
||||
blog_task = Task(
|
||||
description="Write a blog post about Jujuha",
|
||||
agent=blog_agent
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(
|
||||
agents=[fact_agent, research_agent, blog_agent],
|
||||
tasks=[fact_task, research_task, research_task2, research_task3, research_task4, blog_task],
|
||||
memory=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,6 @@
|
||||
from praisonaiagents import Agent, Agents, Tools
|
||||
|
||||
research_agent = Agent(instructions="You are a research agent to search internet about AI 2024", tools=[Tools.internet_search])
|
||||
summarise_agent = Agent(instructions="You are a summarize agent to summarise in points")
|
||||
agents = Agents(agents=[research_agent, summarise_agent])
|
||||
agents.start()
|
||||
@@ -0,0 +1,59 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create Vision Analysis Agent
|
||||
vision_agent = Agent(
|
||||
name="VisionAnalyst",
|
||||
role="Computer Vision Specialist",
|
||||
goal="Analyze images and videos to extract meaningful information",
|
||||
backstory="""You are an expert in computer vision and image analysis.
|
||||
You excel at describing images, detecting objects, and understanding visual content.""",
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 1. Task with Image URL
|
||||
task1 = Task(
|
||||
name="analyze_landmark",
|
||||
description="Describe this famous landmark and its architectural features.",
|
||||
expected_output="Detailed description of the landmark's architecture and significance",
|
||||
agent=vision_agent,
|
||||
images=["https://upload.wikimedia.org/wikipedia/commons/b/bf/Krakow_-_Kosciol_Mariacki.jpg"]
|
||||
)
|
||||
|
||||
# 2. Task with Local Image File
|
||||
task2 = Task(
|
||||
name="analyze_local_image",
|
||||
description="What objects can you see in this image? Describe their arrangement.",
|
||||
expected_output="Detailed description of objects and their spatial relationships",
|
||||
agent=vision_agent,
|
||||
images=["image.jpg"]
|
||||
)
|
||||
|
||||
# 3. Task with Video File
|
||||
task3 = Task(
|
||||
name="analyze_video",
|
||||
description="""Watch this video and provide:
|
||||
1. A summary of the main events
|
||||
2. Key objects and people visible
|
||||
3. Any text or important information shown
|
||||
4. The overall context and setting""",
|
||||
expected_output="Comprehensive analysis of the video content",
|
||||
agent=vision_agent,
|
||||
images=["video.mp4"]
|
||||
)
|
||||
|
||||
# Create PraisonAIAgents instance
|
||||
agents = PraisonAIAgents(
|
||||
agents=[vision_agent],
|
||||
tasks=[task1, task2, task3],
|
||||
process="sequential",
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
result = agents.start()
|
||||
|
||||
# Print results
|
||||
for task_id, task_result in result["task_results"].items():
|
||||
print(f"\nTask {task_id} Result:")
|
||||
print(task_result.raw)
|
||||
@@ -0,0 +1,110 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_time_check():
|
||||
current_time = int(time.time())
|
||||
if current_time % 3 == 0:
|
||||
result = 1
|
||||
elif current_time % 3 == 1:
|
||||
result = 2
|
||||
else:
|
||||
result = 3
|
||||
print(f"Time: {current_time}, Result: {result}")
|
||||
return result
|
||||
|
||||
# Create orchestrator and worker agents
|
||||
router = Agent(
|
||||
name="Router",
|
||||
role="Task router",
|
||||
goal="Distribute tasks to based on response from get_time_check",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
worker1 = Agent(
|
||||
name="Worker 1",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 1",
|
||||
)
|
||||
|
||||
worker2 = Agent(
|
||||
name="Worker 2",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 2",
|
||||
)
|
||||
|
||||
worker3 = Agent(
|
||||
name="Worker 3",
|
||||
role="Specialized worker",
|
||||
goal="Handle specific subtask type 3",
|
||||
)
|
||||
|
||||
synthesizer = Agent(
|
||||
name="Synthesizer",
|
||||
role="Result synthesizer",
|
||||
goal="Combine and synthesize worker outputs",
|
||||
)
|
||||
|
||||
# Create orchestrated workflow tasks
|
||||
router_task = Task(
|
||||
name="route_task",
|
||||
description="Analyze input from get_time_check and route to appropriate workers",
|
||||
expected_output="Task routing decision, 1 , 2 or 3",
|
||||
agent=router,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["worker1_task", "worker2_task", "worker3_task"],
|
||||
condition={
|
||||
"1": ["worker1_task"],
|
||||
"2": ["worker2_task"],
|
||||
"3": ["worker3_task"]
|
||||
}
|
||||
)
|
||||
|
||||
worker1_task = Task(
|
||||
name="worker1_task",
|
||||
description="Process type 1 operation",
|
||||
expected_output="Worker 1 result",
|
||||
agent=worker1,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
worker2_task = Task(
|
||||
name="worker2_task",
|
||||
description="Process type 2 operation",
|
||||
expected_output="Worker 2 result",
|
||||
agent=worker2,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
worker3_task = Task(
|
||||
name="worker3_task",
|
||||
description="Process type 3 operation",
|
||||
expected_output="Worker 3 result",
|
||||
agent=worker3,
|
||||
next_tasks=["synthesize_task"]
|
||||
)
|
||||
|
||||
synthesize_task = Task(
|
||||
name="synthesize_task",
|
||||
description="Synthesize worker results into final output",
|
||||
expected_output="Final synthesized result",
|
||||
agent=synthesizer,
|
||||
context=[worker1_task, worker2_task, worker3_task]
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[router, worker1, worker2, worker3, synthesizer],
|
||||
tasks=[router_task, worker1_task, worker2_task, worker3_task, synthesize_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run orchestrated workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nOrchestrator-Workers Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,95 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
|
||||
def process_time():
|
||||
"""Simulate processing"""
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(f"Processing at: {current_time}")
|
||||
return f"Processed at {current_time}"
|
||||
|
||||
# Create parallel processing agents
|
||||
agent1 = Agent(
|
||||
name="Processor 1",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
agent2 = Agent(
|
||||
name="Processor 2",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
agent3 = Agent(
|
||||
name="Processor 3",
|
||||
role="Time collector",
|
||||
goal="Get the time and return it",
|
||||
tools=[process_time]
|
||||
)
|
||||
|
||||
aggregator = Agent(
|
||||
name="Aggregator",
|
||||
role="Result aggregator",
|
||||
goal="Collect all the processed time from all tasks"
|
||||
)
|
||||
|
||||
# Create parallel tasks with memory disabled
|
||||
task1 = Task(
|
||||
name="process_1",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent1,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="process_2",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent2,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
task3 = Task(
|
||||
name="process_3",
|
||||
description="Use process_time tool to get the time",
|
||||
expected_output="processed time",
|
||||
agent=agent3,
|
||||
is_start=True,
|
||||
async_execution=True
|
||||
)
|
||||
|
||||
aggregate_task = Task(
|
||||
name="aggregate",
|
||||
description="Collect all the processed time from all tasks",
|
||||
expected_output="Output all the processed time from all tasks and just the time",
|
||||
agent=aggregator,
|
||||
context=[task1, task2, task3]
|
||||
)
|
||||
|
||||
async def main():
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[agent1, agent2, agent3, aggregator],
|
||||
tasks=[task1, task2, task3, aggregate_task],
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
# Run parallel workflow
|
||||
results = await workflow.astart()
|
||||
|
||||
# Print results
|
||||
print("\nParallel Processing Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
|
||||
# Run the async main function
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Praison AI Agents - A package for hierarchical AI agent task execution
|
||||
"""
|
||||
|
||||
from .agent.agent import Agent
|
||||
from .agents.agents import PraisonAIAgents
|
||||
from .task.task import Task
|
||||
from .tools.tools import Tools
|
||||
from .agents.autoagents import AutoAgents
|
||||
from .memory.memory import Memory
|
||||
from .main import (
|
||||
TaskOutput,
|
||||
ReflectionOutput,
|
||||
display_interaction,
|
||||
display_self_reflection,
|
||||
display_instruction,
|
||||
display_tool_call,
|
||||
display_error,
|
||||
display_generating,
|
||||
clean_triple_backticks,
|
||||
error_logs,
|
||||
register_display_callback,
|
||||
sync_display_callbacks,
|
||||
async_display_callbacks,
|
||||
)
|
||||
|
||||
# Add Agents as an alias for PraisonAIAgents
|
||||
Agents = PraisonAIAgents
|
||||
|
||||
__all__ = [
|
||||
'Agent',
|
||||
'PraisonAIAgents',
|
||||
'Agents',
|
||||
'Tools',
|
||||
'Task',
|
||||
'TaskOutput',
|
||||
'ReflectionOutput',
|
||||
'AutoAgents',
|
||||
'Memory',
|
||||
'display_interaction',
|
||||
'display_self_reflection',
|
||||
'display_instruction',
|
||||
'display_tool_call',
|
||||
'display_error',
|
||||
'display_generating',
|
||||
'clean_triple_backticks',
|
||||
'error_logs',
|
||||
'register_display_callback',
|
||||
'sync_display_callbacks',
|
||||
'async_display_callbacks',
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Agent module for AI agents"""
|
||||
from .agent import Agent
|
||||
|
||||
__all__ = ['Agent']
|
||||
@@ -0,0 +1,765 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import List, Optional, Any, Dict, Union, Literal, TYPE_CHECKING
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from openai import AsyncOpenAI
|
||||
from ..main import (
|
||||
display_error,
|
||||
display_tool_call,
|
||||
display_instruction,
|
||||
display_interaction,
|
||||
display_generating,
|
||||
display_self_reflection,
|
||||
ReflectionOutput,
|
||||
client,
|
||||
error_logs,
|
||||
adisplay_instruction
|
||||
)
|
||||
import inspect
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..task.task import Task
|
||||
|
||||
class Agent:
|
||||
def _generate_tool_definition(self, function_name):
|
||||
"""
|
||||
Generate a tool definition from a function name by inspecting the function.
|
||||
"""
|
||||
logging.debug(f"Attempting to generate tool definition for: {function_name}")
|
||||
|
||||
# First try to get the tool definition if it exists
|
||||
tool_def_name = f"{function_name}_definition"
|
||||
tool_def = globals().get(tool_def_name)
|
||||
logging.debug(f"Looking for {tool_def_name} in globals: {tool_def is not None}")
|
||||
|
||||
if not tool_def:
|
||||
import __main__
|
||||
tool_def = getattr(__main__, tool_def_name, None)
|
||||
logging.debug(f"Looking for {tool_def_name} in __main__: {tool_def is not None}")
|
||||
|
||||
if tool_def:
|
||||
logging.debug(f"Found tool definition: {tool_def}")
|
||||
return tool_def
|
||||
|
||||
# Try to find the function in the agent's tools list first
|
||||
func = None
|
||||
for tool in self.tools:
|
||||
if callable(tool) and getattr(tool, '__name__', '') == function_name:
|
||||
func = tool
|
||||
break
|
||||
|
||||
logging.debug(f"Looking for {function_name} in agent tools: {func is not None}")
|
||||
|
||||
# If not found in tools, try globals and main
|
||||
if not func:
|
||||
func = globals().get(function_name)
|
||||
logging.debug(f"Looking for {function_name} in globals: {func is not None}")
|
||||
|
||||
if not func:
|
||||
import __main__
|
||||
func = getattr(__main__, function_name, None)
|
||||
logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
|
||||
|
||||
if not func or not callable(func):
|
||||
logging.debug(f"Function {function_name} not found or not callable")
|
||||
return None
|
||||
|
||||
import inspect
|
||||
# Langchain tools
|
||||
if inspect.isclass(func) and hasattr(func, 'run'):
|
||||
original_func = func
|
||||
func = func.run
|
||||
function_name = original_func.__name__
|
||||
|
||||
sig = inspect.signature(func)
|
||||
logging.debug(f"Function signature: {sig}")
|
||||
|
||||
# Skip self, *args, **kwargs, so they don't get passed in arguments
|
||||
parameters_list = []
|
||||
for name, param in sig.parameters.items():
|
||||
if name == "self":
|
||||
continue
|
||||
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
||||
continue
|
||||
parameters_list.append((name, param))
|
||||
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
|
||||
# Parse docstring for parameter descriptions
|
||||
docstring = inspect.getdoc(func)
|
||||
logging.debug(f"Function docstring: {docstring}")
|
||||
|
||||
param_descriptions = {}
|
||||
if docstring:
|
||||
import re
|
||||
param_section = re.split(r'\s*Args:\s*', docstring)
|
||||
logging.debug(f"Param section split: {param_section}")
|
||||
if len(param_section) > 1:
|
||||
param_lines = param_section[1].split('\n')
|
||||
for line in param_lines:
|
||||
line = line.strip()
|
||||
if line and ':' in line:
|
||||
param_name, param_desc = line.split(':', 1)
|
||||
param_descriptions[param_name.strip()] = param_desc.strip()
|
||||
|
||||
logging.debug(f"Parameter descriptions: {param_descriptions}")
|
||||
|
||||
for name, param in parameters_list:
|
||||
param_type = "string" # Default type
|
||||
if param.annotation != inspect.Parameter.empty:
|
||||
if param.annotation == int:
|
||||
param_type = "integer"
|
||||
elif param.annotation == float:
|
||||
param_type = "number"
|
||||
elif param.annotation == bool:
|
||||
param_type = "boolean"
|
||||
elif param.annotation == list:
|
||||
param_type = "array"
|
||||
elif param.annotation == dict:
|
||||
param_type = "object"
|
||||
|
||||
param_info = {"type": param_type}
|
||||
if name in param_descriptions:
|
||||
param_info["description"] = param_descriptions[name]
|
||||
|
||||
parameters["properties"][name] = param_info
|
||||
if param.default == inspect.Parameter.empty:
|
||||
parameters["required"].append(name)
|
||||
|
||||
logging.debug(f"Generated parameters: {parameters}")
|
||||
|
||||
# Extract description from docstring
|
||||
description = docstring.split('\n')[0] if docstring else f"Function {function_name}"
|
||||
|
||||
tool_def = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": function_name,
|
||||
"description": description,
|
||||
"parameters": parameters
|
||||
}
|
||||
}
|
||||
logging.debug(f"Generated tool definition: {tool_def}")
|
||||
return tool_def
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
role: Optional[str] = None,
|
||||
goal: Optional[str] = None,
|
||||
backstory: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
llm: Optional[Union[str, Any]] = None,
|
||||
tools: Optional[List[Any]] = None,
|
||||
function_calling_llm: Optional[Any] = None,
|
||||
max_iter: int = 20,
|
||||
max_rpm: Optional[int] = None,
|
||||
max_execution_time: Optional[int] = None,
|
||||
memory: Optional[Any] = None,
|
||||
verbose: bool = True,
|
||||
allow_delegation: bool = False,
|
||||
step_callback: Optional[Any] = None,
|
||||
cache: bool = True,
|
||||
system_template: Optional[str] = None,
|
||||
prompt_template: Optional[str] = None,
|
||||
response_template: Optional[str] = None,
|
||||
allow_code_execution: Optional[bool] = False,
|
||||
max_retry_limit: int = 2,
|
||||
respect_context_window: bool = True,
|
||||
code_execution_mode: Literal["safe", "unsafe"] = "safe",
|
||||
embedder_config: Optional[Dict[str, Any]] = None,
|
||||
knowledge_sources: Optional[List[Any]] = None,
|
||||
use_system_prompt: Optional[bool] = True,
|
||||
markdown: bool = True,
|
||||
self_reflect: bool = False,
|
||||
max_reflect: int = 3,
|
||||
min_reflect: int = 1,
|
||||
reflect_llm: Optional[str] = None
|
||||
):
|
||||
# Handle backward compatibility for required fields
|
||||
if all(x is None for x in [name, role, goal, backstory, instructions]):
|
||||
raise ValueError("At least one of name, role, goal, backstory, or instructions must be provided")
|
||||
|
||||
# If instructions are provided, use them to set role, goal, and backstory
|
||||
if instructions:
|
||||
self.name = name or "Agent"
|
||||
self.role = role or "Assistant"
|
||||
self.goal = goal or instructions
|
||||
self.backstory = backstory or instructions
|
||||
# Set self_reflect to False by default for instruction-based agents
|
||||
self.self_reflect = False if self_reflect is None else self_reflect
|
||||
else:
|
||||
# Use provided values or defaults
|
||||
self.name = name or "Agent"
|
||||
self.role = role or "Assistant"
|
||||
self.goal = goal or "Help the user with their tasks"
|
||||
self.backstory = backstory or "I am an AI assistant"
|
||||
# Default to True for traditional agents if not specified
|
||||
self.self_reflect = True if self_reflect is None else self_reflect
|
||||
|
||||
self.instructions = instructions
|
||||
# Check for model name in environment variable if not provided
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
self.tools = tools if tools else [] # Store original tools
|
||||
self.function_calling_llm = function_calling_llm
|
||||
self.max_iter = max_iter
|
||||
self.max_rpm = max_rpm
|
||||
self.max_execution_time = max_execution_time
|
||||
self.memory = memory
|
||||
self.verbose = verbose
|
||||
self.allow_delegation = allow_delegation
|
||||
self.step_callback = step_callback
|
||||
self.cache = cache
|
||||
self.system_template = system_template
|
||||
self.prompt_template = prompt_template
|
||||
self.response_template = response_template
|
||||
self.allow_code_execution = allow_code_execution
|
||||
self.max_retry_limit = max_retry_limit
|
||||
self.respect_context_window = respect_context_window
|
||||
self.code_execution_mode = code_execution_mode
|
||||
self.embedder_config = embedder_config
|
||||
self.knowledge_sources = knowledge_sources
|
||||
self.use_system_prompt = use_system_prompt
|
||||
self.chat_history = []
|
||||
self.markdown = markdown
|
||||
self.max_reflect = max_reflect
|
||||
self.min_reflect = min_reflect
|
||||
# Use the same model selection logic for reflect_llm
|
||||
self.reflect_llm = reflect_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
self.console = Console() # Create a single console instance for the agent
|
||||
|
||||
# Initialize system prompt
|
||||
self.system_prompt = f"""{self.backstory}\n
|
||||
Your Role: {self.role}\n
|
||||
Your Goal: {self.goal}
|
||||
"""
|
||||
|
||||
def generate_task(self) -> 'Task':
|
||||
"""Generate a Task object from the agent's instructions"""
|
||||
from ..task.task import Task
|
||||
|
||||
description = self.instructions if self.instructions else f"Execute task as {self.role} with goal: {self.goal}"
|
||||
expected_output = "Complete the assigned task successfully"
|
||||
|
||||
return Task(
|
||||
name=self.name,
|
||||
description=description,
|
||||
expected_output=expected_output,
|
||||
agent=self,
|
||||
tools=self.tools
|
||||
)
|
||||
|
||||
def execute_tool(self, function_name, arguments):
|
||||
"""
|
||||
Execute a tool dynamically based on the function name and arguments.
|
||||
"""
|
||||
logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
|
||||
|
||||
# Try to find the function in the agent's tools list first
|
||||
func = None
|
||||
for tool in self.tools:
|
||||
if (callable(tool) and getattr(tool, '__name__', '') == function_name) or \
|
||||
(inspect.isclass(tool) and tool.__name__ == function_name):
|
||||
func = tool
|
||||
break
|
||||
|
||||
if func is None:
|
||||
# If not found in tools, try globals and main
|
||||
func = globals().get(function_name)
|
||||
if not func:
|
||||
import __main__
|
||||
func = getattr(__main__, function_name, None)
|
||||
|
||||
if func:
|
||||
try:
|
||||
# If it's a class with run method, instantiate and call run
|
||||
if inspect.isclass(func) and hasattr(func, 'run'):
|
||||
instance = func()
|
||||
# Extract only the parameters that run() expects
|
||||
run_params = {k: v for k, v in arguments.items()
|
||||
if k in inspect.signature(instance.run).parameters
|
||||
and k != 'self'}
|
||||
return instance.run(**run_params)
|
||||
# Otherwise treat as regular function
|
||||
elif callable(func):
|
||||
return func(**arguments)
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
logging.error(f"Error executing tool {function_name}: {error_msg}")
|
||||
return {"error": error_msg}
|
||||
|
||||
error_msg = f"Tool '{function_name}' is not callable"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def clear_history(self):
|
||||
self.chat_history = []
|
||||
|
||||
def __str__(self):
|
||||
return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
|
||||
|
||||
def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True):
|
||||
start_time = time.time()
|
||||
logging.debug(f"{self.name} sending messages to LLM: {messages}")
|
||||
|
||||
formatted_tools = []
|
||||
if tools is None:
|
||||
tools = self.tools
|
||||
if tools:
|
||||
for tool in tools:
|
||||
if isinstance(tool, str):
|
||||
# Generate tool definition for string tool names
|
||||
tool_def = self._generate_tool_definition(tool)
|
||||
if tool_def:
|
||||
formatted_tools.append(tool_def)
|
||||
else:
|
||||
logging.warning(f"Could not generate definition for tool: {tool}")
|
||||
elif isinstance(tool, dict):
|
||||
formatted_tools.append(tool)
|
||||
elif hasattr(tool, "to_openai_tool"):
|
||||
formatted_tools.append(tool.to_openai_tool())
|
||||
elif callable(tool):
|
||||
formatted_tools.append(self._generate_tool_definition(tool.__name__))
|
||||
else:
|
||||
logging.warning(f"Tool {tool} not recognized")
|
||||
|
||||
try:
|
||||
initial_response = client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
tools=formatted_tools if formatted_tools else None,
|
||||
stream=False
|
||||
)
|
||||
|
||||
tool_calls = getattr(initial_response.choices[0].message, 'tool_calls', None)
|
||||
|
||||
if tool_calls:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": initial_response.choices[0].message.content,
|
||||
"tool_calls": tool_calls
|
||||
})
|
||||
|
||||
for tool_call in tool_calls:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
|
||||
if self.verbose:
|
||||
display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}")
|
||||
|
||||
tool_result = self.execute_tool(function_name, arguments)
|
||||
results_str = json.dumps(tool_result) if tool_result else "Function returned an empty output"
|
||||
|
||||
if self.verbose:
|
||||
display_tool_call(f"Function '{function_name}' returned: {results_str}")
|
||||
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": results_str
|
||||
})
|
||||
|
||||
if stream:
|
||||
response_stream = client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=True
|
||||
)
|
||||
full_response_text = ""
|
||||
|
||||
# Create Live display with proper configuration
|
||||
with Live(
|
||||
display_generating("", start_time),
|
||||
console=self.console,
|
||||
refresh_per_second=4,
|
||||
transient=True, # Changed to False to preserve output
|
||||
vertical_overflow="ellipsis",
|
||||
auto_refresh=True
|
||||
) as live:
|
||||
for chunk in response_stream:
|
||||
if chunk.choices[0].delta.content:
|
||||
full_response_text += chunk.choices[0].delta.content
|
||||
live.update(display_generating(full_response_text, start_time))
|
||||
|
||||
# Clear the last generating display with a blank line
|
||||
self.console.print()
|
||||
|
||||
final_response = client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=False
|
||||
)
|
||||
return final_response
|
||||
else:
|
||||
if tool_calls:
|
||||
final_response = client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=False
|
||||
)
|
||||
return final_response
|
||||
else:
|
||||
return initial_response
|
||||
|
||||
except Exception as e:
|
||||
display_error(f"Error in chat completion: {e}")
|
||||
return None
|
||||
|
||||
def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
|
||||
if self.use_system_prompt:
|
||||
system_prompt = f"""{self.backstory}\n
|
||||
Your Role: {self.role}\n
|
||||
Your Goal: {self.goal}
|
||||
"""
|
||||
if output_json:
|
||||
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
|
||||
elif output_pydantic:
|
||||
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_pydantic.schema_json()}"
|
||||
else:
|
||||
system_prompt = None
|
||||
|
||||
messages = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.extend(self.chat_history)
|
||||
|
||||
# Modify prompt if output_json or output_pydantic is specified
|
||||
original_prompt = prompt
|
||||
if output_json or output_pydantic:
|
||||
if isinstance(prompt, str):
|
||||
prompt += "\nReturn ONLY a valid JSON object. No other text or explanation."
|
||||
elif isinstance(prompt, list):
|
||||
# For multimodal prompts, append to the text content
|
||||
for item in prompt:
|
||||
if item["type"] == "text":
|
||||
item["text"] += "\nReturn ONLY a valid JSON object. No other text or explanation."
|
||||
break
|
||||
|
||||
if isinstance(prompt, list):
|
||||
# If we receive a multimodal prompt list, place it directly in the user message
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
else:
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
final_response_text = None
|
||||
reflection_count = 0
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
if self.verbose:
|
||||
# Handle both string and list prompts for instruction display
|
||||
display_text = prompt
|
||||
if isinstance(prompt, list):
|
||||
# Extract text content from multimodal prompt
|
||||
display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
|
||||
|
||||
if display_text and str(display_text).strip():
|
||||
# Pass agent information to display_instruction
|
||||
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
display_instruction(
|
||||
f"Agent {self.name} is processing prompt: {display_text}",
|
||||
console=self.console,
|
||||
agent_name=self.name,
|
||||
agent_role=self.role,
|
||||
agent_tools=agent_tools
|
||||
)
|
||||
|
||||
response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
|
||||
if not response:
|
||||
return None
|
||||
|
||||
tool_calls = getattr(response.choices[0].message, 'tool_calls', None)
|
||||
response_text = response.choices[0].message.content.strip()
|
||||
|
||||
if tool_calls:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": response_text,
|
||||
"tool_calls": tool_calls
|
||||
})
|
||||
|
||||
for tool_call in tool_calls:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
|
||||
if self.verbose:
|
||||
display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}", console=self.console)
|
||||
|
||||
tool_result = self.execute_tool(function_name, arguments)
|
||||
|
||||
if tool_result:
|
||||
if self.verbose:
|
||||
display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=self.console)
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": json.dumps(tool_result)
|
||||
})
|
||||
else:
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": "Function returned an empty output"
|
||||
})
|
||||
|
||||
response = self._chat_completion(messages, temperature=temperature)
|
||||
if not response:
|
||||
return None
|
||||
response_text = response.choices[0].message.content.strip()
|
||||
|
||||
# Handle output_json or output_pydantic if specified
|
||||
if output_json or output_pydantic:
|
||||
# Add to chat history and return raw response
|
||||
self.chat_history.append({"role": "user", "content": original_prompt})
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
if self.verbose:
|
||||
display_interaction(original_prompt, response_text, markdown=self.markdown,
|
||||
generation_time=time.time() - start_time, console=self.console)
|
||||
return response_text
|
||||
|
||||
if not self.self_reflect:
|
||||
self.chat_history.append({"role": "user", "content": original_prompt})
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
if self.verbose:
|
||||
logging.debug(f"Agent {self.name} final response: {response_text}")
|
||||
display_interaction(original_prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
|
||||
return response_text
|
||||
|
||||
reflection_prompt = f"""
|
||||
Reflect on your previous response: '{response_text}'.
|
||||
Identify any flaws, improvements, or actions.
|
||||
Provide a "satisfactory" status ('yes' or 'no').
|
||||
Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
"""
|
||||
logging.debug(f"{self.name} reflection attempt {reflection_count+1}, sending prompt: {reflection_prompt}")
|
||||
messages.append({"role": "user", "content": reflection_prompt})
|
||||
|
||||
try:
|
||||
reflection_response = client.beta.chat.completions.parse(
|
||||
model=self.reflect_llm if self.reflect_llm else self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
response_format=ReflectionOutput
|
||||
)
|
||||
|
||||
reflection_output = reflection_response.choices[0].message.parsed
|
||||
|
||||
if self.verbose:
|
||||
display_self_reflection(f"Agent {self.name} self reflection (using {self.reflect_llm if self.reflect_llm else self.llm}): reflection='{reflection_output.reflection}' satisfactory='{reflection_output.satisfactory}'", console=self.console)
|
||||
|
||||
messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"})
|
||||
|
||||
# Only consider satisfactory after minimum reflections
|
||||
if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1:
|
||||
if self.verbose:
|
||||
display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections", console=self.console)
|
||||
self.chat_history.append({"role": "user", "content": prompt})
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
|
||||
return response_text
|
||||
|
||||
# Check if we've hit max reflections
|
||||
if reflection_count >= self.max_reflect - 1:
|
||||
if self.verbose:
|
||||
display_self_reflection("Maximum reflection count reached, returning current response", console=self.console)
|
||||
self.chat_history.append({"role": "user", "content": prompt})
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
|
||||
return response_text
|
||||
|
||||
logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process")
|
||||
messages.append({"role": "user", "content": "Now regenerate your response using the reflection you made"})
|
||||
response = self._chat_completion(messages, temperature=temperature, tools=None, stream=True)
|
||||
response_text = response.choices[0].message.content.strip()
|
||||
reflection_count += 1
|
||||
continue # Continue the loop for more reflections
|
||||
|
||||
except Exception as e:
|
||||
display_error(f"Error in parsing self-reflection json {e}. Retrying", console=self.console)
|
||||
logging.error("Reflection parsing failed.", exc_info=True)
|
||||
messages.append({"role": "assistant", "content": f"Self Reflection failed."})
|
||||
reflection_count += 1
|
||||
continue # Continue even after error to try again
|
||||
|
||||
except Exception as e:
|
||||
display_error(f"Error in chat: {e}", console=self.console)
|
||||
return None
|
||||
|
||||
def clean_json_output(self, output: str) -> str:
|
||||
"""Clean and extract JSON from response text."""
|
||||
cleaned = output.strip()
|
||||
# Remove markdown code blocks if present
|
||||
if cleaned.startswith("```json"):
|
||||
cleaned = cleaned[len("```json"):].strip()
|
||||
if cleaned.startswith("```"):
|
||||
cleaned = cleaned[len("```"):].strip()
|
||||
if cleaned.endswith("```"):
|
||||
cleaned = cleaned[:-3].strip()
|
||||
return cleaned
|
||||
|
||||
async def achat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pydantic=None):
|
||||
"""Async version of chat method"""
|
||||
try:
|
||||
# Build system prompt
|
||||
system_prompt = self.system_prompt
|
||||
if output_json:
|
||||
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
|
||||
elif output_pydantic:
|
||||
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_pydantic.schema_json()}"
|
||||
|
||||
# Build messages
|
||||
if isinstance(prompt, str):
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt + ("\nReturn ONLY a valid JSON object. No other text or explanation." if (output_json or output_pydantic) else "")}
|
||||
]
|
||||
else:
|
||||
# For multimodal prompts
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
if output_json or output_pydantic:
|
||||
# Add JSON instruction to text content
|
||||
for item in messages[-1]["content"]:
|
||||
if item["type"] == "text":
|
||||
item["text"] += "\nReturn ONLY a valid JSON object. No other text or explanation."
|
||||
break
|
||||
|
||||
# Display instruction with agent info if verbose
|
||||
if self.verbose:
|
||||
display_text = prompt
|
||||
if isinstance(prompt, list):
|
||||
display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
|
||||
|
||||
if display_text and str(display_text).strip():
|
||||
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
await adisplay_instruction(
|
||||
f"Agent {self.name} is processing prompt: {display_text}",
|
||||
console=self.console,
|
||||
agent_name=self.name,
|
||||
agent_role=self.role,
|
||||
agent_tools=agent_tools
|
||||
)
|
||||
|
||||
# Format tools if provided
|
||||
formatted_tools = []
|
||||
if tools:
|
||||
for tool in tools:
|
||||
if isinstance(tool, str):
|
||||
tool_def = self._generate_tool_definition(tool)
|
||||
if tool_def:
|
||||
formatted_tools.append(tool_def)
|
||||
elif isinstance(tool, dict):
|
||||
formatted_tools.append(tool)
|
||||
elif hasattr(tool, "to_openai_tool"):
|
||||
formatted_tools.append(tool.to_openai_tool())
|
||||
elif callable(tool):
|
||||
formatted_tools.append(self._generate_tool_definition(tool.__name__))
|
||||
|
||||
# Create async OpenAI client
|
||||
async_client = AsyncOpenAI()
|
||||
|
||||
# Make the API call based on the type of request
|
||||
if tools:
|
||||
response = await async_client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
tools=formatted_tools
|
||||
)
|
||||
return await self._achat_completion(response, tools)
|
||||
elif output_json or output_pydantic:
|
||||
response = await async_client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
# Return the raw response
|
||||
return response.choices[0].message.content
|
||||
else:
|
||||
response = await async_client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=temperature
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
display_error(f"Error in chat completion: {e}")
|
||||
return None
|
||||
|
||||
async def _achat_completion(self, response, tools):
|
||||
"""Async version of _chat_completion method"""
|
||||
try:
|
||||
message = response.choices[0].message
|
||||
if not hasattr(message, 'tool_calls') or not message.tool_calls:
|
||||
return message.content
|
||||
|
||||
results = []
|
||||
for tool_call in message.tool_calls:
|
||||
try:
|
||||
function_name = tool_call.function.name
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
|
||||
# Find the matching tool
|
||||
tool = next((t for t in tools if t.__name__ == function_name), None)
|
||||
if not tool:
|
||||
display_error(f"Tool {function_name} not found")
|
||||
continue
|
||||
|
||||
# Check if the tool is async
|
||||
if asyncio.iscoroutinefunction(tool):
|
||||
result = await tool(**arguments)
|
||||
else:
|
||||
# Run sync function in executor to avoid blocking
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(None, lambda: tool(**arguments))
|
||||
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
display_error(f"Error executing tool {function_name}: {e}")
|
||||
results.append(None)
|
||||
|
||||
# If we have results, format them into a response
|
||||
if results:
|
||||
formatted_results = "\n".join([str(r) for r in results if r is not None])
|
||||
if formatted_results:
|
||||
messages = [
|
||||
{"role": "system", "content": self.system_prompt},
|
||||
{"role": "assistant", "content": "Here are the tool results:"},
|
||||
{"role": "user", "content": formatted_results + "\nPlease process these results and provide a final response."}
|
||||
]
|
||||
try:
|
||||
async_client = AsyncOpenAI()
|
||||
final_response = await async_client.chat.completions.create(
|
||||
model=self.llm,
|
||||
messages=messages,
|
||||
temperature=0.2
|
||||
)
|
||||
return final_response.choices[0].message.content
|
||||
except Exception as e:
|
||||
display_error(f"Error in final chat completion: {e}")
|
||||
return formatted_results
|
||||
return formatted_results
|
||||
return None
|
||||
except Exception as e:
|
||||
display_error(f"Error in _achat_completion: {e}")
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
"""Alias for start() method"""
|
||||
return self.start()
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Agents module for managing multiple AI agents"""
|
||||
from .agents import PraisonAIAgents
|
||||
from .autoagents import AutoAgents
|
||||
|
||||
__all__ = ['PraisonAIAgents', 'AutoAgents']
|
||||
@@ -0,0 +1,730 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, List
|
||||
from pydantic import BaseModel
|
||||
from rich.text import Text
|
||||
from rich.panel import Panel
|
||||
from rich.console import Console
|
||||
from ..main import display_error, TaskOutput, error_logs, client
|
||||
from ..agent.agent import Agent
|
||||
from ..task.task import Task
|
||||
from ..process.process import Process, LoopItems
|
||||
import asyncio
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def encode_file_to_base64(file_path: str) -> str:
|
||||
"""Base64-encode a file."""
|
||||
import base64
|
||||
with open(file_path, "rb") as f:
|
||||
return base64.b64encode(f.read()).decode("utf-8")
|
||||
|
||||
def process_video(video_path: str, seconds_per_frame=2):
|
||||
"""Split video into frames (base64-encoded)."""
|
||||
import cv2
|
||||
import base64
|
||||
base64_frames = []
|
||||
video = cv2.VideoCapture(video_path)
|
||||
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
fps = video.get(cv2.CAP_PROP_FPS)
|
||||
frames_to_skip = int(fps * seconds_per_frame)
|
||||
curr_frame = 0
|
||||
while curr_frame < total_frames:
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, curr_frame)
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
break
|
||||
_, buffer = cv2.imencode(".jpg", frame)
|
||||
base64_frames.append(base64.b64encode(buffer).decode("utf-8"))
|
||||
curr_frame += frames_to_skip
|
||||
video.release()
|
||||
return base64_frames
|
||||
|
||||
class PraisonAIAgents:
|
||||
def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None):
|
||||
if not agents:
|
||||
raise ValueError("At least one agent must be provided")
|
||||
|
||||
self.agents = agents
|
||||
self.tasks = {}
|
||||
if max_retries < 3:
|
||||
max_retries = 3
|
||||
self.completion_checker = completion_checker if completion_checker else self.default_completion_checker
|
||||
self.task_id_counter = 0
|
||||
self.verbose = verbose
|
||||
self.max_retries = max_retries
|
||||
self.process = process
|
||||
|
||||
# Check for manager_llm in environment variable if not provided
|
||||
self.manager_llm = manager_llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
|
||||
# Set logger level based on verbose
|
||||
if verbose >= 5:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# Also set third-party loggers to WARNING
|
||||
logging.getLogger('chromadb').setLevel(logging.WARNING)
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
|
||||
if self.verbose:
|
||||
logger.info(f"Using model {self.manager_llm} for manager")
|
||||
|
||||
# If no tasks provided, generate them from agents
|
||||
if tasks is None:
|
||||
tasks = []
|
||||
for agent in self.agents:
|
||||
task = agent.generate_task()
|
||||
tasks.append(task)
|
||||
logger.info(f"Auto-generated {len(tasks)} tasks from agents")
|
||||
else:
|
||||
# Validate tasks for backward compatibility
|
||||
if not tasks:
|
||||
raise ValueError("If tasks are provided, at least one task must be present")
|
||||
logger.info(f"Using {len(tasks)} provided tasks")
|
||||
|
||||
# Add tasks and set their status
|
||||
for task in tasks:
|
||||
self.add_task(task)
|
||||
task.status = "not started"
|
||||
|
||||
# If tasks were auto-generated from agents or process is sequential, set up sequential flow
|
||||
if len(tasks) > 1 and (process == "sequential" or all(task.next_tasks == [] for task in tasks)):
|
||||
for i in range(len(tasks) - 1):
|
||||
# Set up next task relationship
|
||||
tasks[i].next_tasks = [tasks[i + 1].name]
|
||||
# Set up context for the next task to include the current task
|
||||
if tasks[i + 1].context is None:
|
||||
tasks[i + 1].context = []
|
||||
tasks[i + 1].context.append(tasks[i])
|
||||
logger.info("Set up sequential flow with automatic context passing")
|
||||
|
||||
self._state = {} # Add state storage at PraisonAIAgents level
|
||||
|
||||
# Initialize memory system
|
||||
self.shared_memory = None
|
||||
if memory:
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
|
||||
# Get memory config from parameter or first task
|
||||
mem_cfg = memory_config
|
||||
if not mem_cfg:
|
||||
mem_cfg = next((t.config.get('memory_config') for t in tasks if hasattr(t, 'config') and t.config), None)
|
||||
|
||||
# Set default memory config if none provided
|
||||
if not mem_cfg:
|
||||
mem_cfg = {
|
||||
"provider": "rag",
|
||||
"use_embedding": True,
|
||||
"storage": {
|
||||
"type": "sqlite",
|
||||
"path": "./.praison/memory.db"
|
||||
},
|
||||
"rag_db_path": "./.praison/chroma_db"
|
||||
}
|
||||
|
||||
# Add embedder config if provided
|
||||
if embedder:
|
||||
if isinstance(embedder, dict):
|
||||
mem_cfg = mem_cfg or {}
|
||||
mem_cfg["embedder"] = embedder
|
||||
else:
|
||||
# Handle direct embedder function
|
||||
mem_cfg = mem_cfg or {}
|
||||
mem_cfg["embedder_function"] = embedder
|
||||
|
||||
if mem_cfg:
|
||||
# Pass verbose level to Memory
|
||||
self.shared_memory = Memory(config=mem_cfg, verbose=verbose)
|
||||
if verbose >= 5:
|
||||
logger.info("Initialized shared memory for PraisonAIAgents")
|
||||
|
||||
# Distribute memory to tasks
|
||||
for task in tasks:
|
||||
if not task.memory:
|
||||
task.memory = self.shared_memory
|
||||
if verbose >= 5:
|
||||
logger.info(f"Assigned shared memory to task {task.id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize shared memory: {e}")
|
||||
|
||||
# Update tasks with shared memory
|
||||
if self.shared_memory:
|
||||
for task in tasks:
|
||||
if not task.memory:
|
||||
task.memory = self.shared_memory
|
||||
logger.info(f"Assigned shared memory to task {task.id}")
|
||||
|
||||
def add_task(self, task):
|
||||
task_id = self.task_id_counter
|
||||
task.id = task_id
|
||||
self.tasks[task_id] = task
|
||||
self.task_id_counter += 1
|
||||
return task_id
|
||||
|
||||
def clean_json_output(self, output: str) -> str:
|
||||
cleaned = output.strip()
|
||||
if cleaned.startswith("```json"):
|
||||
cleaned = cleaned[len("```json"):].strip()
|
||||
if cleaned.startswith("```"):
|
||||
cleaned = cleaned[len("```"):].strip()
|
||||
if cleaned.endswith("```"):
|
||||
cleaned = cleaned[:-3].strip()
|
||||
return cleaned
|
||||
|
||||
def default_completion_checker(self, task, agent_output):
|
||||
if task.output_json and task.result and task.result.json_dict:
|
||||
return True
|
||||
if task.output_pydantic and task.result and task.result.pydantic:
|
||||
return True
|
||||
return len(agent_output.strip()) > 0
|
||||
|
||||
async def aexecute_task(self, task_id):
|
||||
"""Async version of execute_task method"""
|
||||
if task_id not in self.tasks:
|
||||
display_error(f"Error: Task with ID {task_id} does not exist")
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
|
||||
# Only import multimodal dependencies if task has images
|
||||
if task.images and task.status == "not started":
|
||||
try:
|
||||
import cv2
|
||||
import base64
|
||||
from moviepy import VideoFileClip
|
||||
except ImportError as e:
|
||||
display_error(f"Error: Missing required dependencies for image/video processing: {e}")
|
||||
display_error("Please install with: pip install opencv-python moviepy")
|
||||
task.status = "failed"
|
||||
return None
|
||||
|
||||
if task.status == "not started":
|
||||
task.status = "in progress"
|
||||
|
||||
executor_agent = task.agent
|
||||
|
||||
# Ensure tools are available from both task and agent
|
||||
tools = task.tools or []
|
||||
if executor_agent and executor_agent.tools:
|
||||
tools.extend(executor_agent.tools)
|
||||
|
||||
task_prompt = f"""
|
||||
You need to do the following task: {task.description}.
|
||||
Expected Output: {task.expected_output}.
|
||||
"""
|
||||
if task.context:
|
||||
context_results = ""
|
||||
for context_task in task.context:
|
||||
if context_task.result:
|
||||
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
|
||||
else:
|
||||
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
|
||||
task_prompt += f"""
|
||||
Here are the results of previous tasks that might be useful:\n
|
||||
{context_results}
|
||||
"""
|
||||
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
|
||||
|
||||
if self.verbose >= 2:
|
||||
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
content = [{"type": "text", "text": text_prompt}]
|
||||
|
||||
for img in images:
|
||||
# If local file path for a valid image
|
||||
if os.path.exists(img):
|
||||
ext = os.path.splitext(img)[1].lower()
|
||||
# If it's a .mp4, convert to frames
|
||||
if ext == ".mp4":
|
||||
frames = process_video(img, seconds_per_frame=1)
|
||||
content.append({"type": "text", "text": "These are frames from the video."})
|
||||
for f in frames:
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
||||
})
|
||||
else:
|
||||
encoded = encode_file_to_base64(img)
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Treat as a remote URL
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": img}
|
||||
})
|
||||
return content
|
||||
|
||||
agent_output = await executor_agent.achat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
tools=tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
else:
|
||||
agent_output = await executor_agent.achat(
|
||||
task_prompt,
|
||||
tools=tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
|
||||
if agent_output:
|
||||
task_output = TaskOutput(
|
||||
description=task.description,
|
||||
summary=task.description[:10],
|
||||
raw=agent_output,
|
||||
agent=executor_agent.name,
|
||||
output_format="RAW"
|
||||
)
|
||||
|
||||
if task.output_json:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
try:
|
||||
parsed = json.loads(cleaned)
|
||||
task_output.json_dict = parsed
|
||||
task_output.output_format = "JSON"
|
||||
except:
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logger.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
|
||||
if task.output_pydantic:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
try:
|
||||
parsed = json.loads(cleaned)
|
||||
pyd_obj = task.output_pydantic(**parsed)
|
||||
task_output.pydantic = pyd_obj
|
||||
task_output.output_format = "Pydantic"
|
||||
except:
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
|
||||
task.result = task_output
|
||||
return task_output
|
||||
else:
|
||||
task.status = "failed"
|
||||
return None
|
||||
|
||||
async def arun_task(self, task_id):
|
||||
"""Async version of run_task method"""
|
||||
if task_id not in self.tasks:
|
||||
display_error(f"Error: Task with ID {task_id} does not exist")
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
if task.status == "completed":
|
||||
logger.info(f"Task with ID {task_id} is already completed")
|
||||
return
|
||||
|
||||
retries = 0
|
||||
while task.status != "completed" and retries < self.max_retries:
|
||||
logger.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
if task.status in ["not started", "in progress"]:
|
||||
task_output = await self.aexecute_task(task_id)
|
||||
if task_output and self.completion_checker(task, task_output.raw):
|
||||
task.status = "completed"
|
||||
# Run execute_callback for memory operations
|
||||
try:
|
||||
await task.execute_callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing memory callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
# Run task callback if exists
|
||||
if task.callback:
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(task.callback):
|
||||
await task.callback(task_output)
|
||||
else:
|
||||
task.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing task callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
self.save_output_to_file(task, task_output)
|
||||
if self.verbose >= 1:
|
||||
logger.info(f"Task {task_id} completed successfully.")
|
||||
else:
|
||||
task.status = "in progress"
|
||||
if self.verbose >= 1:
|
||||
logger.info(f"Task {task_id} not completed, retrying")
|
||||
await asyncio.sleep(1)
|
||||
retries += 1
|
||||
else:
|
||||
if task.status == "failed":
|
||||
logger.info("Task is failed, resetting to in-progress for another try...")
|
||||
task.status = "in progress"
|
||||
else:
|
||||
logger.info("Invalid Task status")
|
||||
break
|
||||
|
||||
if retries == self.max_retries and task.status != "completed":
|
||||
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
|
||||
async def arun_all_tasks(self):
|
||||
"""Async version of run_all_tasks method"""
|
||||
process = Process(
|
||||
tasks=self.tasks,
|
||||
agents=self.agents,
|
||||
manager_llm=self.manager_llm,
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
if self.process == "workflow":
|
||||
# Collect all tasks that should run in parallel
|
||||
parallel_tasks = []
|
||||
async for task_id in process.aworkflow():
|
||||
if self.tasks[task_id].async_execution and self.tasks[task_id].is_start:
|
||||
parallel_tasks.append(task_id)
|
||||
elif parallel_tasks:
|
||||
# Execute collected parallel tasks
|
||||
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
|
||||
parallel_tasks = []
|
||||
# Run the current non-parallel task
|
||||
if self.tasks[task_id].async_execution:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
|
||||
# Execute any remaining parallel tasks
|
||||
if parallel_tasks:
|
||||
await asyncio.gather(*[self.arun_task(t) for t in parallel_tasks])
|
||||
|
||||
elif self.process == "sequential":
|
||||
async for task_id in process.asequential():
|
||||
if self.tasks[task_id].async_execution:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
elif self.process == "hierarchical":
|
||||
async for task_id in process.ahierarchical():
|
||||
if isinstance(task_id, Task):
|
||||
task_id = self.add_task(task_id)
|
||||
if self.tasks[task_id].async_execution:
|
||||
await self.arun_task(task_id)
|
||||
else:
|
||||
self.run_task(task_id)
|
||||
|
||||
async def astart(self):
|
||||
"""Async version of start method"""
|
||||
await self.arun_all_tasks()
|
||||
return {
|
||||
"task_status": self.get_all_tasks_status(),
|
||||
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
|
||||
}
|
||||
|
||||
def save_output_to_file(self, task, task_output):
|
||||
if task.output_file:
|
||||
try:
|
||||
if task.create_directory:
|
||||
os.makedirs(os.path.dirname(task.output_file), exist_ok=True)
|
||||
with open(task.output_file, "w") as f:
|
||||
f.write(str(task_output))
|
||||
if self.verbose >= 1:
|
||||
logger.info(f"Task output saved to {task.output_file}")
|
||||
except Exception as e:
|
||||
display_error(f"Error saving task output to file: {e}")
|
||||
|
||||
def execute_task(self, task_id):
|
||||
"""Synchronous version of execute_task method"""
|
||||
if task_id not in self.tasks:
|
||||
display_error(f"Error: Task with ID {task_id} does not exist")
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
|
||||
logger.info(f"Starting execution of task {task_id}")
|
||||
logger.info(f"Task config: {task.config}")
|
||||
|
||||
# Initialize memory before task execution
|
||||
if not task.memory:
|
||||
task.memory = task.initialize_memory()
|
||||
|
||||
logger.info(f"Task memory status: {'Initialized' if task.memory else 'Not initialized'}")
|
||||
|
||||
# Only import multimodal dependencies if task has images
|
||||
if task.images and task.status == "not started":
|
||||
try:
|
||||
import cv2
|
||||
import base64
|
||||
from moviepy import VideoFileClip
|
||||
except ImportError as e:
|
||||
display_error(f"Error: Missing required dependencies for image/video processing: {e}")
|
||||
display_error("Please install with: pip install opencv-python moviepy")
|
||||
task.status = "failed"
|
||||
return None
|
||||
|
||||
if task.status == "not started":
|
||||
task.status = "in progress"
|
||||
|
||||
executor_agent = task.agent
|
||||
|
||||
task_prompt = f"""
|
||||
You need to do the following task: {task.description}.
|
||||
Expected Output: {task.expected_output}.
|
||||
"""
|
||||
if task.context:
|
||||
context_results = ""
|
||||
for context_task in task.context:
|
||||
if context_task.result:
|
||||
context_results += f"Result of previous task {context_task.name if context_task.name else context_task.description}: {context_task.result.raw}\n"
|
||||
else:
|
||||
context_results += f"Previous task {context_task.name if context_task.name else context_task.description} had no result.\n"
|
||||
task_prompt += f"""
|
||||
Here are the results of previous tasks that might be useful:\n
|
||||
{context_results}
|
||||
"""
|
||||
# Add memory context if available
|
||||
if task.memory:
|
||||
try:
|
||||
memory_context = task.memory.build_context_for_task(task.description)
|
||||
if memory_context:
|
||||
task_prompt += f"\n\nRelevant memory context:\n{memory_context}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting memory context: {e}")
|
||||
|
||||
task_prompt += "Please provide only the final result of your work. Do not add any conversation or extra explanation."
|
||||
|
||||
if self.verbose >= 2:
|
||||
logger.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
|
||||
logger.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
|
||||
|
||||
if task.images:
|
||||
def _get_multimodal_message(text_prompt, images):
|
||||
content = [{"type": "text", "text": text_prompt}]
|
||||
|
||||
for img in images:
|
||||
# If local file path for a valid image
|
||||
if os.path.exists(img):
|
||||
ext = os.path.splitext(img)[1].lower()
|
||||
# If it's a .mp4, convert to frames
|
||||
if ext == ".mp4":
|
||||
frames = process_video(img, seconds_per_frame=1)
|
||||
content.append({"type": "text", "text": "These are frames from the video."})
|
||||
for f in frames:
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpg;base64,{f}"}
|
||||
})
|
||||
else:
|
||||
encoded = encode_file_to_base64(img)
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Treat as a remote URL
|
||||
content.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": img}
|
||||
})
|
||||
return content
|
||||
|
||||
agent_output = executor_agent.chat(
|
||||
_get_multimodal_message(task_prompt, task.images),
|
||||
tools=task.tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
else:
|
||||
agent_output = executor_agent.chat(
|
||||
task_prompt,
|
||||
tools=task.tools,
|
||||
output_json=task.output_json,
|
||||
output_pydantic=task.output_pydantic
|
||||
)
|
||||
|
||||
if agent_output:
|
||||
# Store the response in memory
|
||||
if task.memory:
|
||||
try:
|
||||
task.store_in_memory(
|
||||
content=agent_output,
|
||||
agent_name=executor_agent.name,
|
||||
task_id=task_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store agent output in memory: {e}")
|
||||
|
||||
task_output = TaskOutput(
|
||||
description=task.description,
|
||||
summary=task.description[:10],
|
||||
raw=agent_output,
|
||||
agent=executor_agent.name,
|
||||
output_format="RAW"
|
||||
)
|
||||
|
||||
if task.output_json:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
try:
|
||||
parsed = json.loads(cleaned)
|
||||
task_output.json_dict = parsed
|
||||
task_output.output_format = "JSON"
|
||||
except:
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as JSON")
|
||||
logger.debug(f"Output that failed JSON parsing: {agent_output}")
|
||||
|
||||
if task.output_pydantic:
|
||||
cleaned = self.clean_json_output(agent_output)
|
||||
try:
|
||||
parsed = json.loads(cleaned)
|
||||
pyd_obj = task.output_pydantic(**parsed)
|
||||
task_output.pydantic = pyd_obj
|
||||
task_output.output_format = "Pydantic"
|
||||
except:
|
||||
logger.warning(f"Warning: Could not parse output of task {task_id} as Pydantic Model")
|
||||
logger.debug(f"Output that failed Pydantic parsing: {agent_output}")
|
||||
|
||||
task.result = task_output
|
||||
return task_output
|
||||
else:
|
||||
task.status = "failed"
|
||||
return None
|
||||
|
||||
def run_task(self, task_id):
|
||||
"""Synchronous version of run_task method"""
|
||||
if task_id not in self.tasks:
|
||||
display_error(f"Error: Task with ID {task_id} does not exist")
|
||||
return
|
||||
task = self.tasks[task_id]
|
||||
if task.status == "completed":
|
||||
logger.info(f"Task with ID {task_id} is already completed")
|
||||
return
|
||||
|
||||
retries = 0
|
||||
while task.status != "completed" and retries < self.max_retries:
|
||||
logger.debug(f"Attempt {retries+1} for task {task_id}")
|
||||
if task.status in ["not started", "in progress"]:
|
||||
task_output = self.execute_task(task_id)
|
||||
if task_output and self.completion_checker(task, task_output.raw):
|
||||
task.status = "completed"
|
||||
# Run execute_callback for memory operations
|
||||
try:
|
||||
if asyncio.get_event_loop().is_running():
|
||||
asyncio.create_task(task.execute_callback(task_output))
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(task.execute_callback(task_output))
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing memory callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
# Run task callback if exists
|
||||
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))
|
||||
else:
|
||||
task.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing task callback for task {task_id}: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
self.save_output_to_file(task, task_output)
|
||||
if self.verbose >= 1:
|
||||
logger.info(f"Task {task_id} completed successfully.")
|
||||
else:
|
||||
task.status = "in progress"
|
||||
if self.verbose >= 1:
|
||||
logger.info(f"Task {task_id} not completed, retrying")
|
||||
time.sleep(1)
|
||||
retries += 1
|
||||
else:
|
||||
if task.status == "failed":
|
||||
logger.info("Task is failed, resetting to in-progress for another try...")
|
||||
task.status = "in progress"
|
||||
else:
|
||||
logger.info("Invalid Task status")
|
||||
break
|
||||
|
||||
if retries == self.max_retries and task.status != "completed":
|
||||
logger.info(f"Task {task_id} failed after {self.max_retries} retries.")
|
||||
|
||||
def run_all_tasks(self):
|
||||
"""Synchronous version of run_all_tasks method"""
|
||||
process = Process(
|
||||
tasks=self.tasks,
|
||||
agents=self.agents,
|
||||
manager_llm=self.manager_llm,
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
if self.process == "workflow":
|
||||
for task_id in process.workflow():
|
||||
self.run_task(task_id)
|
||||
elif self.process == "sequential":
|
||||
for task_id in process.sequential():
|
||||
self.run_task(task_id)
|
||||
elif self.process == "hierarchical":
|
||||
for task_id in process.hierarchical():
|
||||
if isinstance(task_id, Task):
|
||||
task_id = self.add_task(task_id)
|
||||
self.run_task(task_id)
|
||||
|
||||
def get_task_status(self, task_id):
|
||||
if task_id in self.tasks:
|
||||
return self.tasks[task_id].status
|
||||
return None
|
||||
|
||||
def get_all_tasks_status(self):
|
||||
return {task_id: self.tasks[task_id].status for task_id in self.tasks}
|
||||
|
||||
def get_task_result(self, task_id):
|
||||
if task_id in self.tasks:
|
||||
return self.tasks[task_id].result
|
||||
return None
|
||||
|
||||
def get_task_details(self, task_id):
|
||||
if task_id in self.tasks:
|
||||
return str(self.tasks[task_id])
|
||||
return None
|
||||
|
||||
def get_agent_details(self, agent_name):
|
||||
agent = [task.agent for task in self.tasks.values() if task.agent and task.agent.name == agent_name]
|
||||
if agent:
|
||||
return str(agent[0])
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
self.run_all_tasks()
|
||||
return {
|
||||
"task_status": self.get_all_tasks_status(),
|
||||
"task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
|
||||
}
|
||||
|
||||
def set_state(self, key: str, value: Any) -> None:
|
||||
"""Set a state value"""
|
||||
self._state[key] = value
|
||||
|
||||
def get_state(self, key: str, default: Any = None) -> Any:
|
||||
"""Get a state value"""
|
||||
return self._state.get(key, default)
|
||||
|
||||
def update_state(self, updates: Dict) -> None:
|
||||
"""Update multiple state values"""
|
||||
self._state.update(updates)
|
||||
|
||||
def clear_state(self) -> None:
|
||||
"""Clear all state values"""
|
||||
self._state.clear()
|
||||
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
AutoAgents - A class for automatically creating and managing AI agents and tasks.
|
||||
|
||||
This class provides a simplified interface for creating and running AI agents with tasks.
|
||||
It automatically handles agent creation, task setup, and execution flow.
|
||||
"""
|
||||
|
||||
from .agents import PraisonAIAgents
|
||||
from ..agent.agent import Agent
|
||||
from ..task.task import Task
|
||||
from typing import List, Any, Optional, Dict, Union
|
||||
import logging
|
||||
import os
|
||||
from pydantic import BaseModel, Field
|
||||
from ..main import display_instruction, display_tool_call, display_interaction, client
|
||||
|
||||
# Define Pydantic models for structured output
|
||||
class TaskConfig(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
expected_output: str
|
||||
tools: List[str]
|
||||
|
||||
class AgentConfig(BaseModel):
|
||||
name: str
|
||||
role: str
|
||||
goal: str
|
||||
backstory: str
|
||||
tools: List[str]
|
||||
tasks: List[TaskConfig]
|
||||
|
||||
class AutoAgentsConfig(BaseModel):
|
||||
main_instruction: str
|
||||
process_type: str
|
||||
agents: List[AgentConfig]
|
||||
|
||||
class AutoAgents(PraisonAIAgents):
|
||||
def __init__(
|
||||
self,
|
||||
instructions: str,
|
||||
tools: Optional[List[Any]] = None,
|
||||
verbose: bool = False,
|
||||
process: str = "sequential",
|
||||
manager_llm: Optional[str] = None,
|
||||
max_retries: int = 5,
|
||||
completion_checker: Optional[Any] = None,
|
||||
allow_code_execution: bool = False,
|
||||
memory: bool = True,
|
||||
markdown: bool = True,
|
||||
self_reflect: bool = False,
|
||||
max_reflect: int = 3,
|
||||
min_reflect: int = 1,
|
||||
llm: Optional[str] = None,
|
||||
function_calling_llm: Optional[str] = None,
|
||||
respect_context_window: bool = True,
|
||||
code_execution_mode: str = "safe",
|
||||
embedder_config: Optional[Dict[str, Any]] = None,
|
||||
knowledge_sources: Optional[List[Any]] = None,
|
||||
use_system_prompt: bool = True,
|
||||
cache: bool = True,
|
||||
allow_delegation: bool = False,
|
||||
step_callback: Optional[Any] = None,
|
||||
system_template: Optional[str] = None,
|
||||
prompt_template: Optional[str] = None,
|
||||
response_template: Optional[str] = None,
|
||||
max_rpm: Optional[int] = None,
|
||||
max_execution_time: Optional[int] = None,
|
||||
max_iter: int = 20,
|
||||
reflect_llm: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
max_agents: int = 3 # New parameter for maximum number of agents
|
||||
):
|
||||
"""Initialize AutoAgents with configuration for automatic agent and task creation."""
|
||||
if max_agents < 1:
|
||||
raise ValueError("max_agents must be at least 1")
|
||||
if max_agents > 10:
|
||||
raise ValueError("max_agents cannot exceed 10")
|
||||
|
||||
# Store all configuration parameters first
|
||||
self.instructions = instructions
|
||||
self.tools = tools or []
|
||||
self.verbose = verbose # Store verbose parameter
|
||||
self.max_agents = max_agents # Store max_agents parameter
|
||||
self.allow_code_execution = allow_code_execution
|
||||
self.memory = memory
|
||||
self.markdown = markdown
|
||||
self.self_reflect = self_reflect
|
||||
self.max_reflect = max_reflect
|
||||
self.min_reflect = min_reflect
|
||||
self.llm = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
||||
self.function_calling_llm = function_calling_llm
|
||||
self.respect_context_window = respect_context_window
|
||||
self.code_execution_mode = code_execution_mode
|
||||
self.embedder_config = embedder_config
|
||||
self.knowledge_sources = knowledge_sources
|
||||
self.use_system_prompt = use_system_prompt
|
||||
self.cache = cache
|
||||
self.allow_delegation = allow_delegation
|
||||
self.step_callback = step_callback
|
||||
self.system_template = system_template
|
||||
self.prompt_template = prompt_template
|
||||
self.response_template = response_template
|
||||
self.max_rpm = max_rpm
|
||||
self.max_execution_time = max_execution_time
|
||||
self.max_iter = max_iter
|
||||
self.reflect_llm = reflect_llm
|
||||
|
||||
# Display initial instruction
|
||||
if self.verbose:
|
||||
display_instruction(f"🎯 Main Task: {self.instructions}")
|
||||
display_instruction(f"📊 Maximum Agents: {self.max_agents}")
|
||||
if self.tools:
|
||||
tool_names = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
display_tool_call(f"🛠️ Available Tools: {', '.join(tool_names)}")
|
||||
|
||||
# Generate agents and tasks configuration
|
||||
config = self._generate_config()
|
||||
agents, tasks = self._create_agents_and_tasks(config)
|
||||
|
||||
# Display agents and their tasks
|
||||
if self.verbose:
|
||||
self._display_agents_and_tasks(agents, tasks)
|
||||
|
||||
# Initialize parent class with generated agents and tasks
|
||||
super().__init__(
|
||||
agents=agents,
|
||||
tasks=tasks,
|
||||
verbose=verbose,
|
||||
completion_checker=completion_checker,
|
||||
max_retries=max_retries,
|
||||
process=process,
|
||||
manager_llm=manager_llm
|
||||
)
|
||||
|
||||
def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]):
|
||||
"""Display the created agents and their assigned tasks"""
|
||||
display_instruction("\n🤖 Generated Agents and Tasks:")
|
||||
|
||||
# Create a mapping of agents to their tasks
|
||||
agent_tasks = {}
|
||||
for task in tasks:
|
||||
if task.agent not in agent_tasks:
|
||||
agent_tasks[task.agent] = []
|
||||
agent_tasks[task.agent].append(task)
|
||||
|
||||
# Display each agent and their tasks
|
||||
for agent in agents:
|
||||
agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in agent.tools]
|
||||
display_interaction(
|
||||
f"\n👤 Agent: {agent.name}",
|
||||
f"""Role: {agent.role}
|
||||
Goal: {agent.goal}
|
||||
Tools: {', '.join(agent_tools)}"""
|
||||
)
|
||||
|
||||
# Display tasks for this agent
|
||||
if agent in agent_tasks:
|
||||
for i, task in enumerate(agent_tasks[agent], 1):
|
||||
task_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in task.tools]
|
||||
display_instruction(
|
||||
f""" 📋 Task {i}: {task.name}
|
||||
Description: {task.description}
|
||||
Expected Output: {task.expected_output}
|
||||
Tools: {', '.join(task_tools)}"""
|
||||
)
|
||||
|
||||
def _get_available_tools(self) -> List[str]:
|
||||
"""Get list of available tools"""
|
||||
if not self.tools:
|
||||
return []
|
||||
return [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools]
|
||||
|
||||
def _get_tool_by_name(self, tool_name: str) -> Optional[Any]:
|
||||
"""Get tool object by its name"""
|
||||
for tool in self.tools:
|
||||
if (hasattr(tool, '__name__') and tool.__name__ == tool_name) or str(tool) == tool_name:
|
||||
return tool
|
||||
return None
|
||||
|
||||
def _assign_tools_to_agent(self, agent_config: AgentConfig) -> List[Any]:
|
||||
"""
|
||||
Assign appropriate tools to an agent based on its role and tasks.
|
||||
|
||||
Args:
|
||||
agent_config: The agent configuration containing role and required tools
|
||||
|
||||
Returns:
|
||||
List of tool objects assigned to this agent
|
||||
"""
|
||||
assigned_tools = []
|
||||
tool_names = set(agent_config.tools)
|
||||
|
||||
# Also look at task requirements
|
||||
for task in agent_config.tasks:
|
||||
tool_names.update(task.tools)
|
||||
|
||||
# Assign tools that match the requirements
|
||||
for tool_name in tool_names:
|
||||
tool = self._get_tool_by_name(tool_name)
|
||||
if tool:
|
||||
assigned_tools.append(tool)
|
||||
|
||||
# If no specific tools matched but we have tools available,
|
||||
# assign all tools to ensure functionality
|
||||
if not assigned_tools and self.tools:
|
||||
assigned_tools = self.tools
|
||||
|
||||
return assigned_tools
|
||||
|
||||
def _generate_config(self) -> AutoAgentsConfig:
|
||||
"""Generate the configuration for agents and tasks"""
|
||||
prompt = f"""
|
||||
Generate a configuration for AI agents to accomplish this task: "{self.instructions}"
|
||||
|
||||
The configuration should include:
|
||||
1. A main instruction that clearly states the overall goal
|
||||
2. A process type (sequential, workflow, or hierarchical)
|
||||
3. A list of maximum {self.max_agents} agents (no more, no less), each with:
|
||||
- Name, role, goal, and backstory
|
||||
- List of required tools from: {self._get_available_tools()}
|
||||
- Only add tools that are needed for the agent to perform its task
|
||||
- Only one task per agent. Add more than one task if absolutely necessary.
|
||||
- List of specific tasks they need to perform
|
||||
- Whether they should self-reflect or allow delegation
|
||||
|
||||
Requirements:
|
||||
1. Each agent should have clear, focused responsibilities
|
||||
2. Tasks should be broken down into manageable steps
|
||||
3. Tool selection should be appropriate for each task
|
||||
4. The process type should match the task requirements
|
||||
5. Generate maximum {self.max_agents} agents to handle this task efficiently
|
||||
|
||||
Return the configuration in a structured JSON format matching the AutoAgentsConfig schema.
|
||||
"""
|
||||
|
||||
try:
|
||||
response = client.beta.chat.completions.parse(
|
||||
model=self.llm,
|
||||
response_format=AutoAgentsConfig,
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a helpful assistant designed to generate AI agent configurations."},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
)
|
||||
|
||||
# Ensure we have exactly max_agents number of agents
|
||||
if len(response.choices[0].message.parsed.agents) > self.max_agents:
|
||||
response.choices[0].message.parsed.agents = response.choices[0].message.parsed.agents[:self.max_agents]
|
||||
elif len(response.choices[0].message.parsed.agents) < self.max_agents:
|
||||
logging.warning(f"Generated {len(response.choices[0].message.parsed.agents)} agents, expected {self.max_agents}")
|
||||
|
||||
return response.choices[0].message.parsed
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating configuration: {e}")
|
||||
raise
|
||||
|
||||
def _create_agents_and_tasks(self, config: AutoAgentsConfig) -> tuple[List[Agent], List[Task]]:
|
||||
"""Create agents and tasks from configuration"""
|
||||
agents = []
|
||||
tasks = []
|
||||
|
||||
for agent_config in config.agents:
|
||||
# Get appropriate tools for this agent
|
||||
agent_tools = self._assign_tools_to_agent(agent_config)
|
||||
|
||||
# Create the agent with all parameters
|
||||
agent = Agent(
|
||||
name=agent_config.name,
|
||||
role=agent_config.role,
|
||||
goal=agent_config.goal,
|
||||
backstory=agent_config.backstory,
|
||||
tools=agent_tools, # Use assigned tools
|
||||
verbose=self.verbose >= 1,
|
||||
allow_code_execution=self.allow_code_execution,
|
||||
memory=self.memory,
|
||||
markdown=self.markdown,
|
||||
self_reflect=self.self_reflect,
|
||||
max_reflect=self.max_reflect,
|
||||
min_reflect=self.min_reflect,
|
||||
llm=self.llm,
|
||||
function_calling_llm=self.function_calling_llm,
|
||||
respect_context_window=self.respect_context_window,
|
||||
code_execution_mode=self.code_execution_mode,
|
||||
embedder_config=self.embedder_config,
|
||||
knowledge_sources=self.knowledge_sources,
|
||||
use_system_prompt=self.use_system_prompt,
|
||||
cache=self.cache,
|
||||
allow_delegation=self.allow_delegation,
|
||||
step_callback=self.step_callback,
|
||||
system_template=self.system_template,
|
||||
prompt_template=self.prompt_template,
|
||||
response_template=self.response_template,
|
||||
max_rpm=self.max_rpm,
|
||||
max_execution_time=self.max_execution_time,
|
||||
max_iter=self.max_iter,
|
||||
reflect_llm=self.reflect_llm
|
||||
)
|
||||
agents.append(agent)
|
||||
|
||||
# Create tasks for this agent
|
||||
for task_config in agent_config.tasks:
|
||||
# Get task-specific tools
|
||||
task_tools = [self._get_tool_by_name(t) for t in task_config.tools]
|
||||
task_tools = [t for t in task_tools if t] # Remove None values
|
||||
|
||||
# If no specific tools matched, use agent's tools
|
||||
if not task_tools:
|
||||
task_tools = agent_tools
|
||||
|
||||
task = Task(
|
||||
name=task_config.name,
|
||||
description=task_config.description,
|
||||
expected_output=task_config.expected_output,
|
||||
agent=agent,
|
||||
tools=task_tools # Use task-specific tools
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
return agents, tasks
|
||||
|
||||
async def astart(self):
|
||||
"""
|
||||
Async version of start() method.
|
||||
Creates tasks based on the instructions, then starts execution.
|
||||
Returns the task status and results dictionary.
|
||||
"""
|
||||
return await super().astart()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Creates tasks based on the instructions, then starts execution.
|
||||
Returns the task status and results dictionary.
|
||||
"""
|
||||
return super().start()
|
||||
@@ -0,0 +1,373 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
from typing import List, Optional, Dict, Any, Union, Literal, Type
|
||||
from openai import OpenAI
|
||||
from pydantic import BaseModel
|
||||
from rich import print
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.markdown import Markdown
|
||||
from rich.logging import RichHandler
|
||||
from rich.live import Live
|
||||
import asyncio
|
||||
|
||||
LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, LOGLEVEL, logging.INFO),
|
||||
format="%(asctime)s %(filename)s:%(lineno)d %(levelname)s %(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True)]
|
||||
)
|
||||
|
||||
# Global list to store error logs
|
||||
error_logs = []
|
||||
|
||||
# Separate registries for sync and async callbacks
|
||||
sync_display_callbacks = {}
|
||||
async_display_callbacks = {}
|
||||
|
||||
# At the top of the file, add display_callbacks to __all__
|
||||
__all__ = [
|
||||
'error_logs',
|
||||
'register_display_callback',
|
||||
'sync_display_callbacks',
|
||||
'async_display_callbacks',
|
||||
# ... other exports
|
||||
]
|
||||
|
||||
def register_display_callback(display_type: str, callback_fn, is_async: bool = False):
|
||||
"""Register a synchronous or asynchronous callback function for a specific display type.
|
||||
|
||||
Args:
|
||||
display_type (str): Type of display event ('interaction', 'self_reflection', etc.)
|
||||
callback_fn: The callback function to register
|
||||
is_async (bool): Whether the callback is asynchronous
|
||||
"""
|
||||
if is_async:
|
||||
async_display_callbacks[display_type] = callback_fn
|
||||
else:
|
||||
sync_display_callbacks[display_type] = callback_fn
|
||||
|
||||
async def execute_callback(display_type: str, **kwargs):
|
||||
"""Execute both sync and async callbacks for a given display type.
|
||||
|
||||
Args:
|
||||
display_type (str): Type of display event
|
||||
**kwargs: Arguments to pass to the callback functions
|
||||
"""
|
||||
# Execute synchronous callback if registered
|
||||
if display_type in sync_display_callbacks:
|
||||
callback = sync_display_callbacks[display_type]
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, lambda: callback(**kwargs))
|
||||
|
||||
# Execute asynchronous callback if registered
|
||||
if display_type in async_display_callbacks:
|
||||
callback = async_display_callbacks[display_type]
|
||||
await callback(**kwargs)
|
||||
|
||||
def _clean_display_content(content: str, max_length: int = 20000) -> str:
|
||||
"""Helper function to clean and truncate content for display."""
|
||||
if not content or not str(content).strip():
|
||||
return ""
|
||||
|
||||
content = str(content)
|
||||
# Handle base64 content
|
||||
if "base64" in content:
|
||||
content_parts = []
|
||||
for line in content.split('\n'):
|
||||
if "base64" not in line:
|
||||
content_parts.append(line)
|
||||
content = '\n'.join(content_parts)
|
||||
|
||||
# Truncate if too long
|
||||
if len(content) > max_length:
|
||||
content = content[:max_length] + "..."
|
||||
|
||||
return content.strip()
|
||||
|
||||
def display_interaction(message, response, markdown=True, generation_time=None, console=None):
|
||||
"""Synchronous version of display_interaction."""
|
||||
if console is None:
|
||||
console = Console()
|
||||
|
||||
if isinstance(message, list):
|
||||
text_content = next((item["text"] for item in message if item["type"] == "text"), "")
|
||||
message = text_content
|
||||
|
||||
message = _clean_display_content(str(message))
|
||||
response = _clean_display_content(str(response))
|
||||
|
||||
# Execute synchronous callback if registered
|
||||
if 'interaction' in sync_display_callbacks:
|
||||
sync_display_callbacks['interaction'](
|
||||
message=message,
|
||||
response=response,
|
||||
markdown=markdown,
|
||||
generation_time=generation_time
|
||||
)
|
||||
|
||||
# Rest of the display logic...
|
||||
if generation_time:
|
||||
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
|
||||
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
|
||||
else:
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
|
||||
|
||||
def display_self_reflection(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
# Execute callback if registered
|
||||
if 'self_reflection' in sync_display_callbacks:
|
||||
sync_display_callbacks['self_reflection'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
|
||||
|
||||
def display_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
# Execute callback if registered
|
||||
if 'instruction' in sync_display_callbacks:
|
||||
sync_display_callbacks['instruction'](message=message)
|
||||
|
||||
# Display agent info if available
|
||||
if agent_name:
|
||||
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
|
||||
if agent_role:
|
||||
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
|
||||
if agent_tools:
|
||||
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
|
||||
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
|
||||
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
|
||||
|
||||
# Only print if log level is DEBUG
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
|
||||
def display_tool_call(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
# Execute callback if registered
|
||||
if 'tool_call' in sync_display_callbacks:
|
||||
sync_display_callbacks['tool_call'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
|
||||
def display_error(message: str, console=None):
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
# Execute callback if registered
|
||||
if 'error' in sync_display_callbacks:
|
||||
sync_display_callbacks['error'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold red"), title="Error", border_style="red"))
|
||||
error_logs.append(message)
|
||||
|
||||
def display_generating(content: str = "", start_time: Optional[float] = None):
|
||||
if not content or not str(content).strip():
|
||||
return Panel("", title="", border_style="green")
|
||||
|
||||
elapsed_str = ""
|
||||
if start_time is not None:
|
||||
elapsed = time.time() - start_time
|
||||
elapsed_str = f" {elapsed:.1f}s"
|
||||
|
||||
content = _clean_display_content(str(content))
|
||||
|
||||
# Execute callback if registered
|
||||
if 'generating' in sync_display_callbacks:
|
||||
sync_display_callbacks['generating'](
|
||||
content=content,
|
||||
elapsed_time=elapsed_str.strip() if elapsed_str else None
|
||||
)
|
||||
|
||||
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style="green")
|
||||
|
||||
# Async versions with 'a' prefix
|
||||
async def adisplay_interaction(message, response, markdown=True, generation_time=None, console=None):
|
||||
"""Async version of display_interaction."""
|
||||
if console is None:
|
||||
console = Console()
|
||||
|
||||
if isinstance(message, list):
|
||||
text_content = next((item["text"] for item in message if item["type"] == "text"), "")
|
||||
message = text_content
|
||||
|
||||
message = _clean_display_content(str(message))
|
||||
response = _clean_display_content(str(response))
|
||||
|
||||
# Execute callbacks
|
||||
await execute_callback(
|
||||
'interaction',
|
||||
message=message,
|
||||
response=response,
|
||||
markdown=markdown,
|
||||
generation_time=generation_time
|
||||
)
|
||||
|
||||
# Rest of the display logic...
|
||||
if generation_time:
|
||||
console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
|
||||
|
||||
if markdown:
|
||||
console.print(Panel.fit(Markdown(message), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
|
||||
else:
|
||||
console.print(Panel.fit(Text(message, style="bold green"), title="Task", border_style="cyan"))
|
||||
console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
|
||||
|
||||
async def adisplay_self_reflection(message: str, console=None):
|
||||
"""Async version of display_self_reflection."""
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
if 'self_reflection' in async_display_callbacks:
|
||||
await async_display_callbacks['self_reflection'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
|
||||
|
||||
async def adisplay_instruction(message: str, console=None, agent_name: str = None, agent_role: str = None, agent_tools: List[str] = None):
|
||||
"""Async version of display_instruction."""
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
if 'instruction' in async_display_callbacks:
|
||||
await async_display_callbacks['instruction'](message=message)
|
||||
|
||||
# Display agent info if available
|
||||
if agent_name:
|
||||
agent_info = f"[bold #FF9B9B]👤 Agent:[/] [#FFE5E5]{agent_name}[/]"
|
||||
if agent_role:
|
||||
agent_info += f"\n[bold #B4B4B3]Role:[/] [#FFE5E5]{agent_role}[/]"
|
||||
if agent_tools:
|
||||
tools_str = ", ".join(f"[italic #B4D4FF]{tool}[/]" for tool in agent_tools)
|
||||
agent_info += f"\n[bold #86A789]Tools:[/] {tools_str}"
|
||||
console.print(Panel(agent_info, border_style="#D2E3C8", title="[bold]Agent Info[/]", title_align="left", padding=(1, 2)))
|
||||
|
||||
# Only print if log level is DEBUG
|
||||
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
|
||||
console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
|
||||
|
||||
async def adisplay_tool_call(message: str, console=None):
|
||||
"""Async version of display_tool_call."""
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
if 'tool_call' in async_display_callbacks:
|
||||
await async_display_callbacks['tool_call'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
|
||||
|
||||
async def adisplay_error(message: str, console=None):
|
||||
"""Async version of display_error."""
|
||||
if not message or not message.strip():
|
||||
return
|
||||
if console is None:
|
||||
console = Console()
|
||||
message = _clean_display_content(str(message))
|
||||
|
||||
if 'error' in async_display_callbacks:
|
||||
await async_display_callbacks['error'](message=message)
|
||||
|
||||
console.print(Panel.fit(Text(message, style="bold red"), title="Error", border_style="red"))
|
||||
error_logs.append(message)
|
||||
|
||||
async def adisplay_generating(content: str = "", start_time: Optional[float] = None):
|
||||
"""Async version of display_generating."""
|
||||
if not content or not str(content).strip():
|
||||
return Panel("", title="", border_style="green")
|
||||
|
||||
elapsed_str = ""
|
||||
if start_time is not None:
|
||||
elapsed = time.time() - start_time
|
||||
elapsed_str = f" {elapsed:.1f}s"
|
||||
|
||||
content = _clean_display_content(str(content))
|
||||
|
||||
if 'generating' in async_display_callbacks:
|
||||
await async_display_callbacks['generating'](
|
||||
content=content,
|
||||
elapsed_time=elapsed_str.strip() if elapsed_str else None
|
||||
)
|
||||
|
||||
return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style="green")
|
||||
|
||||
def clean_triple_backticks(text: str) -> str:
|
||||
"""Remove triple backticks and surrounding json fences from a string."""
|
||||
cleaned = text.strip()
|
||||
if cleaned.startswith("```json"):
|
||||
cleaned = cleaned[len("```json"):].strip()
|
||||
if cleaned.startswith("```"):
|
||||
cleaned = cleaned[len("```"):].strip()
|
||||
if cleaned.endswith("```"):
|
||||
cleaned = cleaned[:-3].strip()
|
||||
return cleaned
|
||||
|
||||
class ReflectionOutput(BaseModel):
|
||||
reflection: str
|
||||
satisfactory: Literal["yes", "no"]
|
||||
|
||||
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
||||
|
||||
class TaskOutput(BaseModel):
|
||||
description: str
|
||||
summary: Optional[str] = None
|
||||
raw: str
|
||||
pydantic: Optional[BaseModel] = None
|
||||
json_dict: Optional[Dict[str, Any]] = None
|
||||
agent: str
|
||||
output_format: Literal["RAW", "JSON", "Pydantic"] = "RAW"
|
||||
|
||||
def json(self) -> Optional[str]:
|
||||
if self.output_format == "JSON" and self.json_dict:
|
||||
return json.dumps(self.json_dict)
|
||||
return None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
output_dict = {}
|
||||
if self.json_dict:
|
||||
output_dict.update(self.json_dict)
|
||||
if self.pydantic:
|
||||
output_dict.update(self.pydantic.model_dump())
|
||||
return output_dict
|
||||
|
||||
def __str__(self):
|
||||
if self.pydantic:
|
||||
return str(self.pydantic)
|
||||
elif self.json_dict:
|
||||
return json.dumps(self.json_dict)
|
||||
else:
|
||||
return self.raw
|
||||
@@ -0,0 +1,931 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import json
|
||||
import time
|
||||
import shutil
|
||||
from typing import Any, Dict, List, Optional, Union, Literal
|
||||
import logging
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import chromadb
|
||||
from chromadb.config import Settings as ChromaSettings
|
||||
CHROMADB_AVAILABLE = True
|
||||
except ImportError:
|
||||
CHROMADB_AVAILABLE = False
|
||||
logger.warning("To use memory features, please run: pip install \"praisonaiagents[memory]\"")
|
||||
|
||||
try:
|
||||
import mem0
|
||||
MEM0_AVAILABLE = True
|
||||
except ImportError:
|
||||
MEM0_AVAILABLE = False
|
||||
|
||||
try:
|
||||
import openai
|
||||
OPENAI_AVAILABLE = True
|
||||
except ImportError:
|
||||
OPENAI_AVAILABLE = False
|
||||
|
||||
|
||||
|
||||
|
||||
class Memory:
|
||||
"""
|
||||
A single-file memory manager covering:
|
||||
- Short-term memory (STM) for ephemeral context
|
||||
- Long-term memory (LTM) for persistent knowledge
|
||||
- Entity memory (structured data about named entities)
|
||||
- User memory (preferences/history for each user)
|
||||
- Quality score logic for deciding which data to store in LTM
|
||||
- Context building from multiple memory sources
|
||||
|
||||
Config example:
|
||||
{
|
||||
"provider": "rag" or "mem0" or "none",
|
||||
"use_embedding": True,
|
||||
"short_db": "short_term.db",
|
||||
"long_db": "long_term.db",
|
||||
"rag_db_path": "rag_db", # optional path for local embedding store
|
||||
"config": {
|
||||
"api_key": "...", # if mem0 usage
|
||||
"org_id": "...",
|
||||
"project_id": "...",
|
||||
...
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], verbose: int = 0):
|
||||
self.cfg = config or {}
|
||||
self.verbose = verbose
|
||||
|
||||
# Set logger level based on verbose
|
||||
if verbose >= 5:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# Also set ChromaDB and OpenAI client loggers to WARNING
|
||||
logging.getLogger('chromadb').setLevel(logging.WARNING)
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
logging.getLogger('chromadb.segment.impl.vector.local_persistent_hnsw').setLevel(logging.ERROR)
|
||||
|
||||
self.provider = self.cfg.get("provider", "rag")
|
||||
self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
|
||||
self.use_rag = (self.provider.lower() == "rag") and CHROMADB_AVAILABLE and self.cfg.get("use_embedding", False)
|
||||
|
||||
# Create .praison directory if it doesn't exist
|
||||
os.makedirs(".praison", exist_ok=True)
|
||||
|
||||
# Short-term DB
|
||||
self.short_db = self.cfg.get("short_db", ".praison/short_term.db")
|
||||
self._init_stm()
|
||||
|
||||
# Long-term DB
|
||||
self.long_db = self.cfg.get("long_db", ".praison/long_term.db")
|
||||
self._init_ltm()
|
||||
|
||||
# Conditionally init Mem0 or local RAG
|
||||
if self.use_mem0:
|
||||
self._init_mem0()
|
||||
elif self.use_rag:
|
||||
self._init_chroma()
|
||||
|
||||
def _log_verbose(self, msg: str, level: int = logging.INFO):
|
||||
"""Only log if verbose >= 5"""
|
||||
if self.verbose >= 5:
|
||||
logger.log(level, msg)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Initialization
|
||||
# -------------------------------------------------------------------------
|
||||
def _init_stm(self):
|
||||
"""Creates or verifies short-term memory table."""
|
||||
os.makedirs(os.path.dirname(self.short_db) or ".", exist_ok=True)
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
c = conn.cursor()
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS short_mem (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT,
|
||||
meta TEXT,
|
||||
created_at REAL
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def _init_ltm(self):
|
||||
"""Creates or verifies long-term memory table."""
|
||||
os.makedirs(os.path.dirname(self.long_db) or ".", exist_ok=True)
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
c = conn.cursor()
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS long_mem (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT,
|
||||
meta TEXT,
|
||||
created_at REAL
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def _init_mem0(self):
|
||||
"""Initialize Mem0 client for agent or user memory."""
|
||||
from mem0 import MemoryClient
|
||||
mem_cfg = self.cfg.get("config", {})
|
||||
api_key = mem_cfg.get("api_key", os.getenv("MEM0_API_KEY"))
|
||||
org_id = mem_cfg.get("org_id")
|
||||
proj_id = mem_cfg.get("project_id")
|
||||
if org_id and proj_id:
|
||||
self.mem0_client = MemoryClient(api_key=api_key, org_id=org_id, project_id=proj_id)
|
||||
else:
|
||||
self.mem0_client = MemoryClient(api_key=api_key)
|
||||
|
||||
def _init_chroma(self):
|
||||
"""Initialize a local Chroma client for embedding-based search."""
|
||||
try:
|
||||
# Create directory if it doesn't exist
|
||||
rag_path = self.cfg.get("rag_db_path", "chroma_db")
|
||||
os.makedirs(rag_path, exist_ok=True)
|
||||
|
||||
# Initialize ChromaDB with persistent storage
|
||||
self.chroma_client = chromadb.PersistentClient(
|
||||
path=rag_path,
|
||||
settings=ChromaSettings(
|
||||
anonymized_telemetry=False,
|
||||
allow_reset=True
|
||||
)
|
||||
)
|
||||
|
||||
collection_name = "memory_store"
|
||||
try:
|
||||
self.chroma_col = self.chroma_client.get_collection(name=collection_name)
|
||||
self._log_verbose("Using existing ChromaDB collection")
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Collection '{collection_name}' not found. Creating new collection. Error: {e}")
|
||||
self.chroma_col = self.chroma_client.create_collection(
|
||||
name=collection_name,
|
||||
metadata={"hnsw:space": "cosine"}
|
||||
)
|
||||
self._log_verbose("Created new ChromaDB collection")
|
||||
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Failed to initialize ChromaDB: {e}", logging.ERROR)
|
||||
self.use_rag = False
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Basic Quality Score Computation
|
||||
# -------------------------------------------------------------------------
|
||||
def compute_quality_score(
|
||||
self,
|
||||
completeness: float,
|
||||
relevance: float,
|
||||
clarity: float,
|
||||
accuracy: float,
|
||||
weights: Dict[str, float] = None
|
||||
) -> float:
|
||||
"""
|
||||
Combine multiple sub-metrics into one final score, as an example.
|
||||
|
||||
Args:
|
||||
completeness (float): 0-1
|
||||
relevance (float): 0-1
|
||||
clarity (float): 0-1
|
||||
accuracy (float): 0-1
|
||||
weights (Dict[str, float]): optional weighting like {"completeness": 0.25, "relevance": 0.3, ...}
|
||||
|
||||
Returns:
|
||||
float: Weighted average 0-1
|
||||
"""
|
||||
if not weights:
|
||||
weights = {
|
||||
"completeness": 0.25,
|
||||
"relevance": 0.25,
|
||||
"clarity": 0.25,
|
||||
"accuracy": 0.25
|
||||
}
|
||||
total = (completeness * weights["completeness"]
|
||||
+ relevance * weights["relevance"]
|
||||
+ clarity * weights["clarity"]
|
||||
+ accuracy * weights["accuracy"]
|
||||
)
|
||||
return round(total, 3) # e.g. round to 3 decimal places
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Short-Term Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_short_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Dict[str, Any] = None,
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
):
|
||||
"""Store in short-term memory with optional quality metrics"""
|
||||
logger.info(f"Storing in short-term memory: {text[:100]}...")
|
||||
logger.info(f"Metadata: {metadata}")
|
||||
|
||||
metadata = self._process_quality_metrics(
|
||||
metadata, completeness, relevance, clarity,
|
||||
accuracy, weights, evaluator_quality
|
||||
)
|
||||
logger.info(f"Processed metadata: {metadata}")
|
||||
|
||||
# Existing store logic
|
||||
try:
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
ident = str(time.time_ns())
|
||||
conn.execute(
|
||||
"INSERT INTO short_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
|
||||
(ident, text, json.dumps(metadata), time.time())
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Successfully stored in short-term memory with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in short-term memory: {e}")
|
||||
raise
|
||||
|
||||
def search_short_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
min_quality: float = 0.0,
|
||||
relevance_cutoff: float = 0.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search short-term memory with optional quality filter"""
|
||||
self._log_verbose(f"Searching short memory for: {query}")
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
results = self.mem0_client.search(query=query, limit=limit)
|
||||
filtered = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
|
||||
return filtered
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
|
||||
response = client.embeddings.create(
|
||||
input=query,
|
||||
model="text-embedding-3-small"
|
||||
)
|
||||
query_embedding = response.data[0].embedding
|
||||
|
||||
resp = self.chroma_col.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=limit
|
||||
)
|
||||
|
||||
results = []
|
||||
if resp["ids"]:
|
||||
for i in range(len(resp["ids"][0])):
|
||||
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
|
||||
quality = metadata.get("quality", 0.0)
|
||||
score = 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
|
||||
if quality >= min_quality and score >= relevance_cutoff:
|
||||
results.append({
|
||||
"id": resp["ids"][0][i],
|
||||
"text": resp["documents"][0][i],
|
||||
"metadata": metadata,
|
||||
"score": score
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
|
||||
return []
|
||||
|
||||
else:
|
||||
# Local fallback
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
c = conn.cursor()
|
||||
rows = c.execute(
|
||||
"SELECT id, content, meta FROM short_mem WHERE content LIKE ? LIMIT ?",
|
||||
(f"%{query}%", limit)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
results = []
|
||||
for row in rows:
|
||||
meta = json.loads(row[2] or "{}")
|
||||
quality = meta.get("quality", 0.0)
|
||||
if quality >= min_quality:
|
||||
results.append({
|
||||
"id": row[0],
|
||||
"text": row[1],
|
||||
"metadata": meta
|
||||
})
|
||||
return results
|
||||
|
||||
def reset_short_term(self):
|
||||
"""Completely clears short-term memory."""
|
||||
conn = sqlite3.connect(self.short_db)
|
||||
conn.execute("DELETE FROM short_mem")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Long-Term Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def _sanitize_metadata(self, metadata: Dict) -> Dict:
|
||||
"""Sanitize metadata for ChromaDB - convert to acceptable types"""
|
||||
sanitized = {}
|
||||
for k, v in metadata.items():
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, (str, int, float, bool)):
|
||||
sanitized[k] = v
|
||||
elif isinstance(v, dict):
|
||||
# Convert dict to string representation
|
||||
sanitized[k] = str(v)
|
||||
else:
|
||||
# Convert other types to string
|
||||
sanitized[k] = str(v)
|
||||
return sanitized
|
||||
|
||||
def store_long_term(
|
||||
self,
|
||||
text: str,
|
||||
metadata: Dict[str, Any] = None,
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
):
|
||||
"""Store in long-term memory with optional quality metrics"""
|
||||
logger.info(f"Storing in long-term memory: {text[:100]}...")
|
||||
logger.info(f"Initial metadata: {metadata}")
|
||||
|
||||
# Process metadata
|
||||
metadata = metadata or {}
|
||||
metadata = self._process_quality_metrics(
|
||||
metadata, completeness, relevance, clarity,
|
||||
accuracy, weights, evaluator_quality
|
||||
)
|
||||
logger.info(f"Processed metadata: {metadata}")
|
||||
|
||||
# Generate unique ID
|
||||
ident = str(time.time_ns())
|
||||
created = time.time()
|
||||
|
||||
# Store in SQLite
|
||||
try:
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
conn.execute(
|
||||
"INSERT INTO long_mem (id, content, meta, created_at) VALUES (?,?,?,?)",
|
||||
(ident, text, json.dumps(metadata), created)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"Successfully stored in SQLite with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in SQLite: {e}")
|
||||
return
|
||||
|
||||
# Store in vector database if enabled
|
||||
if self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Ensure API key is correctly set
|
||||
|
||||
logger.info("Getting embeddings from OpenAI...")
|
||||
logger.debug(f"Embedding input text: {text}") # Log the input text
|
||||
|
||||
response = client.embeddings.create(
|
||||
input=text,
|
||||
model="text-embedding-3-small"
|
||||
)
|
||||
embedding = response.data[0].embedding
|
||||
logger.info("Successfully got embeddings")
|
||||
logger.debug(f"Received embedding of length: {len(embedding)}") # Log embedding details
|
||||
|
||||
# Sanitize metadata for ChromaDB
|
||||
sanitized_metadata = self._sanitize_metadata(metadata)
|
||||
|
||||
# Store in ChromaDB with embedding
|
||||
self.chroma_col.add(
|
||||
documents=[text],
|
||||
metadatas=[sanitized_metadata],
|
||||
ids=[ident],
|
||||
embeddings=[embedding]
|
||||
)
|
||||
logger.info(f"Successfully stored in ChromaDB with ID: {ident}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in ChromaDB: {e}")
|
||||
|
||||
elif self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
try:
|
||||
self.mem0_client.add(text, metadata=metadata)
|
||||
logger.info("Successfully stored in Mem0")
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing in Mem0: {e}")
|
||||
|
||||
|
||||
def search_long_term(
|
||||
self,
|
||||
query: str,
|
||||
limit: int = 5,
|
||||
relevance_cutoff: float = 0.0,
|
||||
min_quality: float = 0.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search long-term memory with optional quality filter"""
|
||||
self._log_verbose(f"Searching long memory for: {query}")
|
||||
self._log_verbose(f"Min quality: {min_quality}")
|
||||
|
||||
found = []
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
results = self.mem0_client.search(query=query, limit=limit)
|
||||
# Filter by quality
|
||||
filtered = [r for r in results if r.get("metadata", {}).get("quality", 0.0) >= min_quality]
|
||||
logger.info(f"Found {len(filtered)} results in Mem0")
|
||||
return filtered
|
||||
|
||||
elif self.use_rag and hasattr(self, "chroma_col"):
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
|
||||
# Get query embedding
|
||||
response = client.embeddings.create(
|
||||
input=query,
|
||||
model="text-embedding-3-small" # Using consistent model
|
||||
)
|
||||
query_embedding = response.data[0].embedding
|
||||
|
||||
# Search ChromaDB with embedding
|
||||
resp = self.chroma_col.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=limit,
|
||||
include=["documents", "metadatas", "distances"]
|
||||
)
|
||||
|
||||
results = []
|
||||
if resp["ids"]:
|
||||
for i in range(len(resp["ids"][0])):
|
||||
metadata = resp["metadatas"][0][i] if "metadatas" in resp else {}
|
||||
text = resp["documents"][0][i]
|
||||
# Add memory record citation
|
||||
text = f"{text} (Memory record: {text})"
|
||||
found.append({
|
||||
"id": resp["ids"][0][i],
|
||||
"text": text,
|
||||
"metadata": metadata,
|
||||
"score": 1.0 - (resp["distances"][0][i] if "distances" in resp else 0.0)
|
||||
})
|
||||
logger.info(f"Found {len(found)} results in ChromaDB")
|
||||
|
||||
except Exception as e:
|
||||
self._log_verbose(f"Error searching ChromaDB: {e}", logging.ERROR)
|
||||
|
||||
# Always try SQLite as fallback or additional source
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
c = conn.cursor()
|
||||
rows = c.execute(
|
||||
"SELECT id, content, meta, created_at FROM long_mem WHERE content LIKE ? LIMIT ?",
|
||||
(f"%{query}%", limit)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
for row in rows:
|
||||
meta = json.loads(row[2] or "{}")
|
||||
text = row[1]
|
||||
# Add memory record citation if not already present
|
||||
if "(Memory record:" not in text:
|
||||
text = f"{text} (Memory record: {text})"
|
||||
# Only add if not already found by ChromaDB/Mem0
|
||||
if not any(f["id"] == row[0] for f in found):
|
||||
found.append({
|
||||
"id": row[0],
|
||||
"text": text,
|
||||
"metadata": meta,
|
||||
"created_at": row[3]
|
||||
})
|
||||
logger.info(f"Found {len(found)} total results after SQLite")
|
||||
|
||||
results = found
|
||||
|
||||
# Filter by quality if needed
|
||||
if min_quality > 0:
|
||||
self._log_verbose(f"Found {len(results)} initial results")
|
||||
results = [
|
||||
r for r in results
|
||||
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
|
||||
]
|
||||
self._log_verbose(f"After quality filter: {len(results)} results")
|
||||
|
||||
# Apply relevance cutoff if specified
|
||||
if relevance_cutoff > 0:
|
||||
results = [r for r in results if r.get("score", 1.0) >= relevance_cutoff]
|
||||
logger.info(f"After relevance filter: {len(results)} results")
|
||||
|
||||
return results[:limit]
|
||||
|
||||
def reset_long_term(self):
|
||||
"""Clear local LTM DB, plus Chroma or mem0 if in use."""
|
||||
conn = sqlite3.connect(self.long_db)
|
||||
conn.execute("DELETE FROM long_mem")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
# Mem0 has no universal reset API. Could implement partial or no-op.
|
||||
pass
|
||||
if self.use_rag and hasattr(self, "chroma_client"):
|
||||
self.chroma_client.reset() # entire DB
|
||||
self._init_chroma() # re-init fresh
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Entity Memory Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_entity(self, name: str, type_: str, desc: str, relations: str):
|
||||
"""
|
||||
Save entity info in LTM (or mem0/rag).
|
||||
We'll label the metadata type = entity for easy filtering.
|
||||
"""
|
||||
data = f"Entity {name}({type_}): {desc} | relationships: {relations}"
|
||||
self.store_long_term(data, metadata={"category": "entity"})
|
||||
|
||||
def search_entity(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Filter to items that have metadata 'category=entity'.
|
||||
"""
|
||||
all_hits = self.search_long_term(query, limit=20) # gather more
|
||||
ents = []
|
||||
for h in all_hits:
|
||||
meta = h.get("metadata") or {}
|
||||
if meta.get("category") == "entity":
|
||||
ents.append(h)
|
||||
return ents[:limit]
|
||||
|
||||
def reset_entity_only(self):
|
||||
"""
|
||||
If you only want to drop entity items from LTM, you'd do a custom
|
||||
delete from local DB where meta LIKE '%category=entity%'.
|
||||
For brevity, we do a full LTM reset here.
|
||||
"""
|
||||
self.reset_long_term()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# User Memory Methods
|
||||
# -------------------------------------------------------------------------
|
||||
def store_user_memory(self, user_id: str, text: str, extra: Dict[str, Any] = None):
|
||||
"""
|
||||
If mem0 is used, do user-based addition. Otherwise store in LTM with user in metadata.
|
||||
"""
|
||||
meta = {"user_id": user_id}
|
||||
if extra:
|
||||
meta.update(extra)
|
||||
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
self.mem0_client.add(text, user_id=user_id, metadata=meta)
|
||||
else:
|
||||
self.store_long_term(text, metadata=meta)
|
||||
|
||||
def search_user_memory(self, user_id: str, query: str, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
If mem0 is used, pass user_id in. Otherwise fallback to local filter on user in metadata.
|
||||
"""
|
||||
if self.use_mem0 and hasattr(self, "mem0_client"):
|
||||
return self.mem0_client.search(query=query, limit=limit, user_id=user_id)
|
||||
else:
|
||||
hits = self.search_long_term(query, limit=20)
|
||||
filtered = []
|
||||
for h in hits:
|
||||
meta = h.get("metadata", {})
|
||||
if meta.get("user_id") == user_id:
|
||||
filtered.append(h)
|
||||
return filtered[:limit]
|
||||
|
||||
def reset_user_memory(self):
|
||||
"""
|
||||
Clear all user-based info. For simplicity, we do a full LTM reset.
|
||||
Real usage might filter only metadata "user_id".
|
||||
"""
|
||||
self.reset_long_term()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Putting it all Together: Task Finalization
|
||||
# -------------------------------------------------------------------------
|
||||
def finalize_task_output(
|
||||
self,
|
||||
content: str,
|
||||
agent_name: str,
|
||||
quality_score: float,
|
||||
threshold: float = 0.7,
|
||||
metrics: Dict[str, Any] = None,
|
||||
task_id: str = None
|
||||
):
|
||||
"""Store task output in memory with appropriate metadata"""
|
||||
logger.info(f"Finalizing task output: {content[:100]}...")
|
||||
logger.info(f"Agent: {agent_name}, Quality: {quality_score}, Threshold: {threshold}")
|
||||
|
||||
metadata = {
|
||||
"task_id": task_id,
|
||||
"agent": agent_name,
|
||||
"quality": quality_score,
|
||||
"metrics": metrics,
|
||||
"task_type": "output",
|
||||
"stored_at": time.time()
|
||||
}
|
||||
logger.info(f"Prepared metadata: {metadata}")
|
||||
|
||||
# Always store in short-term memory
|
||||
try:
|
||||
logger.info("Storing in short-term memory...")
|
||||
self.store_short_term(
|
||||
text=content,
|
||||
metadata=metadata
|
||||
)
|
||||
logger.info("Successfully stored in short-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in short-term memory: {e}")
|
||||
|
||||
# Store in long-term memory if quality meets threshold
|
||||
if quality_score >= threshold:
|
||||
try:
|
||||
logger.info(f"Quality score {quality_score} >= {threshold}, storing in long-term memory...")
|
||||
self.store_long_term(
|
||||
text=content,
|
||||
metadata=metadata
|
||||
)
|
||||
logger.info("Successfully stored in long-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in long-term memory: {e}")
|
||||
else:
|
||||
logger.info(f"Quality score {quality_score} < {threshold}, skipping long-term storage")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Building Context (Short, Long, Entities, User)
|
||||
# -------------------------------------------------------------------------
|
||||
def build_context_for_task(
|
||||
self,
|
||||
task_descr: str,
|
||||
user_id: Optional[str] = None,
|
||||
additional: str = "",
|
||||
max_items: int = 3
|
||||
) -> str:
|
||||
"""
|
||||
Merges relevant short-term, long-term, entity, user memories
|
||||
into a single text block with deduplication and clean formatting.
|
||||
"""
|
||||
q = (task_descr + " " + additional).strip()
|
||||
lines = []
|
||||
seen_contents = set() # Track unique contents
|
||||
|
||||
def normalize_content(content: str) -> str:
|
||||
"""Normalize content for deduplication"""
|
||||
# Extract just the main content without citations for comparison
|
||||
normalized = content.split("(Memory record:")[0].strip()
|
||||
# Keep more characters to reduce false duplicates
|
||||
normalized = ''.join(c.lower() for c in normalized if not c.isspace())
|
||||
return normalized
|
||||
|
||||
def format_content(content: str, max_len: int = 150) -> str:
|
||||
"""Format content with clean truncation at word boundaries"""
|
||||
if not content:
|
||||
return ""
|
||||
|
||||
# Clean up content by removing extra whitespace and newlines
|
||||
content = ' '.join(content.split())
|
||||
|
||||
# If content contains a memory citation, preserve it
|
||||
if "(Memory record:" in content:
|
||||
return content # Keep original citation format
|
||||
|
||||
# Regular content truncation
|
||||
if len(content) <= max_len:
|
||||
return content
|
||||
|
||||
truncate_at = content.rfind(' ', 0, max_len - 3)
|
||||
if truncate_at == -1:
|
||||
truncate_at = max_len - 3
|
||||
return content[:truncate_at] + "..."
|
||||
|
||||
def add_section(title: str, hits: List[Any]) -> None:
|
||||
"""Add a section of memory hits with deduplication"""
|
||||
if not hits:
|
||||
return
|
||||
|
||||
formatted_hits = []
|
||||
for h in hits:
|
||||
content = h.get('text', '') if isinstance(h, dict) else str(h)
|
||||
if not content:
|
||||
continue
|
||||
|
||||
# Keep original format if it has a citation
|
||||
if "(Memory record:" in content:
|
||||
formatted = content
|
||||
else:
|
||||
formatted = format_content(content)
|
||||
|
||||
# Only add if we haven't seen this normalized content before
|
||||
normalized = normalize_content(formatted)
|
||||
if normalized not in seen_contents:
|
||||
seen_contents.add(normalized)
|
||||
formatted_hits.append(formatted)
|
||||
|
||||
if formatted_hits:
|
||||
# Add section header
|
||||
if lines:
|
||||
lines.append("") # Space before new section
|
||||
lines.append(title)
|
||||
lines.append("=" * len(title)) # Underline the title
|
||||
lines.append("") # Space after title
|
||||
|
||||
# Add formatted content with bullet points
|
||||
for content in formatted_hits:
|
||||
lines.append(f" • {content}")
|
||||
|
||||
# Add each section
|
||||
# First get all results
|
||||
short_term = self.search_short_term(q, limit=max_items)
|
||||
long_term = self.search_long_term(q, limit=max_items)
|
||||
entities = self.search_entity(q, limit=max_items)
|
||||
user_mem = self.search_user_memory(user_id, q, limit=max_items) if user_id else []
|
||||
|
||||
# Add sections in order of priority
|
||||
add_section("Short-term Memory Context", short_term)
|
||||
add_section("Long-term Memory Context", long_term)
|
||||
add_section("Entity Context", entities)
|
||||
if user_id:
|
||||
add_section("User Context", user_mem)
|
||||
|
||||
return "\n".join(lines) if lines else ""
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Master Reset (Everything)
|
||||
# -------------------------------------------------------------------------
|
||||
def reset_all(self):
|
||||
"""
|
||||
Fully wipes short-term, long-term, and any memory in mem0 or rag.
|
||||
"""
|
||||
self.reset_short_term()
|
||||
self.reset_long_term()
|
||||
# Entities & user memory are stored in LTM or mem0, so no separate step needed.
|
||||
|
||||
def _process_quality_metrics(
|
||||
self,
|
||||
metadata: Dict[str, Any],
|
||||
completeness: float = None,
|
||||
relevance: float = None,
|
||||
clarity: float = None,
|
||||
accuracy: float = None,
|
||||
weights: Dict[str, float] = None,
|
||||
evaluator_quality: float = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Process and store quality metrics in metadata"""
|
||||
metadata = metadata or {}
|
||||
|
||||
# Handle sub-metrics if provided
|
||||
if None not in [completeness, relevance, clarity, accuracy]:
|
||||
metadata.update({
|
||||
"completeness": completeness,
|
||||
"relevance": relevance,
|
||||
"clarity": clarity,
|
||||
"accuracy": accuracy,
|
||||
"quality": self.compute_quality_score(
|
||||
completeness, relevance, clarity, accuracy, weights
|
||||
)
|
||||
})
|
||||
# Handle external evaluator quality if provided
|
||||
elif evaluator_quality is not None:
|
||||
metadata["quality"] = evaluator_quality
|
||||
|
||||
return metadata
|
||||
|
||||
def calculate_quality_metrics(
|
||||
self,
|
||||
output: str,
|
||||
expected_output: str,
|
||||
llm: Optional[str] = None,
|
||||
custom_prompt: Optional[str] = None
|
||||
) -> Dict[str, float]:
|
||||
"""Calculate quality metrics using LLM"""
|
||||
logger.info("Calculating quality metrics for output")
|
||||
logger.info(f"Output: {output[:100]}...")
|
||||
logger.info(f"Expected: {expected_output[:100]}...")
|
||||
|
||||
# Default evaluation prompt
|
||||
default_prompt = f"""
|
||||
Evaluate the following output against expected output.
|
||||
Score each metric from 0.0 to 1.0:
|
||||
- Completeness: Does it address all requirements?
|
||||
- Relevance: Does it match expected output?
|
||||
- Clarity: Is it clear and well-structured?
|
||||
- Accuracy: Is it factually correct?
|
||||
|
||||
Expected: {expected_output}
|
||||
Actual: {output}
|
||||
|
||||
Return ONLY a JSON with these keys: completeness, relevance, clarity, accuracy
|
||||
Example: {{"completeness": 0.95, "relevance": 0.8, "clarity": 0.9, "accuracy": 0.85}}
|
||||
"""
|
||||
|
||||
try:
|
||||
# Use OpenAI client from main.py
|
||||
from ..main import client
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=llm or "gpt-4o",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": custom_prompt or default_prompt
|
||||
}],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.3
|
||||
)
|
||||
|
||||
metrics = json.loads(response.choices[0].message.content)
|
||||
|
||||
# Validate metrics
|
||||
required = ["completeness", "relevance", "clarity", "accuracy"]
|
||||
if not all(k in metrics for k in required):
|
||||
raise ValueError("Missing required metrics in LLM response")
|
||||
|
||||
logger.info(f"Calculated metrics: {metrics}")
|
||||
return metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating metrics: {e}")
|
||||
return {
|
||||
"completeness": 0.0,
|
||||
"relevance": 0.0,
|
||||
"clarity": 0.0,
|
||||
"accuracy": 0.0
|
||||
}
|
||||
|
||||
def store_quality(
|
||||
self,
|
||||
text: str,
|
||||
quality_score: float,
|
||||
task_id: Optional[str] = None,
|
||||
iteration: Optional[int] = None,
|
||||
metrics: Optional[Dict[str, float]] = None,
|
||||
memory_type: Literal["short", "long"] = "long"
|
||||
) -> None:
|
||||
"""Store quality metrics in memory"""
|
||||
logger.info(f"Attempting to store in {memory_type} memory: {text[:100]}...")
|
||||
|
||||
metadata = {
|
||||
"quality": quality_score,
|
||||
"task_id": task_id,
|
||||
"iteration": iteration
|
||||
}
|
||||
|
||||
if metrics:
|
||||
metadata.update({
|
||||
k: v for k, v in metrics.items() # Remove metric_ prefix
|
||||
})
|
||||
|
||||
logger.info(f"With metadata: {metadata}")
|
||||
|
||||
try:
|
||||
if memory_type == "short":
|
||||
self.store_short_term(text, metadata=metadata)
|
||||
logger.info("Successfully stored in short-term memory")
|
||||
else:
|
||||
self.store_long_term(text, metadata=metadata)
|
||||
logger.info("Successfully stored in long-term memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store in memory: {e}")
|
||||
|
||||
def search_with_quality(
|
||||
self,
|
||||
query: str,
|
||||
min_quality: float = 0.0,
|
||||
memory_type: Literal["short", "long"] = "long",
|
||||
limit: int = 5
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search with quality filter"""
|
||||
logger.info(f"Searching {memory_type} memory for: {query}")
|
||||
logger.info(f"Min quality: {min_quality}")
|
||||
|
||||
search_func = (
|
||||
self.search_short_term if memory_type == "short"
|
||||
else self.search_long_term
|
||||
)
|
||||
|
||||
results = search_func(query, limit=limit)
|
||||
logger.info(f"Found {len(results)} initial results")
|
||||
|
||||
filtered = [
|
||||
r for r in results
|
||||
if r.get("metadata", {}).get("quality", 0.0) >= min_quality
|
||||
]
|
||||
logger.info(f"After quality filter: {len(filtered)} results")
|
||||
|
||||
return filtered
|
||||
@@ -0,0 +1,3 @@
|
||||
from .process import Process
|
||||
|
||||
__all__ = ['Process']
|
||||
@@ -0,0 +1,569 @@
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Dict, Optional, List, Any, AsyncGenerator
|
||||
from pydantic import BaseModel
|
||||
from ..agent.agent import Agent
|
||||
from ..task.task import Task
|
||||
from ..main import display_error, client
|
||||
|
||||
class LoopItems(BaseModel):
|
||||
items: List[Any]
|
||||
|
||||
class Process:
|
||||
def __init__(self, tasks: Dict[str, Task], agents: List[Agent], manager_llm: Optional[str] = None, verbose: bool = False, max_iter: int = 10):
|
||||
self.tasks = tasks
|
||||
self.agents = agents
|
||||
self.manager_llm = manager_llm
|
||||
self.verbose = verbose
|
||||
self.max_iter = max_iter
|
||||
|
||||
async def aworkflow(self) -> AsyncGenerator[str, None]:
|
||||
"""Async version of workflow method"""
|
||||
current_iter = 0 # Track how many times we've looped
|
||||
# Build workflow relationships first
|
||||
for task in self.tasks.values():
|
||||
if task.next_tasks:
|
||||
for next_task_name in task.next_tasks:
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
if next_task:
|
||||
next_task.previous_tasks.append(task.name)
|
||||
|
||||
# Find start task
|
||||
start_task = None
|
||||
for task_id, task in self.tasks.items():
|
||||
if task.is_start:
|
||||
start_task = task
|
||||
break
|
||||
|
||||
if not start_task:
|
||||
start_task = list(self.tasks.values())[0]
|
||||
logging.info("No start task marked, using first task")
|
||||
|
||||
current_task = start_task
|
||||
visited_tasks = set()
|
||||
loop_data = {} # Store loop-specific data
|
||||
|
||||
while current_task:
|
||||
current_iter += 1
|
||||
if current_iter > self.max_iter:
|
||||
logging.info(f"Max iteration limit {self.max_iter} reached, ending workflow.")
|
||||
break
|
||||
|
||||
task_id = current_task.id
|
||||
logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
|
||||
|
||||
# Add context from previous tasks to description
|
||||
if current_task.previous_tasks or current_task.context:
|
||||
context = "\nInput data from previous tasks:"
|
||||
|
||||
# Add data from previous tasks in workflow
|
||||
for prev_name in current_task.previous_tasks:
|
||||
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
|
||||
if prev_task and prev_task.result:
|
||||
# Handle loop data
|
||||
if current_task.task_type == "loop":
|
||||
# create a loop manager Agent
|
||||
loop_manager = Agent(
|
||||
name="Loop Manager",
|
||||
role="Loop data processor",
|
||||
goal="Process loop data and convert it to list format",
|
||||
backstory="Expert at handling loop data and converting it to proper format",
|
||||
llm=self.manager_llm,
|
||||
verbose=self.verbose,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# get the loop data convert it to list using calling Agent class chat
|
||||
loop_prompt = f"""
|
||||
Process this data into a list format:
|
||||
{prev_task.result.raw}
|
||||
|
||||
Return a JSON object with an 'items' array containing the items to process.
|
||||
"""
|
||||
if current_task.async_execution:
|
||||
loop_data_str = await loop_manager.achat(
|
||||
prompt=loop_prompt,
|
||||
output_json=LoopItems
|
||||
)
|
||||
else:
|
||||
loop_data_str = loop_manager.chat(
|
||||
prompt=loop_prompt,
|
||||
output_json=LoopItems
|
||||
)
|
||||
|
||||
try:
|
||||
# The response will already be parsed into LoopItems model
|
||||
loop_data[f"loop_{current_task.name}"] = {
|
||||
"items": loop_data_str.items,
|
||||
"index": 0,
|
||||
"remaining": len(loop_data_str.items)
|
||||
}
|
||||
context += f"\nCurrent loop item: {loop_data_str.items[0]}"
|
||||
except Exception as e:
|
||||
display_error(f"Failed to process loop data: {e}")
|
||||
context += f"\n{prev_name}: {prev_task.result.raw}"
|
||||
else:
|
||||
context += f"\n{prev_name}: {prev_task.result.raw}"
|
||||
|
||||
# Add data from context tasks
|
||||
if current_task.context:
|
||||
for ctx_task in current_task.context:
|
||||
if ctx_task.result and ctx_task.name != current_task.name:
|
||||
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
|
||||
|
||||
# Update task description with context
|
||||
current_task.description = current_task.description + context
|
||||
|
||||
# Execute task using existing run_task method
|
||||
yield task_id
|
||||
visited_tasks.add(task_id)
|
||||
|
||||
# Reset completed task to "not started" so it can run again
|
||||
if self.tasks[task_id].status == "completed":
|
||||
logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
|
||||
self.tasks[task_id].status = "not started"
|
||||
|
||||
# Handle loop progression
|
||||
if current_task.task_type == "loop":
|
||||
loop_key = f"loop_{current_task.name}"
|
||||
if loop_key in loop_data:
|
||||
loop_info = loop_data[loop_key]
|
||||
loop_info["index"] += 1
|
||||
has_more = loop_info["remaining"] > 0
|
||||
|
||||
# Update result to trigger correct condition
|
||||
if current_task.result:
|
||||
result = current_task.result.raw
|
||||
if has_more:
|
||||
result += "\nmore"
|
||||
else:
|
||||
result += "\ndone"
|
||||
current_task.result.raw = result
|
||||
|
||||
# Determine next task based on result
|
||||
next_task = None
|
||||
if current_task and current_task.result:
|
||||
if current_task.task_type in ["decision", "loop"]:
|
||||
result = current_task.result.raw.lower()
|
||||
# Check conditions
|
||||
for condition, tasks in current_task.condition.items():
|
||||
if condition.lower() in result:
|
||||
# Handle both list and direct string values
|
||||
task_value = tasks[0] if isinstance(tasks, list) else tasks
|
||||
if not task_value or task_value == "exit": # If empty or explicit exit
|
||||
logging.info("Workflow exit condition met, ending workflow")
|
||||
current_task = None
|
||||
break
|
||||
next_task_name = task_value
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
# For loops, allow revisiting the same task
|
||||
if next_task and next_task.id == current_task.id:
|
||||
visited_tasks.discard(current_task.id)
|
||||
break
|
||||
|
||||
if not next_task and current_task and current_task.next_tasks:
|
||||
next_task_name = current_task.next_tasks[0]
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
|
||||
current_task = next_task
|
||||
if not current_task:
|
||||
logging.info("Workflow execution completed")
|
||||
break
|
||||
|
||||
async def asequential(self) -> AsyncGenerator[str, None]:
|
||||
"""Async version of sequential method"""
|
||||
for task_id in self.tasks:
|
||||
if self.tasks[task_id].status != "completed":
|
||||
yield task_id
|
||||
|
||||
async def ahierarchical(self) -> AsyncGenerator[str, None]:
|
||||
"""Async version of hierarchical method"""
|
||||
logging.debug(f"Starting hierarchical task execution with {len(self.tasks)} tasks")
|
||||
manager_agent = Agent(
|
||||
name="Manager",
|
||||
role="Project manager",
|
||||
goal="Manage the entire flow of tasks and delegate them to the right agent",
|
||||
backstory="Expert project manager to coordinate tasks among agents",
|
||||
llm=self.manager_llm,
|
||||
verbose=self.verbose,
|
||||
markdown=True,
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
class ManagerInstructions(BaseModel):
|
||||
task_id: int
|
||||
agent_name: str
|
||||
action: str
|
||||
|
||||
manager_task = Task(
|
||||
name="manager_task",
|
||||
description="Decide the order of tasks and which agent executes them",
|
||||
expected_output="All tasks completed successfully",
|
||||
agent=manager_agent
|
||||
)
|
||||
manager_task_id = yield manager_task
|
||||
logging.info(f"Created manager task with ID {manager_task_id}")
|
||||
|
||||
completed_count = 0
|
||||
total_tasks = len(self.tasks) - 1
|
||||
logging.info(f"Need to complete {total_tasks} tasks (excluding manager task)")
|
||||
|
||||
while completed_count < total_tasks:
|
||||
tasks_summary = []
|
||||
for tid, tk in self.tasks.items():
|
||||
if tk.name == "manager_task":
|
||||
continue
|
||||
task_info = {
|
||||
"task_id": tid,
|
||||
"name": tk.name,
|
||||
"description": tk.description,
|
||||
"status": tk.status if tk.status else "not started",
|
||||
"agent": tk.agent.name if tk.agent else "No agent"
|
||||
}
|
||||
tasks_summary.append(task_info)
|
||||
logging.info(f"Task {tid} status: {task_info}")
|
||||
|
||||
manager_prompt = f"""
|
||||
Here is the current status of all tasks except yours (manager_task):
|
||||
{tasks_summary}
|
||||
|
||||
Provide a JSON with the structure:
|
||||
{{
|
||||
"task_id": <int>,
|
||||
"agent_name": "<string>",
|
||||
"action": "<execute or stop>"
|
||||
}}
|
||||
"""
|
||||
|
||||
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
|
||||
)
|
||||
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 = manager_response.choices[0].message.parsed
|
||||
logging.info(f"Manager instructions: {parsed_instructions}")
|
||||
except Exception as e:
|
||||
display_error(f"Manager parse error: {e}")
|
||||
logging.error(f"Manager parse error: {str(e)}", exc_info=True)
|
||||
break
|
||||
|
||||
selected_task_id = parsed_instructions.task_id
|
||||
selected_agent_name = parsed_instructions.agent_name
|
||||
action = parsed_instructions.action
|
||||
|
||||
logging.info(f"Manager selected task_id={selected_task_id}, agent={selected_agent_name}, action={action}")
|
||||
|
||||
if action.lower() == "stop":
|
||||
logging.info("Manager decided to stop task execution")
|
||||
break
|
||||
|
||||
if selected_task_id not in self.tasks:
|
||||
error_msg = f"Manager selected invalid task id {selected_task_id}"
|
||||
display_error(error_msg)
|
||||
logging.error(error_msg)
|
||||
break
|
||||
|
||||
original_agent = self.tasks[selected_task_id].agent.name if self.tasks[selected_task_id].agent else "None"
|
||||
for a in self.agents:
|
||||
if a.name == selected_agent_name:
|
||||
self.tasks[selected_task_id].agent = a
|
||||
logging.info(f"Changed agent for task {selected_task_id} from {original_agent} to {selected_agent_name}")
|
||||
break
|
||||
|
||||
if self.tasks[selected_task_id].status != "completed":
|
||||
logging.info(f"Starting execution of task {selected_task_id}")
|
||||
yield selected_task_id
|
||||
logging.info(f"Finished execution of task {selected_task_id}, status: {self.tasks[selected_task_id].status}")
|
||||
|
||||
if self.tasks[selected_task_id].status == "completed":
|
||||
completed_count += 1
|
||||
logging.info(f"Task {selected_task_id} completed. Total completed: {completed_count}/{total_tasks}")
|
||||
|
||||
self.tasks[manager_task.id].status = "completed"
|
||||
if self.verbose >= 1:
|
||||
logging.info("All tasks completed under manager supervision.")
|
||||
logging.info("Hierarchical task execution finished")
|
||||
|
||||
def workflow(self):
|
||||
"""Synchronous version of workflow method"""
|
||||
current_iter = 0 # Track how many times we've looped
|
||||
# Build workflow relationships first
|
||||
for task in self.tasks.values():
|
||||
if task.next_tasks:
|
||||
for next_task_name in task.next_tasks:
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
if next_task:
|
||||
next_task.previous_tasks.append(task.name)
|
||||
|
||||
# Find start task
|
||||
start_task = None
|
||||
for task_id, task in self.tasks.items():
|
||||
if task.is_start:
|
||||
start_task = task
|
||||
break
|
||||
|
||||
if not start_task:
|
||||
start_task = list(self.tasks.values())[0]
|
||||
logging.info("No start task marked, using first task")
|
||||
|
||||
current_task = start_task
|
||||
visited_tasks = set()
|
||||
loop_data = {} # Store loop-specific data
|
||||
|
||||
while current_task:
|
||||
current_iter += 1
|
||||
if current_iter > self.max_iter:
|
||||
logging.info(f"Max iteration limit {self.max_iter} reached, ending workflow.")
|
||||
break
|
||||
|
||||
task_id = current_task.id
|
||||
logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
|
||||
|
||||
# Add context from previous tasks to description
|
||||
if current_task.previous_tasks or current_task.context:
|
||||
context = "\nInput data from previous tasks:"
|
||||
|
||||
# Add data from previous tasks in workflow
|
||||
for prev_name in current_task.previous_tasks:
|
||||
prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
|
||||
if prev_task and prev_task.result:
|
||||
# Handle loop data
|
||||
if current_task.task_type == "loop":
|
||||
# create a loop manager Agent
|
||||
loop_manager = Agent(
|
||||
name="Loop Manager",
|
||||
role="Loop data processor",
|
||||
goal="Process loop data and convert it to list format",
|
||||
backstory="Expert at handling loop data and converting it to proper format",
|
||||
llm=self.manager_llm,
|
||||
verbose=self.verbose,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# get the loop data convert it to list using calling Agent class chat
|
||||
loop_prompt = f"""
|
||||
Process this data into a list format:
|
||||
{prev_task.result.raw}
|
||||
|
||||
Return a JSON object with an 'items' array containing the items to process.
|
||||
"""
|
||||
loop_data_str = loop_manager.chat(
|
||||
prompt=loop_prompt,
|
||||
output_json=LoopItems
|
||||
)
|
||||
|
||||
try:
|
||||
# The response will already be parsed into LoopItems model
|
||||
loop_data[f"loop_{current_task.name}"] = {
|
||||
"items": loop_data_str.items,
|
||||
"index": 0,
|
||||
"remaining": len(loop_data_str.items)
|
||||
}
|
||||
context += f"\nCurrent loop item: {loop_data_str.items[0]}"
|
||||
except Exception as e:
|
||||
display_error(f"Failed to process loop data: {e}")
|
||||
context += f"\n{prev_name}: {prev_task.result.raw}"
|
||||
else:
|
||||
context += f"\n{prev_name}: {prev_task.result.raw}"
|
||||
|
||||
# Add data from context tasks
|
||||
if current_task.context:
|
||||
for ctx_task in current_task.context:
|
||||
if ctx_task.result and ctx_task.name != current_task.name:
|
||||
context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
|
||||
|
||||
# Update task description with context
|
||||
current_task.description = current_task.description + context
|
||||
|
||||
# Execute task using existing run_task method
|
||||
yield task_id
|
||||
visited_tasks.add(task_id)
|
||||
|
||||
# Reset completed task to "not started" so it can run again
|
||||
if self.tasks[task_id].status == "completed":
|
||||
logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
|
||||
self.tasks[task_id].status = "not started"
|
||||
|
||||
# Handle loop progression
|
||||
if current_task.task_type == "loop":
|
||||
loop_key = f"loop_{current_task.name}"
|
||||
if loop_key in loop_data:
|
||||
loop_info = loop_data[loop_key]
|
||||
loop_info["index"] += 1
|
||||
has_more = loop_info["remaining"] > 0
|
||||
|
||||
# Update result to trigger correct condition
|
||||
if current_task.result:
|
||||
result = current_task.result.raw
|
||||
if has_more:
|
||||
result += "\nmore"
|
||||
else:
|
||||
result += "\ndone"
|
||||
current_task.result.raw = result
|
||||
|
||||
# Determine next task based on result
|
||||
next_task = None
|
||||
if current_task and current_task.result:
|
||||
if current_task.task_type in ["decision", "loop"]:
|
||||
result = current_task.result.raw.lower()
|
||||
# Check conditions
|
||||
for condition, tasks in current_task.condition.items():
|
||||
if condition.lower() in result:
|
||||
# Handle both list and direct string values
|
||||
task_value = tasks[0] if isinstance(tasks, list) else tasks
|
||||
if not task_value or task_value == "exit": # If empty or explicit exit
|
||||
logging.info("Workflow exit condition met, ending workflow")
|
||||
current_task = None
|
||||
break
|
||||
next_task_name = task_value
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
# For loops, allow revisiting the same task
|
||||
if next_task and next_task.id == current_task.id:
|
||||
visited_tasks.discard(current_task.id)
|
||||
break
|
||||
|
||||
if not next_task and current_task and current_task.next_tasks:
|
||||
next_task_name = current_task.next_tasks[0]
|
||||
next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
|
||||
|
||||
current_task = next_task
|
||||
if not current_task:
|
||||
logging.info("Workflow execution completed")
|
||||
break
|
||||
|
||||
def sequential(self):
|
||||
"""Synchronous version of sequential method"""
|
||||
for task_id in self.tasks:
|
||||
if self.tasks[task_id].status != "completed":
|
||||
yield task_id
|
||||
|
||||
def hierarchical(self):
|
||||
"""Synchronous version of hierarchical method"""
|
||||
logging.debug(f"Starting hierarchical task execution with {len(self.tasks)} tasks")
|
||||
manager_agent = Agent(
|
||||
name="Manager",
|
||||
role="Project manager",
|
||||
goal="Manage the entire flow of tasks and delegate them to the right agent",
|
||||
backstory="Expert project manager to coordinate tasks among agents",
|
||||
llm=self.manager_llm,
|
||||
verbose=self.verbose,
|
||||
markdown=True,
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
class ManagerInstructions(BaseModel):
|
||||
task_id: int
|
||||
agent_name: str
|
||||
action: str
|
||||
|
||||
manager_task = Task(
|
||||
name="manager_task",
|
||||
description="Decide the order of tasks and which agent executes them",
|
||||
expected_output="All tasks completed successfully",
|
||||
agent=manager_agent
|
||||
)
|
||||
manager_task_id = yield manager_task
|
||||
logging.info(f"Created manager task with ID {manager_task_id}")
|
||||
|
||||
completed_count = 0
|
||||
total_tasks = len(self.tasks) - 1
|
||||
logging.info(f"Need to complete {total_tasks} tasks (excluding manager task)")
|
||||
|
||||
while completed_count < total_tasks:
|
||||
tasks_summary = []
|
||||
for tid, tk in self.tasks.items():
|
||||
if tk.name == "manager_task":
|
||||
continue
|
||||
task_info = {
|
||||
"task_id": tid,
|
||||
"name": tk.name,
|
||||
"description": tk.description,
|
||||
"status": tk.status if tk.status else "not started",
|
||||
"agent": tk.agent.name if tk.agent else "No agent"
|
||||
}
|
||||
tasks_summary.append(task_info)
|
||||
logging.info(f"Task {tid} status: {task_info}")
|
||||
|
||||
manager_prompt = f"""
|
||||
Here is the current status of all tasks except yours (manager_task):
|
||||
{tasks_summary}
|
||||
|
||||
Provide a JSON with the structure:
|
||||
{{
|
||||
"task_id": <int>,
|
||||
"agent_name": "<string>",
|
||||
"action": "<execute or stop>"
|
||||
}}
|
||||
"""
|
||||
|
||||
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 = manager_response.choices[0].message.parsed
|
||||
logging.info(f"Manager instructions: {parsed_instructions}")
|
||||
except Exception as e:
|
||||
display_error(f"Manager parse error: {e}")
|
||||
logging.error(f"Manager parse error: {str(e)}", exc_info=True)
|
||||
break
|
||||
|
||||
selected_task_id = parsed_instructions.task_id
|
||||
selected_agent_name = parsed_instructions.agent_name
|
||||
action = parsed_instructions.action
|
||||
|
||||
logging.info(f"Manager selected task_id={selected_task_id}, agent={selected_agent_name}, action={action}")
|
||||
|
||||
if action.lower() == "stop":
|
||||
logging.info("Manager decided to stop task execution")
|
||||
break
|
||||
|
||||
if selected_task_id not in self.tasks:
|
||||
error_msg = f"Manager selected invalid task id {selected_task_id}"
|
||||
display_error(error_msg)
|
||||
logging.error(error_msg)
|
||||
break
|
||||
|
||||
original_agent = self.tasks[selected_task_id].agent.name if self.tasks[selected_task_id].agent else "None"
|
||||
for a in self.agents:
|
||||
if a.name == selected_agent_name:
|
||||
self.tasks[selected_task_id].agent = a
|
||||
logging.info(f"Changed agent for task {selected_task_id} from {original_agent} to {selected_agent_name}")
|
||||
break
|
||||
|
||||
if self.tasks[selected_task_id].status != "completed":
|
||||
logging.info(f"Starting execution of task {selected_task_id}")
|
||||
yield selected_task_id
|
||||
logging.info(f"Finished execution of task {selected_task_id}, status: {self.tasks[selected_task_id].status}")
|
||||
|
||||
if self.tasks[selected_task_id].status == "completed":
|
||||
completed_count += 1
|
||||
logging.info(f"Task {selected_task_id} completed. Total completed: {completed_count}/{total_tasks}")
|
||||
|
||||
self.tasks[manager_task.id].status = "completed"
|
||||
if self.verbose >= 1:
|
||||
logging.info("All tasks completed under manager supervision.")
|
||||
logging.info("Hierarchical task execution finished")
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Task module for AI agent tasks"""
|
||||
from .task import Task
|
||||
|
||||
__all__ = ['Task']
|
||||
@@ -0,0 +1,222 @@
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine
|
||||
from pydantic import BaseModel
|
||||
from ..main import TaskOutput
|
||||
from ..agent.agent import Agent
|
||||
import uuid
|
||||
import os
|
||||
import time
|
||||
|
||||
# Set up logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Task:
|
||||
def __init__(
|
||||
self,
|
||||
description: str,
|
||||
expected_output: Optional[str] = None,
|
||||
agent: Optional[Agent] = None,
|
||||
name: Optional[str] = None,
|
||||
tools: Optional[List[Any]] = None,
|
||||
context: Optional[List["Task"]] = None,
|
||||
async_execution: Optional[bool] = False,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
output_file: Optional[str] = None,
|
||||
output_json: Optional[Type[BaseModel]] = None,
|
||||
output_pydantic: Optional[Type[BaseModel]] = None,
|
||||
callback: Optional[Union[Callable[[TaskOutput], Any], Callable[[TaskOutput], Coroutine[Any, Any, Any]]]] = None,
|
||||
status: str = "not started",
|
||||
result: Optional[TaskOutput] = None,
|
||||
create_directory: Optional[bool] = False,
|
||||
id: Optional[int] = None,
|
||||
images: Optional[List[str]] = None,
|
||||
next_tasks: Optional[List[str]] = None,
|
||||
task_type: str = "task",
|
||||
condition: Optional[Dict[str, List[str]]] = None,
|
||||
is_start: bool = False,
|
||||
loop_state: Optional[Dict[str, Union[str, int]]] = None,
|
||||
memory=None,
|
||||
quality_check=True
|
||||
):
|
||||
self.id = str(uuid.uuid4()) if id is None else str(id)
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.expected_output = expected_output if expected_output is not None else "Complete the task successfully"
|
||||
self.agent = agent
|
||||
self.tools = tools if tools else []
|
||||
self.context = context if context else []
|
||||
self.async_execution = async_execution
|
||||
self.config = config if config else {}
|
||||
self.output_file = output_file
|
||||
self.output_json = output_json
|
||||
self.output_pydantic = output_pydantic
|
||||
self.callback = callback
|
||||
self.status = status
|
||||
self.result = result
|
||||
self.create_directory = create_directory
|
||||
self.images = images if images else []
|
||||
self.next_tasks = next_tasks if next_tasks else []
|
||||
self.task_type = task_type
|
||||
self.condition = condition if condition else {}
|
||||
self.is_start = is_start
|
||||
self.loop_state = loop_state if loop_state else {}
|
||||
self.memory = memory
|
||||
self.quality_check = quality_check
|
||||
|
||||
# Set logger level based on config verbose level
|
||||
verbose = self.config.get("verbose", 0)
|
||||
if verbose >= 5:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# Also set third-party loggers to WARNING
|
||||
logging.getLogger('chromadb').setLevel(logging.WARNING)
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
|
||||
if self.output_json and self.output_pydantic:
|
||||
raise ValueError("Only one output type can be defined")
|
||||
|
||||
# Track previous tasks based on next_tasks relationships
|
||||
self.previous_tasks = []
|
||||
|
||||
def __str__(self):
|
||||
return f"Task(name='{self.name if self.name else 'None'}', description='{self.description}', agent='{self.agent.name if self.agent else 'None'}', status='{self.status}')"
|
||||
|
||||
def initialize_memory(self):
|
||||
"""Initialize memory if config exists but memory doesn't"""
|
||||
if not self.memory and self.config.get('memory_config'):
|
||||
try:
|
||||
from ..memory.memory import Memory
|
||||
logger.info(f"Task {self.id}: Initializing memory from config: {self.config['memory_config']}")
|
||||
self.memory = Memory(config=self.config['memory_config'])
|
||||
logger.info(f"Task {self.id}: Memory initialized successfully")
|
||||
|
||||
# Verify database was created
|
||||
if os.path.exists(self.config['memory_config']['storage']['path']):
|
||||
logger.info(f"Task {self.id}: Memory database exists after initialization")
|
||||
else:
|
||||
logger.error(f"Task {self.id}: Failed to create memory database!")
|
||||
return self.memory
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to initialize memory: {e}")
|
||||
logger.exception(e)
|
||||
return None
|
||||
|
||||
def store_in_memory(self, content: str, agent_name: str = None, task_id: str = None):
|
||||
"""Store content in memory with metadata"""
|
||||
if self.memory:
|
||||
try:
|
||||
logger.info(f"Task {self.id}: Storing content in memory...")
|
||||
self.memory.store_long_term(
|
||||
text=content,
|
||||
metadata={
|
||||
"agent_name": agent_name or "Agent",
|
||||
"task_id": task_id or self.id,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
)
|
||||
logger.info(f"Task {self.id}: Content stored in memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to store content in memory: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
async def execute_callback(self, task_output: TaskOutput) -> None:
|
||||
"""Execute callback and store quality metrics if enabled"""
|
||||
logger.info(f"Task {self.id}: execute_callback called")
|
||||
logger.info(f"Quality check enabled: {self.quality_check}")
|
||||
|
||||
# Initialize memory if not already initialized
|
||||
if not self.memory:
|
||||
self.memory = self.initialize_memory()
|
||||
|
||||
logger.info(f"Memory object exists: {self.memory is not None}")
|
||||
if self.memory:
|
||||
logger.info(f"Memory config: {self.memory.cfg}")
|
||||
# Store task output in memory
|
||||
try:
|
||||
logger.info(f"Task {self.id}: Storing task output in memory...")
|
||||
self.store_in_memory(
|
||||
content=task_output.raw,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
task_id=self.id
|
||||
)
|
||||
logger.info(f"Task {self.id}: Task output stored in memory")
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to store task output in memory: {e}")
|
||||
logger.exception(e)
|
||||
|
||||
logger.info(f"Task output: {task_output.raw[:100]}...")
|
||||
|
||||
if self.quality_check and self.memory:
|
||||
try:
|
||||
logger.info(f"Task {self.id}: Starting memory operations")
|
||||
logger.info(f"Task {self.id}: Calculating quality metrics for output: {task_output.raw[:100]}...")
|
||||
|
||||
# Get quality metrics from LLM
|
||||
metrics = self.memory.calculate_quality_metrics(
|
||||
task_output.raw,
|
||||
self.expected_output
|
||||
)
|
||||
logger.info(f"Task {self.id}: Quality metrics calculated: {metrics}")
|
||||
|
||||
quality_score = metrics.get("accuracy", 0.0)
|
||||
logger.info(f"Task {self.id}: Quality score: {quality_score}")
|
||||
|
||||
# Store in both short and long-term memory with higher threshold
|
||||
logger.info(f"Task {self.id}: Finalizing task output in memory...")
|
||||
self.memory.finalize_task_output(
|
||||
content=task_output.raw,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
quality_score=quality_score,
|
||||
threshold=0.7, # Only high quality outputs in long-term memory
|
||||
metrics=metrics,
|
||||
task_id=self.id
|
||||
)
|
||||
logger.info(f"Task {self.id}: Finalized task output in memory")
|
||||
|
||||
# Store quality metrics separately
|
||||
logger.info(f"Task {self.id}: Storing quality metrics...")
|
||||
self.memory.store_quality(
|
||||
text=task_output.raw,
|
||||
quality_score=quality_score,
|
||||
task_id=self.id,
|
||||
metrics=metrics
|
||||
)
|
||||
|
||||
# Store in both short and long-term memory with higher threshold
|
||||
self.memory.finalize_task_output(
|
||||
content=task_output.raw,
|
||||
agent_name=self.agent.name if self.agent else "Agent",
|
||||
quality_score=quality_score,
|
||||
threshold=0.7 # Only high quality outputs in long-term memory
|
||||
)
|
||||
|
||||
# Build context for next tasks
|
||||
if self.next_tasks:
|
||||
logger.info(f"Task {self.id}: Building context for next tasks...")
|
||||
context = self.memory.build_context_for_task(
|
||||
task_descr=task_output.raw,
|
||||
max_items=5
|
||||
)
|
||||
logger.info(f"Task {self.id}: Built context for next tasks: {len(context)} items")
|
||||
|
||||
logger.info(f"Task {self.id}: Memory operations complete")
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to process memory operations: {e}")
|
||||
logger.exception(e) # Print full stack trace
|
||||
# Continue execution even if memory operations fail
|
||||
|
||||
# Execute original callback
|
||||
if self.callback:
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(self.callback):
|
||||
await self.callback(task_output)
|
||||
else:
|
||||
self.callback(task_output)
|
||||
except Exception as e:
|
||||
logger.error(f"Task {self.id}: Failed to execute callback: {e}")
|
||||
logger.exception(e)
|
||||
@@ -0,0 +1,149 @@
|
||||
# PraisonAI Tools Guide
|
||||
|
||||
Welcome to the PraisonAI Tools directory! This guide will help you understand how our tools work and how to create new ones, whether you're a beginner or an experienced programmer.
|
||||
|
||||
## What is a Tool?
|
||||
|
||||
A tool is a piece of code that helps our AI agents perform specific tasks. Think of tools as special abilities that we give to our agents. For example:
|
||||
- An internet search tool lets agents search the web
|
||||
- A stock market tool lets agents check stock prices
|
||||
- A weather tool lets agents check the weather
|
||||
|
||||
## Creating New Tools: The Two Approaches
|
||||
|
||||
### 1. Function-Based Approach (Simple Tools)
|
||||
|
||||
Best for simple tools that do one specific thing. Like a calculator that just adds numbers.
|
||||
|
||||
**When to use:**
|
||||
- Tool does one simple task
|
||||
- Doesn't need to remember information between uses
|
||||
- Doesn't need to share information with other tools
|
||||
- Quick, one-time operations
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
def internet_search(query: str):
|
||||
# Search the internet and return results
|
||||
return search_results
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from praisonaiagents.tools import internet_search
|
||||
|
||||
results = internet_search("AI news")
|
||||
```
|
||||
|
||||
### 2. Class-Based Approach (Complex Tools)
|
||||
|
||||
Best for tools that do multiple related things or need to remember information. Like a smart calculator that remembers your previous calculations and can do many different math operations.
|
||||
|
||||
**When to use:**
|
||||
- Tool has multiple related functions
|
||||
- Needs to remember or share information
|
||||
- Needs to manage resources efficiently
|
||||
- Has complex setup requirements
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
class StockTools:
|
||||
def get_stock_price(self, symbol):
|
||||
# Get current stock price
|
||||
return price
|
||||
|
||||
def get_stock_info(self, symbol):
|
||||
# Get detailed stock information
|
||||
return info
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from praisonaiagents.tools import get_stock_price, get_stock_info
|
||||
|
||||
price = get_stock_price("AAPL")
|
||||
info = get_stock_info("AAPL")
|
||||
```
|
||||
|
||||
## How to Choose Your Approach
|
||||
|
||||
Ask yourself these questions:
|
||||
|
||||
1. **Is your tool doing one simple thing?**
|
||||
- Yes → Use Function-Based Approach
|
||||
- No → Consider Class-Based Approach
|
||||
|
||||
2. **Does your tool need to remember information?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
3. **Are your tool's operations related to each other?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
4. **Does your tool need to manage resources efficiently?**
|
||||
- Yes → Use Class-Based Approach
|
||||
- No → Use Function-Based Approach
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### Internet Search Tool (Function-Based)
|
||||
- Does one thing: searches the internet
|
||||
- Doesn't need to remember previous searches
|
||||
- Each search is independent
|
||||
- Simple input/output operation
|
||||
|
||||
### Stock Market Tool (Class-Based)
|
||||
- Does multiple things: check prices, get company info, get historical data
|
||||
- Remembers stock information to avoid repeated downloads
|
||||
- Operations are related (all about stocks)
|
||||
- Manages connections efficiently
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Choose Your Approach** based on the guidelines above
|
||||
|
||||
2. **Create Your Tool File**:
|
||||
- Name it descriptively (e.g., `weather_tools.py`)
|
||||
- Place it in the `praisonaiagents/tools` directory
|
||||
|
||||
3. **Write Your Tool**:
|
||||
- Add clear documentation
|
||||
- Include type hints for better understanding
|
||||
- Handle errors gracefully
|
||||
|
||||
4. **Test Your Tool**:
|
||||
- Make sure it works as expected
|
||||
- Test error cases
|
||||
- Check performance
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Documentation**:
|
||||
- Explain what your tool does
|
||||
- Provide examples
|
||||
- List any requirements
|
||||
|
||||
2. **Error Handling**:
|
||||
- Always handle possible errors
|
||||
- Return helpful error messages
|
||||
- Don't let your tool crash
|
||||
|
||||
3. **Performance**:
|
||||
- Keep it efficient
|
||||
- Don't waste resources
|
||||
- Cache when helpful
|
||||
|
||||
4. **User-Friendly**:
|
||||
- Make it easy to use
|
||||
- Use clear function/method names
|
||||
- Keep it simple
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check existing tools for examples
|
||||
- Ask in our community
|
||||
- Read the documentation
|
||||
- Don't hesitate to ask questions!
|
||||
|
||||
Remember: The goal is to make tools that are easy to use and maintain. Choose the approach that makes the most sense for your specific tool's needs.
|
||||
@@ -0,0 +1,178 @@
|
||||
"""Tools package for PraisonAI Agents"""
|
||||
from importlib import import_module
|
||||
from typing import Any
|
||||
|
||||
# Map of function names to their module and class (if any)
|
||||
TOOL_MAPPINGS = {
|
||||
# Direct functions
|
||||
'internet_search': ('.duckduckgo_tools', None),
|
||||
'duckduckgo': ('.duckduckgo_tools', None),
|
||||
|
||||
# arXiv Tools
|
||||
'search_arxiv': ('.arxiv_tools', None),
|
||||
'get_arxiv_paper': ('.arxiv_tools', None),
|
||||
'get_papers_by_author': ('.arxiv_tools', None),
|
||||
'get_papers_by_category': ('.arxiv_tools', None),
|
||||
'arxiv_tools': ('.arxiv_tools', None),
|
||||
|
||||
# Wikipedia Tools
|
||||
'wiki_search': ('.wikipedia_tools', None),
|
||||
'wiki_summary': ('.wikipedia_tools', None),
|
||||
'wiki_page': ('.wikipedia_tools', None),
|
||||
'wiki_random': ('.wikipedia_tools', None),
|
||||
'wiki_language': ('.wikipedia_tools', None),
|
||||
'wikipedia_tools': ('.wikipedia_tools', None),
|
||||
|
||||
# Newspaper Tools
|
||||
'get_article': ('.newspaper_tools', None),
|
||||
'get_news_sources': ('.newspaper_tools', None),
|
||||
'get_articles_from_source': ('.newspaper_tools', None),
|
||||
'get_trending_topics': ('.newspaper_tools', None),
|
||||
'newspaper_tools': ('.newspaper_tools', None),
|
||||
|
||||
# Spider Tools
|
||||
'scrape_page': ('.spider_tools', None),
|
||||
'extract_links': ('.spider_tools', None),
|
||||
'crawl': ('.spider_tools', None),
|
||||
'extract_text': ('.spider_tools', None),
|
||||
'spider_tools': ('.spider_tools', None),
|
||||
|
||||
# DuckDB Tools
|
||||
'query': ('.duckdb_tools', None),
|
||||
'create_table': ('.duckdb_tools', None),
|
||||
'load_data': ('.duckdb_tools', None),
|
||||
'export_data': ('.duckdb_tools', None),
|
||||
'get_table_info': ('.duckdb_tools', None),
|
||||
'analyze_data': ('.duckdb_tools', None),
|
||||
'duckdb_tools': ('.duckdb_tools', None),
|
||||
|
||||
# Shell Tools
|
||||
'execute_command': ('.shell_tools', None),
|
||||
'list_processes': ('.shell_tools', None),
|
||||
'kill_process': ('.shell_tools', None),
|
||||
'get_system_info': ('.shell_tools', None),
|
||||
'shell_tools': ('.shell_tools', None),
|
||||
|
||||
# Calculator Tools
|
||||
'evaluate': ('.calculator_tools', None),
|
||||
'solve_equation': ('.calculator_tools', None),
|
||||
'convert_units': ('.calculator_tools', None),
|
||||
'calculate_statistics': ('.calculator_tools', None),
|
||||
'calculate_financial': ('.calculator_tools', None),
|
||||
'calculator_tools': ('.calculator_tools', None),
|
||||
|
||||
# Class methods from YFinance
|
||||
'get_stock_price': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'get_stock_info': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'get_historical_data': ('.yfinance_tools', 'YFinanceTools'),
|
||||
'yfinance': ('.yfinance_tools', 'YFinanceTools'),
|
||||
|
||||
# File Tools
|
||||
'read_file': ('.file_tools', 'FileTools'),
|
||||
'write_file': ('.file_tools', 'FileTools'),
|
||||
'list_files': ('.file_tools', 'FileTools'),
|
||||
'get_file_info': ('.file_tools', 'FileTools'),
|
||||
'copy_file': ('.file_tools', 'FileTools'),
|
||||
'move_file': ('.file_tools', 'FileTools'),
|
||||
'delete_file': ('.file_tools', 'FileTools'),
|
||||
'file_tools': ('.file_tools', 'FileTools'),
|
||||
|
||||
# CSV Tools
|
||||
'read_csv': ('.csv_tools', 'CSVTools'),
|
||||
'write_csv': ('.csv_tools', 'CSVTools'),
|
||||
'merge_csv': ('.csv_tools', 'CSVTools'),
|
||||
'analyze_csv': ('.csv_tools', 'CSVTools'),
|
||||
'split_csv': ('.csv_tools', 'CSVTools'),
|
||||
'csv_tools': ('.csv_tools', 'CSVTools'),
|
||||
|
||||
# JSON Tools
|
||||
'read_json': ('.json_tools', 'JSONTools'),
|
||||
'write_json': ('.json_tools', 'JSONTools'),
|
||||
'merge_json': ('.json_tools', 'JSONTools'),
|
||||
'validate_json': ('.json_tools', 'JSONTools'),
|
||||
'analyze_json': ('.json_tools', 'JSONTools'),
|
||||
'transform_json': ('.json_tools', 'JSONTools'),
|
||||
'json_tools': ('.json_tools', 'JSONTools'),
|
||||
|
||||
# Excel Tools
|
||||
'read_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'write_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'merge_excel': ('.excel_tools', 'ExcelTools'),
|
||||
'create_chart': ('.excel_tools', 'ExcelTools'),
|
||||
'add_chart_to_sheet': ('.excel_tools', 'ExcelTools'),
|
||||
'excel_tools': ('.excel_tools', 'ExcelTools'),
|
||||
|
||||
# XML Tools
|
||||
'read_xml': ('.xml_tools', 'XMLTools'),
|
||||
'write_xml': ('.xml_tools', 'XMLTools'),
|
||||
'transform_xml': ('.xml_tools', 'XMLTools'),
|
||||
'validate_xml': ('.xml_tools', 'XMLTools'),
|
||||
'xml_to_dict': ('.xml_tools', 'XMLTools'),
|
||||
'dict_to_xml': ('.xml_tools', 'XMLTools'),
|
||||
'xpath_query': ('.xml_tools', 'XMLTools'),
|
||||
'xml_tools': ('.xml_tools', 'XMLTools'),
|
||||
|
||||
# YAML Tools
|
||||
'read_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'write_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'merge_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'validate_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'analyze_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'transform_yaml': ('.yaml_tools', 'YAMLTools'),
|
||||
'yaml_tools': ('.yaml_tools', 'YAMLTools'),
|
||||
|
||||
# Python Tools
|
||||
'execute_code': ('.python_tools', 'PythonTools'),
|
||||
'analyze_code': ('.python_tools', 'PythonTools'),
|
||||
'format_code': ('.python_tools', 'PythonTools'),
|
||||
'lint_code': ('.python_tools', 'PythonTools'),
|
||||
'disassemble_code': ('.python_tools', 'PythonTools'),
|
||||
'python_tools': ('.python_tools', 'PythonTools'),
|
||||
|
||||
# Pandas Tools
|
||||
'filter_data': ('.pandas_tools', 'PandasTools'),
|
||||
'get_summary': ('.pandas_tools', 'PandasTools'),
|
||||
'group_by': ('.pandas_tools', 'PandasTools'),
|
||||
'pivot_table': ('.pandas_tools', 'PandasTools'),
|
||||
'pandas_tools': ('.pandas_tools', 'PandasTools'),
|
||||
}
|
||||
|
||||
_instances = {} # Cache for class instances
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Smart lazy loading of tools with class method support."""
|
||||
if name not in TOOL_MAPPINGS:
|
||||
raise AttributeError(f"module '{__package__}' has no attribute '{name}'")
|
||||
|
||||
module_path, class_name = TOOL_MAPPINGS[name]
|
||||
|
||||
if class_name is None:
|
||||
# Direct function import
|
||||
module = import_module(module_path, __package__)
|
||||
if name in [
|
||||
'duckduckgo', 'internet_search',
|
||||
'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',
|
||||
'scrape_page', 'extract_links', 'crawl', 'extract_text',
|
||||
'query', 'create_table', 'load_data', 'export_data', 'get_table_info', 'analyze_data',
|
||||
'execute_command', 'list_processes', 'kill_process', 'get_system_info',
|
||||
'evaluate', 'solve_equation', 'convert_units', 'calculate_statistics', 'calculate_financial'
|
||||
]:
|
||||
return getattr(module, name)
|
||||
if name in ['file_tools', 'pandas_tools', 'wikipedia_tools',
|
||||
'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools']:
|
||||
return module # Returns the callable module
|
||||
return getattr(module, name)
|
||||
else:
|
||||
# Class method import
|
||||
if class_name not in _instances:
|
||||
module = import_module(module_path, __package__)
|
||||
class_ = getattr(module, class_name)
|
||||
_instances[class_name] = class_()
|
||||
|
||||
# Get the method and bind it to the instance
|
||||
method = getattr(_instances[class_name], name)
|
||||
return method
|
||||
|
||||
__all__ = list(TOOL_MAPPINGS.keys())
|
||||
@@ -0,0 +1,292 @@
|
||||
"""Tools for searching and retrieving papers from arXiv.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import arxiv_tools
|
||||
papers = arxiv_tools.search("quantum computing")
|
||||
paper = arxiv_tools.get_paper("2401.00123")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import search_arxiv, get_arxiv_paper
|
||||
papers = search_arxiv("quantum computing")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import json
|
||||
|
||||
# Map sort criteria to arxiv.SortCriterion
|
||||
SORT_CRITERIA = {
|
||||
"relevance": "Relevance",
|
||||
"lastupdateddate": "LastUpdatedDate",
|
||||
"submitteddate": "SubmittedDate"
|
||||
}
|
||||
|
||||
# Map sort order to arxiv.SortOrder
|
||||
SORT_ORDER = {
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending"
|
||||
}
|
||||
|
||||
class ArxivTools:
|
||||
"""Tools for searching and retrieving papers from arXiv."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize ArxivTools and check for arxiv package."""
|
||||
self._check_arxiv()
|
||||
|
||||
def _check_arxiv(self):
|
||||
"""Check if arxiv package is installed."""
|
||||
if util.find_spec("arxiv") is None:
|
||||
raise ImportError("arxiv package is not available. Please install it using: pip install arxiv")
|
||||
global arxiv
|
||||
import arxiv
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "relevance",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Search arXiv for papers matching the query.
|
||||
|
||||
Args:
|
||||
query: Search query (e.g., "quantum computing", "author:Einstein")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results. If None, includes all.
|
||||
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"]
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
try:
|
||||
import arxiv
|
||||
|
||||
# Configure search client
|
||||
client = arxiv.Client()
|
||||
|
||||
# Map sort criteria
|
||||
sort_by_enum = getattr(arxiv.SortCriterion, SORT_CRITERIA[sort_by.lower()])
|
||||
sort_order_enum = getattr(arxiv.SortOrder, SORT_ORDER[sort_order.lower()])
|
||||
|
||||
# Build search query
|
||||
search = arxiv.Search(
|
||||
query=query,
|
||||
max_results=max_results,
|
||||
sort_by=sort_by_enum,
|
||||
sort_order=sort_order_enum
|
||||
)
|
||||
|
||||
# Execute search
|
||||
results = []
|
||||
for result in client.results(search):
|
||||
# Convert to dict with selected fields
|
||||
paper = self._result_to_dict(result, include_fields)
|
||||
results.append(paper)
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error searching arXiv: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_paper(
|
||||
self,
|
||||
paper_id: str,
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[Dict[str, Any], Dict[str, str]]:
|
||||
"""
|
||||
Get details of a specific paper by its arXiv ID.
|
||||
|
||||
Args:
|
||||
paper_id: arXiv paper ID (e.g., "2401.00123")
|
||||
include_fields: List of fields to include in results. If None, includes all.
|
||||
Available fields: ["title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"]
|
||||
|
||||
Returns:
|
||||
Dict: Paper details or error dict
|
||||
"""
|
||||
try:
|
||||
import arxiv
|
||||
|
||||
# Configure client
|
||||
client = arxiv.Client()
|
||||
|
||||
# Get paper by ID
|
||||
search = arxiv.Search(id_list=[paper_id])
|
||||
results = list(client.results(search))
|
||||
|
||||
if not results:
|
||||
return {"error": f"Paper with ID {paper_id} not found"}
|
||||
|
||||
# Convert to dict with selected fields
|
||||
paper = self._result_to_dict(results[0], include_fields)
|
||||
return paper
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting paper {paper_id}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_papers_by_author(
|
||||
self,
|
||||
author: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "submittedDate",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Get papers by a specific author.
|
||||
|
||||
Args:
|
||||
author: Author name (e.g., "Einstein")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
query = f'au:"{author}"'
|
||||
return self.search(query, max_results, sort_by, sort_order, include_fields)
|
||||
|
||||
def get_papers_by_category(
|
||||
self,
|
||||
category: str,
|
||||
max_results: int = 10,
|
||||
sort_by: str = "submittedDate",
|
||||
sort_order: str = "descending",
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Get papers from a specific category.
|
||||
|
||||
Args:
|
||||
category: arXiv category (e.g., "cs.AI", "physics.gen-ph")
|
||||
max_results: Maximum number of results to return
|
||||
sort_by: Sort results by ("relevance", "lastUpdatedDate", "submittedDate")
|
||||
sort_order: Sort order ("ascending" or "descending")
|
||||
include_fields: List of fields to include in results
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of papers or error dict
|
||||
"""
|
||||
query = f'cat:{category}'
|
||||
return self.search(query, max_results, sort_by, sort_order, include_fields)
|
||||
|
||||
def _result_to_dict(
|
||||
self,
|
||||
result: Any,
|
||||
include_fields: Optional[List[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Convert arxiv.Result to dictionary with selected fields."""
|
||||
# Default fields to include
|
||||
if include_fields is None:
|
||||
include_fields = [
|
||||
"title", "authors", "summary", "comment", "journal_ref",
|
||||
"doi", "primary_category", "categories", "links"
|
||||
]
|
||||
|
||||
# Build paper dict with selected fields
|
||||
paper = {}
|
||||
|
||||
# Always include these basic fields
|
||||
paper["arxiv_id"] = result.entry_id.split("/")[-1]
|
||||
paper["updated"] = result.updated.isoformat() if result.updated else None
|
||||
paper["published"] = result.published.isoformat() if result.published else None
|
||||
|
||||
# Add requested fields
|
||||
if "title" in include_fields:
|
||||
paper["title"] = result.title
|
||||
if "authors" in include_fields:
|
||||
paper["authors"] = [str(author) for author in result.authors]
|
||||
if "summary" in include_fields:
|
||||
paper["summary"] = result.summary
|
||||
if "comment" in include_fields:
|
||||
paper["comment"] = result.comment
|
||||
if "journal_ref" in include_fields:
|
||||
paper["journal_ref"] = result.journal_ref
|
||||
if "doi" in include_fields:
|
||||
paper["doi"] = result.doi
|
||||
if "primary_category" in include_fields:
|
||||
paper["primary_category"] = result.primary_category
|
||||
if "categories" in include_fields:
|
||||
paper["categories"] = result.categories
|
||||
if "links" in include_fields:
|
||||
paper["pdf_url"] = result.pdf_url
|
||||
paper["abstract_url"] = f"https://arxiv.org/abs/{paper['arxiv_id']}"
|
||||
|
||||
return paper
|
||||
|
||||
# Create instance for direct function access
|
||||
_arxiv_tools = ArxivTools()
|
||||
search_arxiv = _arxiv_tools.search
|
||||
get_arxiv_paper = _arxiv_tools.get_paper
|
||||
get_papers_by_author = _arxiv_tools.get_papers_by_author
|
||||
get_papers_by_category = _arxiv_tools.get_papers_by_category
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("ArxivTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Search for papers
|
||||
print("1. Searching for Papers")
|
||||
print("------------------------------")
|
||||
query = "quantum computing"
|
||||
papers = search_arxiv(query, max_results=3)
|
||||
print(f"Papers about {query}:")
|
||||
if isinstance(papers, list):
|
||||
print(json.dumps(papers, indent=2))
|
||||
else:
|
||||
print(papers) # Show error
|
||||
print()
|
||||
|
||||
# 2. Get specific paper
|
||||
print("2. Getting Specific Paper")
|
||||
print("------------------------------")
|
||||
if isinstance(papers, list) and papers:
|
||||
paper_id = papers[0]["arxiv_id"]
|
||||
paper = get_arxiv_paper(paper_id)
|
||||
print(f"Paper {paper_id}:")
|
||||
if "error" not in paper:
|
||||
print(json.dumps(paper, indent=2))
|
||||
else:
|
||||
print(paper) # Show error
|
||||
print()
|
||||
|
||||
# 3. Get papers by author
|
||||
print("3. Getting Papers by Author")
|
||||
print("------------------------------")
|
||||
author = "Yoshua Bengio"
|
||||
author_papers = get_papers_by_author(author, max_results=3)
|
||||
print(f"Papers by {author}:")
|
||||
if isinstance(author_papers, list):
|
||||
print(json.dumps(author_papers, indent=2))
|
||||
else:
|
||||
print(author_papers) # Show error
|
||||
print()
|
||||
|
||||
# 4. Get papers by category
|
||||
print("4. Getting Papers by Category")
|
||||
print("------------------------------")
|
||||
category = "cs.AI"
|
||||
category_papers = get_papers_by_category(category, max_results=3)
|
||||
print(f"Papers in category {category}:")
|
||||
if isinstance(category_papers, list):
|
||||
print(json.dumps(category_papers, indent=2))
|
||||
else:
|
||||
print(category_papers) # Show error
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,278 @@
|
||||
"""Tools for performing calculations.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import calculator_tools
|
||||
result = calculator_tools.evaluate("2 + 2 * 3")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import evaluate, solve_equation, convert_units
|
||||
result = evaluate("2 + 2 * 3")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Optional, Any
|
||||
from importlib import util
|
||||
import math
|
||||
|
||||
class CalculatorTools:
|
||||
"""Tools for performing calculations."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize CalculatorTools."""
|
||||
self._check_dependencies()
|
||||
|
||||
def _check_dependencies(self):
|
||||
"""Check if required packages are installed."""
|
||||
missing = []
|
||||
for package in ['sympy', 'pint']:
|
||||
if util.find_spec(package) is None:
|
||||
missing.append(package)
|
||||
|
||||
if missing:
|
||||
raise ImportError(
|
||||
f"Required packages not available. Please install: {', '.join(missing)}\n"
|
||||
f"Run: pip install {' '.join(missing)}"
|
||||
)
|
||||
|
||||
def evaluate(
|
||||
self,
|
||||
expression: str,
|
||||
variables: Optional[Dict[str, float]] = None,
|
||||
precision: int = 10
|
||||
) -> Dict[str, Any]:
|
||||
"""Evaluate a mathematical expression."""
|
||||
try:
|
||||
if util.find_spec('sympy') is None:
|
||||
error_msg = "sympy package is not available. Please install it using: pip install sympy"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Import sympy only when needed
|
||||
import sympy
|
||||
|
||||
# Replace common mathematical functions
|
||||
expression = expression.lower()
|
||||
replacements = {
|
||||
'sin(': 'math.sin(',
|
||||
'cos(': 'math.cos(',
|
||||
'tan(': 'math.tan(',
|
||||
'sqrt(': 'math.sqrt(',
|
||||
'log(': 'math.log(',
|
||||
'exp(': 'math.exp(',
|
||||
'pi': str(math.pi),
|
||||
'e': str(math.e)
|
||||
}
|
||||
for old, new in replacements.items():
|
||||
expression = expression.replace(old, new)
|
||||
|
||||
# Create safe namespace
|
||||
safe_dict = {
|
||||
'math': math,
|
||||
'__builtins__': None
|
||||
}
|
||||
if variables:
|
||||
safe_dict.update(variables)
|
||||
|
||||
# Evaluate expression
|
||||
result = eval(expression, safe_dict)
|
||||
|
||||
# Round to specified precision
|
||||
if isinstance(result, (int, float)):
|
||||
return {"result": round(float(result), precision)}
|
||||
return {"error": "Invalid expression"}
|
||||
except Exception as e:
|
||||
error_msg = f"Error evaluating expression: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def solve_equation(
|
||||
self,
|
||||
equation: str,
|
||||
variable: str = 'x'
|
||||
) -> Dict[str, Any]:
|
||||
"""Solve a mathematical equation."""
|
||||
try:
|
||||
if util.find_spec('sympy') is None:
|
||||
error_msg = "sympy package is not available. Please install it using: pip install sympy"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Import sympy only when needed
|
||||
import sympy
|
||||
|
||||
# Parse equation
|
||||
if '=' in equation:
|
||||
left, right = equation.split('=')
|
||||
equation = f"({left}) - ({right})"
|
||||
|
||||
# Convert to SymPy expression
|
||||
x = sympy.Symbol(variable)
|
||||
expr = sympy.sympify(equation)
|
||||
|
||||
# Solve equation
|
||||
solutions = sympy.solve(expr, x)
|
||||
|
||||
# Convert complex solutions to real if possible
|
||||
real_solutions = []
|
||||
for sol in solutions:
|
||||
if sol.is_real:
|
||||
real_solutions.append(float(sol))
|
||||
|
||||
return {"solutions": real_solutions} if real_solutions else {"error": "No real solutions found"}
|
||||
except Exception as e:
|
||||
error_msg = f"Error solving equation: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def convert_units(
|
||||
self,
|
||||
value: float,
|
||||
from_unit: str,
|
||||
to_unit: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Convert between different units."""
|
||||
try:
|
||||
if util.find_spec('pint') is None:
|
||||
error_msg = "pint package is not available. Please install it using: pip install pint"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Import pint only when needed
|
||||
import pint
|
||||
|
||||
# Create quantity with source unit
|
||||
ureg = pint.UnitRegistry()
|
||||
quantity = value * ureg(from_unit)
|
||||
|
||||
# Convert to target unit
|
||||
result = quantity.to(to_unit)
|
||||
|
||||
return {"result": float(result.magnitude)}
|
||||
except Exception as e:
|
||||
error_msg = f"Error converting units: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def calculate_statistics(
|
||||
self,
|
||||
numbers: List[float]
|
||||
) -> Dict[str, Any]:
|
||||
"""Calculate basic statistics for a list of numbers."""
|
||||
try:
|
||||
if not numbers:
|
||||
return {"error": "No numbers provided"}
|
||||
|
||||
n = len(numbers)
|
||||
mean = sum(numbers) / n
|
||||
|
||||
# Calculate variance and std dev
|
||||
squared_diff_sum = sum((x - mean) ** 2 for x in numbers)
|
||||
variance = squared_diff_sum / (n - 1) if n > 1 else 0
|
||||
std_dev = math.sqrt(variance)
|
||||
|
||||
# Calculate median
|
||||
sorted_nums = sorted(numbers)
|
||||
if n % 2 == 0:
|
||||
median = (sorted_nums[n//2 - 1] + sorted_nums[n//2]) / 2
|
||||
else:
|
||||
median = sorted_nums[n//2]
|
||||
|
||||
return {
|
||||
"mean": mean,
|
||||
"median": median,
|
||||
"std_dev": std_dev,
|
||||
"variance": variance,
|
||||
"min": min(numbers),
|
||||
"max": max(numbers),
|
||||
"range": max(numbers) - min(numbers),
|
||||
"count": n
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error calculating statistics: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def calculate_financial(
|
||||
self,
|
||||
principal: float,
|
||||
rate: float,
|
||||
time: float,
|
||||
frequency: str = 'yearly'
|
||||
) -> Dict[str, Any]:
|
||||
"""Calculate financial metrics like compound interest."""
|
||||
try:
|
||||
# Convert rate to decimal
|
||||
rate = rate / 100
|
||||
|
||||
# Get compounding frequency
|
||||
freq_map = {
|
||||
'yearly': 1,
|
||||
'semi-annual': 2,
|
||||
'quarterly': 4,
|
||||
'monthly': 12,
|
||||
'daily': 365
|
||||
}
|
||||
n = freq_map.get(frequency.lower(), 1)
|
||||
|
||||
# Calculate compound interest
|
||||
amount = principal * (1 + rate/n)**(n*time)
|
||||
interest = amount - principal
|
||||
|
||||
# Calculate simple interest
|
||||
simple_interest = principal * rate * time
|
||||
|
||||
return {
|
||||
"final_amount": amount,
|
||||
"compound_interest": interest,
|
||||
"simple_interest": simple_interest,
|
||||
"difference": interest - simple_interest
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error calculating financial metrics: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Create instance for direct function access
|
||||
_calculator_tools = CalculatorTools()
|
||||
evaluate = _calculator_tools.evaluate
|
||||
solve_equation = _calculator_tools.solve_equation
|
||||
convert_units = _calculator_tools.convert_units
|
||||
calculate_statistics = _calculator_tools.calculate_statistics
|
||||
calculate_financial = _calculator_tools.calculate_financial
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("CalculatorTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
print("1. Basic Arithmetic")
|
||||
print("------------------------------")
|
||||
print("2 + 2 =", evaluate("2 + 2")["result"])
|
||||
print("5 * 6 =", evaluate("5 * 6")["result"])
|
||||
print("10 / 3 =", evaluate("10 / 3")["result"])
|
||||
print()
|
||||
|
||||
print("2. Complex Expressions")
|
||||
print("------------------------------")
|
||||
print("(2 + 3) * 4 =", evaluate("(2 + 3) * 4")["result"])
|
||||
print("2^3 + 4 =", evaluate("2^3 + 4")["result"])
|
||||
print("sqrt(16) + 2 =", evaluate("sqrt(16) + 2")["result"])
|
||||
print()
|
||||
|
||||
print("3. Solving Equations")
|
||||
print("------------------------------")
|
||||
result = solve_equation("x^2 - 5*x + 6 = 0")
|
||||
print("x^2 - 5*x + 6 = 0")
|
||||
print("Solutions:", result["solutions"])
|
||||
print()
|
||||
|
||||
print("4. Unit Conversions")
|
||||
print("------------------------------")
|
||||
print("100 meters to kilometers:", convert_units(100, "meters", "kilometers")["result"])
|
||||
print("1 hour to minutes:", convert_units(1, "hours", "minutes")["result"])
|
||||
print("32 fahrenheit to celsius:", convert_units(32, "fahrenheit", "celsius")["result"])
|
||||
print()
|
||||
|
||||
print("==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,266 @@
|
||||
"""Tools for working with CSV files.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import csv_tools
|
||||
df = csv_tools.read_csv("data.csv")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_csv, write_csv, merge_csv
|
||||
df = read_csv("data.csv")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any, TYPE_CHECKING
|
||||
from importlib import util
|
||||
import json
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pandas as pd
|
||||
|
||||
class CSVTools:
|
||||
"""Tools for working with CSV files."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize CSVTools."""
|
||||
pass
|
||||
|
||||
def _get_pandas(self) -> Optional['pd']:
|
||||
"""Get pandas module, installing if needed"""
|
||||
if util.find_spec('pandas') is None:
|
||||
error_msg = "pandas package is not available. Please install it using: pip install pandas"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import pandas as pd
|
||||
return pd
|
||||
|
||||
def read_csv(
|
||||
self,
|
||||
filepath: str,
|
||||
encoding: str = 'utf-8',
|
||||
delimiter: str = ',',
|
||||
header: Union[int, List[int], None] = 0,
|
||||
usecols: Optional[List[str]] = None,
|
||||
dtype: Optional[Dict[str, str]] = None,
|
||||
parse_dates: Optional[List[str]] = None,
|
||||
na_values: Optional[List[str]] = None,
|
||||
nrows: Optional[int] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Read a CSV file with advanced options.
|
||||
|
||||
Args:
|
||||
filepath: Path to CSV file
|
||||
encoding: File encoding
|
||||
delimiter: Column delimiter
|
||||
header: Row number(s) to use as column names
|
||||
usecols: Columns to read
|
||||
dtype: Dict of column dtypes
|
||||
parse_dates: List of columns to parse as dates
|
||||
na_values: Additional strings to recognize as NA/NaN
|
||||
nrows: Number of rows to read
|
||||
|
||||
Returns:
|
||||
List of row dicts
|
||||
"""
|
||||
try:
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return {"error": "pandas package not available"}
|
||||
|
||||
df = pd.read_csv(
|
||||
filepath,
|
||||
encoding=encoding,
|
||||
delimiter=delimiter,
|
||||
header=header,
|
||||
usecols=usecols,
|
||||
dtype=dtype,
|
||||
parse_dates=parse_dates,
|
||||
na_values=na_values,
|
||||
nrows=nrows
|
||||
)
|
||||
return df.to_dict('records')
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def write_csv(
|
||||
self,
|
||||
filepath: str,
|
||||
data: List[Dict[str, Any]],
|
||||
encoding: str = 'utf-8',
|
||||
delimiter: str = ',',
|
||||
index: bool = False,
|
||||
header: bool = True,
|
||||
float_format: Optional[str] = None,
|
||||
date_format: Optional[str] = None,
|
||||
mode: str = 'w'
|
||||
) -> bool:
|
||||
"""Write data to a CSV file.
|
||||
|
||||
Args:
|
||||
filepath: Path to CSV file
|
||||
data: List of row dicts to write
|
||||
encoding: File encoding
|
||||
delimiter: Column delimiter
|
||||
index: Whether to write row indices
|
||||
header: Whether to write column headers
|
||||
float_format: Format string for float values
|
||||
date_format: Format string for date values
|
||||
mode: Write mode ('w' for write, 'a' for append)
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return False
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
df.to_csv(
|
||||
filepath,
|
||||
encoding=encoding,
|
||||
sep=delimiter,
|
||||
index=index,
|
||||
header=header,
|
||||
float_format=float_format,
|
||||
date_format=date_format,
|
||||
mode=mode
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def merge_csv(
|
||||
self,
|
||||
files: List[str],
|
||||
output_file: str,
|
||||
how: str = 'inner',
|
||||
on: Optional[Union[str, List[str]]] = None,
|
||||
suffixes: Optional[tuple] = None
|
||||
) -> bool:
|
||||
"""Merge multiple CSV files.
|
||||
|
||||
Args:
|
||||
files: List of CSV files to merge
|
||||
output_file: Output file path
|
||||
how: Merge method ('inner', 'outer', 'left', 'right')
|
||||
on: Column(s) to merge on
|
||||
suffixes: Suffixes for overlapping columns
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
if len(files) < 2:
|
||||
error_msg = "At least two files are required for merging"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return False
|
||||
|
||||
# Read first file
|
||||
result = pd.read_csv(files[0])
|
||||
|
||||
# Merge with remaining files
|
||||
for file in files[1:]:
|
||||
df = pd.read_csv(file)
|
||||
result = pd.merge(
|
||||
result,
|
||||
df,
|
||||
how=how,
|
||||
on=on,
|
||||
suffixes=suffixes or ('_1', '_2')
|
||||
)
|
||||
|
||||
# Write merged result
|
||||
result.to_csv(output_file, index=False)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error merging CSV files: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Create instance for direct function access
|
||||
_csv_tools = CSVTools()
|
||||
read_csv = _csv_tools.read_csv
|
||||
write_csv = _csv_tools.write_csv
|
||||
merge_csv = _csv_tools.merge_csv
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("CSVTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create a temporary file for testing
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp:
|
||||
temp_file = temp.name
|
||||
|
||||
print("1. Writing CSV File")
|
||||
print("------------------------------")
|
||||
data = [
|
||||
{"name": "Alice", "age": 25, "city": "New York"},
|
||||
{"name": "Bob", "age": 30, "city": "San Francisco"},
|
||||
{"name": "Charlie", "age": 35, "city": "Chicago"}
|
||||
]
|
||||
result = write_csv(temp_file, data)
|
||||
print(f"Data written successfully: {result}")
|
||||
print()
|
||||
|
||||
print("2. Reading CSV File")
|
||||
print("------------------------------")
|
||||
read_data = read_csv(temp_file)
|
||||
print("Contents of CSV file:")
|
||||
for row in read_data:
|
||||
print(row)
|
||||
print()
|
||||
|
||||
print("3. Merging CSV Files")
|
||||
print("------------------------------")
|
||||
# Create a second file for merging
|
||||
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp2:
|
||||
temp_file2 = temp2.name
|
||||
data2 = [
|
||||
{"name": "Alice", "salary": 75000},
|
||||
{"name": "Bob", "salary": 85000},
|
||||
{"name": "Charlie", "salary": 95000}
|
||||
]
|
||||
write_csv(temp_file2, data2)
|
||||
|
||||
# Merge files
|
||||
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp3:
|
||||
temp_file3 = temp3.name
|
||||
result = merge_csv(
|
||||
[temp_file, temp_file2],
|
||||
temp_file3,
|
||||
how='inner',
|
||||
on='name'
|
||||
)
|
||||
print(f"Files merged successfully: {result}")
|
||||
if result:
|
||||
merged_data = read_csv(temp_file3)
|
||||
print("\nMerged contents:")
|
||||
for row in merged_data:
|
||||
print(row)
|
||||
print()
|
||||
|
||||
# Clean up temporary files
|
||||
os.unlink(temp_file)
|
||||
os.unlink(temp_file2)
|
||||
os.unlink(temp_file3)
|
||||
|
||||
print("==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================\n")
|
||||
@@ -0,0 +1,268 @@
|
||||
"""Tools for working with DuckDB databases.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import duckdb_tools
|
||||
df = duckdb_tools.query("SELECT * FROM my_table")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import query_db, create_table, load_data
|
||||
df = query_db("SELECT * FROM my_table")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional, Union, TYPE_CHECKING
|
||||
from importlib import util
|
||||
import json
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import duckdb
|
||||
import pandas as pd
|
||||
|
||||
class DuckDBTools:
|
||||
"""Tools for working with DuckDB databases."""
|
||||
|
||||
def __init__(self, database: str = ':memory:'):
|
||||
"""Initialize DuckDBTools.
|
||||
|
||||
Args:
|
||||
database: Path to database file or ':memory:' for in-memory database
|
||||
"""
|
||||
self.database = database
|
||||
self._conn = None
|
||||
|
||||
def _get_duckdb(self) -> Optional['duckdb']:
|
||||
"""Get duckdb module, installing if needed"""
|
||||
if util.find_spec('duckdb') is None:
|
||||
error_msg = "duckdb package is not available. Please install it using: pip install duckdb"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import duckdb
|
||||
return duckdb
|
||||
|
||||
def _get_pandas(self) -> Optional['pd']:
|
||||
"""Get pandas module, installing if needed"""
|
||||
if util.find_spec('pandas') is None:
|
||||
error_msg = "pandas package is not available. Please install it using: pip install pandas"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import pandas as pd
|
||||
return pd
|
||||
|
||||
def _get_connection(self) -> Optional['duckdb.DuckDBPyConnection']:
|
||||
"""Get or create database connection"""
|
||||
if self._conn is None:
|
||||
duckdb = self._get_duckdb()
|
||||
if duckdb is None:
|
||||
return None
|
||||
try:
|
||||
self._conn = duckdb.connect(self.database)
|
||||
except Exception as e:
|
||||
error_msg = f"Error connecting to database {self.database}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
return self._conn
|
||||
|
||||
def execute_query(
|
||||
self,
|
||||
query: str,
|
||||
params: Optional[Union[tuple, dict]] = None,
|
||||
return_df: bool = True
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""Execute a SQL query.
|
||||
|
||||
Args:
|
||||
query: SQL query to execute
|
||||
params: Query parameters
|
||||
return_df: If True, return results as DataFrame records
|
||||
|
||||
Returns:
|
||||
Query results as list of dicts, or error dict
|
||||
"""
|
||||
try:
|
||||
conn = self._get_connection()
|
||||
if conn is None:
|
||||
return {"error": "Could not connect to database"}
|
||||
|
||||
if params:
|
||||
result = conn.execute(query, params)
|
||||
else:
|
||||
result = conn.execute(query)
|
||||
|
||||
if return_df:
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return {"error": "pandas package not available"}
|
||||
df = result.df()
|
||||
return df.to_dict('records')
|
||||
else:
|
||||
return [dict(row) for row in result.fetchall()]
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing query: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def load_csv(
|
||||
self,
|
||||
table_name: str,
|
||||
filepath: str,
|
||||
schema: Optional[Dict[str, str]] = None,
|
||||
if_exists: str = 'replace'
|
||||
) -> bool:
|
||||
"""Load a CSV file into a table.
|
||||
|
||||
Args:
|
||||
table_name: Name of table to create
|
||||
filepath: Path to CSV file
|
||||
schema: Optional column definitions
|
||||
if_exists: What to do if table exists ('fail', 'replace', 'append')
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
conn = self._get_connection()
|
||||
if conn is None:
|
||||
return False
|
||||
|
||||
# Check if table exists
|
||||
exists = conn.execute(f"""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='{table_name}'
|
||||
""").fetchone() is not None
|
||||
|
||||
if exists:
|
||||
if if_exists == 'fail':
|
||||
raise ValueError(f"Table {table_name} already exists")
|
||||
elif if_exists == 'replace':
|
||||
conn.execute(f"DROP TABLE IF EXISTS {table_name}")
|
||||
elif if_exists != 'append':
|
||||
raise ValueError("if_exists must be 'fail', 'replace', or 'append'")
|
||||
|
||||
# Create table if needed
|
||||
if not exists or if_exists == 'replace':
|
||||
if schema:
|
||||
# Create table with schema
|
||||
columns = ', '.join(f"{k} {v}" for k, v in schema.items())
|
||||
conn.execute(f"CREATE TABLE {table_name} ({columns})")
|
||||
else:
|
||||
# Infer schema from CSV
|
||||
conn.execute(f"""
|
||||
CREATE TABLE {table_name} AS
|
||||
SELECT * FROM read_csv_auto('{filepath}')
|
||||
WHERE 1=0
|
||||
""")
|
||||
|
||||
# Load data
|
||||
conn.execute(f"""
|
||||
INSERT INTO {table_name}
|
||||
SELECT * FROM read_csv_auto('{filepath}')
|
||||
""")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error loading CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def export_csv(
|
||||
self,
|
||||
query: str,
|
||||
filepath: str,
|
||||
params: Optional[Union[tuple, dict]] = None
|
||||
) -> bool:
|
||||
"""Export query results to CSV.
|
||||
|
||||
Args:
|
||||
query: SQL query to execute
|
||||
filepath: Output file path
|
||||
params: Optional query parameters
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
# Execute query and get results as DataFrame
|
||||
results = self.execute_query(query, params)
|
||||
if isinstance(results, dict) and 'error' in results:
|
||||
return False
|
||||
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return False
|
||||
|
||||
# Convert to DataFrame and save
|
||||
df = pd.DataFrame(results)
|
||||
df.to_csv(filepath, index=False)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error exporting to CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Close database connection."""
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
|
||||
# Create instance for direct function access
|
||||
_duckdb_tools = DuckDBTools()
|
||||
execute_query = _duckdb_tools.execute_query
|
||||
load_csv = _duckdb_tools.load_csv
|
||||
export_csv = _duckdb_tools.export_csv
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("DuckDBTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create a temporary file for testing
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp:
|
||||
temp_file = temp.name
|
||||
|
||||
# Create sample data
|
||||
with open(temp_file, 'w') as f:
|
||||
f.write("name,age,city\n")
|
||||
f.write("Alice,25,New York\n")
|
||||
f.write("Bob,30,San Francisco\n")
|
||||
f.write("Charlie,35,Chicago\n")
|
||||
|
||||
print("1. Loading CSV File")
|
||||
print("------------------------------")
|
||||
result = load_csv('users', temp_file)
|
||||
print(f"CSV loaded successfully: {result}")
|
||||
print()
|
||||
|
||||
print("2. Executing Query")
|
||||
print("------------------------------")
|
||||
query = "SELECT * FROM users WHERE age > 25"
|
||||
results = execute_query(query)
|
||||
print("Query results:")
|
||||
for row in results:
|
||||
print(row)
|
||||
print()
|
||||
|
||||
print("3. Exporting Query Results")
|
||||
print("------------------------------")
|
||||
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False) as temp2:
|
||||
temp_file2 = temp2.name
|
||||
result = export_csv(query, temp_file2)
|
||||
print(f"Results exported successfully: {result}")
|
||||
if result:
|
||||
print("\nExported file contents:")
|
||||
with open(temp_file2) as f:
|
||||
print(f.read())
|
||||
|
||||
# Clean up temporary files
|
||||
os.unlink(temp_file)
|
||||
os.unlink(temp_file2)
|
||||
|
||||
print("==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================\n")
|
||||
@@ -0,0 +1,52 @@
|
||||
"""DuckDuckGo search functionality.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import internet_search
|
||||
results = internet_search("AI news")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
results = duckduckgo("AI news")
|
||||
"""
|
||||
|
||||
from typing import List, Dict
|
||||
import logging
|
||||
from importlib import util
|
||||
|
||||
def internet_search(query: str) -> List[Dict]:
|
||||
"""Perform an internet search using DuckDuckGo."""
|
||||
# Check if duckduckgo_search is installed
|
||||
if util.find_spec("duckduckgo_search") is None:
|
||||
error_msg = "DuckDuckGo search is not available. Please install duckduckgo_search package using: pip install duckduckgo_search"
|
||||
logging.error(error_msg)
|
||||
return [{"error": error_msg}]
|
||||
|
||||
try:
|
||||
# Import only when needed
|
||||
from duckduckgo_search import DDGS
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error during DuckDuckGo search: {e}"
|
||||
logging.error(error_msg)
|
||||
return [{"error": error_msg}]
|
||||
|
||||
def duckduckgo(query: str) -> List[Dict]:
|
||||
"""Alias for internet_search function."""
|
||||
return internet_search(query)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
results = internet_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')}")
|
||||
@@ -0,0 +1,310 @@
|
||||
"""Tools for working with Excel files.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import excel_tools
|
||||
df = excel_tools.read_excel("data.xlsx")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_excel, write_excel, merge_excel
|
||||
df = read_excel("data.xlsx")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any, TYPE_CHECKING, Tuple
|
||||
from importlib import util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pandas as pd
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
|
||||
from openpyxl.chart.label import DataLabelList
|
||||
|
||||
class ExcelTools:
|
||||
"""Tools for working with Excel files."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize ExcelTools."""
|
||||
pass
|
||||
|
||||
def _get_pandas(self) -> Optional['pd']:
|
||||
"""Get pandas module, installing if needed"""
|
||||
if util.find_spec('pandas') is None:
|
||||
error_msg = "pandas package is not available. Please install it using: pip install pandas"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
if util.find_spec('openpyxl') is None:
|
||||
error_msg = "openpyxl package is not available. Please install it using: pip install openpyxl"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import pandas as pd
|
||||
return pd
|
||||
|
||||
def read_excel(
|
||||
self,
|
||||
filepath: str,
|
||||
sheet_name: Optional[Union[str, int, List[Union[str, int]]]] = 0,
|
||||
header: Optional[int] = 0,
|
||||
usecols: Optional[List[str]] = None,
|
||||
skiprows: Optional[Union[int, List[int]]] = None,
|
||||
na_values: Optional[List[str]] = None,
|
||||
dtype: Optional[Dict[str, str]] = None
|
||||
) -> Union[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]]:
|
||||
"""Read an Excel file with advanced options.
|
||||
|
||||
Args:
|
||||
filepath: Path to Excel file
|
||||
sheet_name: Sheet name(s) or index(es)
|
||||
header: Row number(s) to use as column names
|
||||
usecols: Columns to read
|
||||
skiprows: Line numbers to skip
|
||||
na_values: Additional strings to recognize as NA/NaN
|
||||
dtype: Dict of column dtypes
|
||||
|
||||
Returns:
|
||||
Dict of sheet names to data if multiple sheets, else list of row dicts
|
||||
"""
|
||||
try:
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return {"error": "Required packages not available"}
|
||||
|
||||
# Read Excel file
|
||||
df = pd.read_excel(
|
||||
filepath,
|
||||
sheet_name=sheet_name,
|
||||
header=header,
|
||||
usecols=usecols,
|
||||
skiprows=skiprows,
|
||||
na_values=na_values,
|
||||
dtype=dtype,
|
||||
engine='openpyxl'
|
||||
)
|
||||
|
||||
# Convert to dict format
|
||||
if isinstance(df, dict):
|
||||
return {
|
||||
name: df[name].to_dict('records')
|
||||
for name in df.keys()
|
||||
}
|
||||
else:
|
||||
return df.to_dict('records')
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading Excel file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def write_excel(
|
||||
self,
|
||||
filepath: str,
|
||||
data: Union[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]],
|
||||
sheet_name: Optional[str] = None,
|
||||
index: bool = False,
|
||||
header: bool = True,
|
||||
mode: str = 'w'
|
||||
) -> bool:
|
||||
"""Write data to an Excel file.
|
||||
|
||||
Args:
|
||||
filepath: Path to Excel file
|
||||
data: Data to write (dict of sheet names to data or list of row dicts)
|
||||
sheet_name: Sheet name if data is a list
|
||||
index: Whether to write row indices
|
||||
header: Whether to write column headers
|
||||
mode: Write mode ('w' for write, 'a' for append)
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return False
|
||||
|
||||
# Convert data to DataFrame(s)
|
||||
if isinstance(data, dict):
|
||||
if mode == 'a' and os.path.exists(filepath):
|
||||
book = pd.ExcelFile(filepath)
|
||||
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
|
||||
# Copy existing sheets
|
||||
for sheet in book.sheet_names:
|
||||
pd.read_excel(filepath, sheet_name=sheet).to_excel(
|
||||
writer, sheet_name=sheet, index=index, header=header
|
||||
)
|
||||
# Add new sheets
|
||||
for name, sheet_data in data.items():
|
||||
df = pd.DataFrame(sheet_data)
|
||||
df.to_excel(
|
||||
writer, sheet_name=name, index=index, header=header
|
||||
)
|
||||
else:
|
||||
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
|
||||
for name, sheet_data in data.items():
|
||||
df = pd.DataFrame(sheet_data)
|
||||
df.to_excel(
|
||||
writer, sheet_name=name, index=index, header=header
|
||||
)
|
||||
else:
|
||||
if mode == 'a' and os.path.exists(filepath):
|
||||
book = pd.ExcelFile(filepath)
|
||||
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
|
||||
# Copy existing sheets
|
||||
for sheet in book.sheet_names:
|
||||
pd.read_excel(filepath, sheet_name=sheet).to_excel(
|
||||
writer, sheet_name=sheet, index=index, header=header
|
||||
)
|
||||
# Add new sheet
|
||||
df = pd.DataFrame(data)
|
||||
df.to_excel(
|
||||
writer,
|
||||
sheet_name=sheet_name or 'Sheet1',
|
||||
index=index,
|
||||
header=header
|
||||
)
|
||||
else:
|
||||
df = pd.DataFrame(data)
|
||||
df.to_excel(
|
||||
filepath,
|
||||
sheet_name=sheet_name or 'Sheet1',
|
||||
index=index,
|
||||
header=header,
|
||||
engine='openpyxl'
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing Excel file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def merge_excel(
|
||||
self,
|
||||
files: List[str],
|
||||
output_file: str,
|
||||
how: str = 'inner',
|
||||
on: Optional[Union[str, List[str]]] = None,
|
||||
suffixes: Optional[Tuple[str, str]] = None
|
||||
) -> bool:
|
||||
"""Merge multiple Excel files.
|
||||
|
||||
Args:
|
||||
files: List of Excel files to merge
|
||||
output_file: Output file path
|
||||
how: Merge method ('inner', 'outer', 'left', 'right')
|
||||
on: Column(s) to merge on
|
||||
suffixes: Suffixes for overlapping columns
|
||||
|
||||
Returns:
|
||||
bool: Success status
|
||||
"""
|
||||
try:
|
||||
if len(files) < 2:
|
||||
error_msg = "At least two files are required for merging"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Read first file
|
||||
result = self.read_excel(files[0])
|
||||
|
||||
# Merge with remaining files
|
||||
for file in files[1:]:
|
||||
df = self.read_excel(file)
|
||||
if isinstance(df, dict):
|
||||
df = next(iter(df.values()))
|
||||
|
||||
pd = self._get_pandas()
|
||||
if pd is None:
|
||||
return False
|
||||
|
||||
result = pd.merge(
|
||||
pd.DataFrame(result),
|
||||
pd.DataFrame(df),
|
||||
how=how,
|
||||
on=on,
|
||||
suffixes=suffixes or ('_1', '_2')
|
||||
).to_dict('records')
|
||||
|
||||
# Write merged result
|
||||
return self.write_excel(output_file, result)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error merging Excel files: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Create instance for direct function access
|
||||
_excel_tools = ExcelTools()
|
||||
read_excel = _excel_tools.read_excel
|
||||
write_excel = _excel_tools.write_excel
|
||||
merge_excel = _excel_tools.merge_excel
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("ExcelTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create a temporary file for testing
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp:
|
||||
temp_file = temp.name
|
||||
|
||||
print("1. Writing Excel File")
|
||||
print("------------------------------")
|
||||
data = [
|
||||
{"name": "Alice", "age": 25, "city": "New York"},
|
||||
{"name": "Bob", "age": 30, "city": "San Francisco"},
|
||||
{"name": "Charlie", "age": 35, "city": "Chicago"}
|
||||
]
|
||||
result = write_excel(temp_file, data, "People")
|
||||
print(f"Data written successfully: {result}")
|
||||
print()
|
||||
|
||||
print("2. Reading Excel File")
|
||||
print("------------------------------")
|
||||
read_data = read_excel(temp_file)
|
||||
print("Contents of Excel file:")
|
||||
for row in read_data:
|
||||
print(row)
|
||||
print()
|
||||
|
||||
print("3. Merging Excel Files")
|
||||
print("------------------------------")
|
||||
# Create a second file for merging
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp2:
|
||||
temp_file2 = temp2.name
|
||||
data2 = [
|
||||
{"name": "Alice", "salary": 75000},
|
||||
{"name": "Bob", "salary": 85000},
|
||||
{"name": "Charlie", "salary": 95000}
|
||||
]
|
||||
write_excel(temp_file2, data2, "Salaries")
|
||||
|
||||
# Merge files
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp3:
|
||||
temp_file3 = temp3.name
|
||||
result = merge_excel(
|
||||
[temp_file, temp_file2],
|
||||
temp_file3,
|
||||
how='inner',
|
||||
on='name'
|
||||
)
|
||||
print(f"Files merged successfully: {result}")
|
||||
if result:
|
||||
merged_data = read_excel(temp_file3)
|
||||
print("\nMerged contents:")
|
||||
for row in merged_data:
|
||||
print(row)
|
||||
print()
|
||||
|
||||
# Clean up temporary files
|
||||
os.unlink(temp_file)
|
||||
os.unlink(temp_file2)
|
||||
os.unlink(temp_file3)
|
||||
|
||||
print("==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================\n")
|
||||
@@ -0,0 +1,274 @@
|
||||
"""File handling tools for basic file operations.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import file_tools
|
||||
content = file_tools.read_file("example.txt")
|
||||
file_tools.write_file("output.txt", "Hello World")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_file, write_file, list_files
|
||||
content = read_file("example.txt")
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from typing import List, Dict, Union, Optional
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
class FileTools:
|
||||
"""Tools for file operations including read, write, list, and information."""
|
||||
|
||||
@staticmethod
|
||||
def read_file(filepath: str, encoding: str = 'utf-8') -> str:
|
||||
"""
|
||||
Read content from a file.
|
||||
|
||||
Args:
|
||||
filepath: Path to the file
|
||||
encoding: File encoding (default: utf-8)
|
||||
|
||||
Returns:
|
||||
str: Content of the file
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'r', encoding=encoding) as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return error_msg
|
||||
|
||||
@staticmethod
|
||||
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> bool:
|
||||
"""
|
||||
Write content to a file.
|
||||
|
||||
Args:
|
||||
filepath: Path to the file
|
||||
content: Content to write
|
||||
encoding: File encoding (default: utf-8)
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Create directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w', encoding=encoding) as f:
|
||||
f.write(content)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing to file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def list_files(directory: str, pattern: Optional[str] = None) -> List[Dict[str, Union[str, int]]]:
|
||||
"""
|
||||
List files in a directory with optional pattern matching.
|
||||
|
||||
Args:
|
||||
directory: Directory path
|
||||
pattern: Optional glob pattern (e.g., "*.txt")
|
||||
|
||||
Returns:
|
||||
List[Dict]: List of file information dictionaries
|
||||
"""
|
||||
try:
|
||||
path = Path(directory)
|
||||
if pattern:
|
||||
files = path.glob(pattern)
|
||||
else:
|
||||
files = path.iterdir()
|
||||
|
||||
result = []
|
||||
for file in files:
|
||||
if file.is_file():
|
||||
stat = file.stat()
|
||||
result.append({
|
||||
'name': file.name,
|
||||
'path': str(file),
|
||||
'size': stat.st_size,
|
||||
'modified': stat.st_mtime,
|
||||
'created': stat.st_ctime
|
||||
})
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error listing files in {directory}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return [{'error': error_msg}]
|
||||
|
||||
@staticmethod
|
||||
def get_file_info(filepath: str) -> Dict[str, Union[str, int]]:
|
||||
"""
|
||||
Get detailed information about a file.
|
||||
|
||||
Args:
|
||||
filepath: Path to the file
|
||||
|
||||
Returns:
|
||||
Dict: File information including size, dates, etc.
|
||||
"""
|
||||
try:
|
||||
path = Path(filepath)
|
||||
if not path.exists():
|
||||
return {'error': f'File not found: {filepath}'}
|
||||
|
||||
stat = path.stat()
|
||||
return {
|
||||
'name': path.name,
|
||||
'path': str(path),
|
||||
'size': stat.st_size,
|
||||
'modified': stat.st_mtime,
|
||||
'created': stat.st_ctime,
|
||||
'is_file': path.is_file(),
|
||||
'is_dir': path.is_dir(),
|
||||
'extension': path.suffix,
|
||||
'parent': str(path.parent)
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting file info for {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {'error': error_msg}
|
||||
|
||||
@staticmethod
|
||||
def copy_file(src: str, dst: str) -> bool:
|
||||
"""
|
||||
Copy a file from source to destination.
|
||||
|
||||
Args:
|
||||
src: Source file path
|
||||
dst: Destination file path
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Create destination directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error copying file from {src} to {dst}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def move_file(src: str, dst: str) -> bool:
|
||||
"""
|
||||
Move a file from source to destination.
|
||||
|
||||
Args:
|
||||
src: Source file path
|
||||
dst: Destination file path
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Create destination directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
shutil.move(src, dst)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error moving file from {src} to {dst}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def delete_file(filepath: str) -> bool:
|
||||
"""
|
||||
Delete a file.
|
||||
|
||||
Args:
|
||||
filepath: Path to the file
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
os.remove(filepath)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error deleting file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Create instance for direct function access
|
||||
_file_tools = FileTools()
|
||||
read_file = _file_tools.read_file
|
||||
write_file = _file_tools.write_file
|
||||
list_files = _file_tools.list_files
|
||||
get_file_info = _file_tools.get_file_info
|
||||
copy_file = _file_tools.copy_file
|
||||
move_file = _file_tools.move_file
|
||||
delete_file = _file_tools.delete_file
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("FileTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create a test directory
|
||||
test_dir = os.path.join(os.getcwd(), "test_files")
|
||||
os.makedirs(test_dir, exist_ok=True)
|
||||
|
||||
# Create test files
|
||||
test_file = os.path.join(test_dir, "test_file.txt")
|
||||
test_content = "Hello, this is a test file!"
|
||||
|
||||
print("1. Writing to file")
|
||||
print("------------------------------")
|
||||
success = write_file(test_file, test_content)
|
||||
print(f"Write successful: {success}\n")
|
||||
|
||||
print("2. Reading from file")
|
||||
print("------------------------------")
|
||||
content = read_file(test_file)
|
||||
print(f"Content: {content}\n")
|
||||
|
||||
print("3. File Information")
|
||||
print("------------------------------")
|
||||
info = get_file_info(test_file)
|
||||
print(json.dumps(info, indent=2))
|
||||
print()
|
||||
|
||||
print("4. Listing Files")
|
||||
print("------------------------------")
|
||||
files = list_files(test_dir, "*.txt")
|
||||
for file in files:
|
||||
print(f"Found: {file['name']} ({file['size']} bytes)")
|
||||
print()
|
||||
|
||||
print("5. Copying File")
|
||||
print("------------------------------")
|
||||
copy_file_path = os.path.join(test_dir, "test_file_copy.txt")
|
||||
copy_success = copy_file(test_file, copy_file_path)
|
||||
print(f"Copy successful: {copy_success}\n")
|
||||
|
||||
print("6. Moving File")
|
||||
print("------------------------------")
|
||||
move_file_path = os.path.join(test_dir, "test_file_moved.txt")
|
||||
move_success = move_file(copy_file_path, move_file_path)
|
||||
print(f"Move successful: {move_success}\n")
|
||||
|
||||
print("7. Deleting Files")
|
||||
print("------------------------------")
|
||||
delete_success = delete_file(test_file)
|
||||
print(f"Delete original successful: {delete_success}")
|
||||
delete_success = delete_file(move_file_path)
|
||||
print(f"Delete moved file successful: {delete_success}\n")
|
||||
|
||||
# Clean up test directory
|
||||
try:
|
||||
shutil.rmtree(test_dir)
|
||||
print("Test directory cleaned up successfully")
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up test directory: {str(e)}")
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,515 @@
|
||||
"""Tools for working with JSON files.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import json_tools
|
||||
data = json_tools.read_json("data.json")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_json, write_json, merge_json
|
||||
data = read_json("data.json")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any, Tuple
|
||||
from importlib import util
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
class JSONTools:
|
||||
"""Tools for working with JSON files."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize JSONTools."""
|
||||
pass
|
||||
|
||||
def read_json(
|
||||
self,
|
||||
filepath: str,
|
||||
encoding: str = 'utf-8',
|
||||
validate_schema: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Read a JSON file with optional schema validation.
|
||||
|
||||
Args:
|
||||
filepath: Path to JSON file
|
||||
encoding: File encoding
|
||||
validate_schema: Optional JSON schema for validation
|
||||
|
||||
Returns:
|
||||
Dict with JSON data
|
||||
"""
|
||||
try:
|
||||
# Read JSON file
|
||||
with open(filepath, 'r', encoding=encoding) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Validate against schema if provided
|
||||
if validate_schema:
|
||||
if util.find_spec('jsonschema') is None:
|
||||
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
import jsonschema
|
||||
try:
|
||||
jsonschema.validate(instance=data, schema=validate_schema)
|
||||
except jsonschema.exceptions.ValidationError as e:
|
||||
error_msg = f"JSON validation failed: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading JSON file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def write_json(
|
||||
self,
|
||||
data: Union[Dict[str, Any], List[Any]],
|
||||
filepath: str,
|
||||
encoding: str = 'utf-8',
|
||||
indent: int = 2,
|
||||
sort_keys: bool = False,
|
||||
ensure_ascii: bool = False
|
||||
) -> bool:
|
||||
"""Write data to a JSON file.
|
||||
|
||||
Args:
|
||||
data: Data to write
|
||||
filepath: Output file path
|
||||
encoding: File encoding
|
||||
indent: Number of spaces for indentation
|
||||
sort_keys: Sort dictionary keys
|
||||
ensure_ascii: Escape non-ASCII characters
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
with open(filepath, 'w', encoding=encoding) as f:
|
||||
json.dump(
|
||||
data,
|
||||
f,
|
||||
indent=indent,
|
||||
sort_keys=sort_keys,
|
||||
ensure_ascii=ensure_ascii
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing JSON file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def merge_json(
|
||||
self,
|
||||
files: List[str],
|
||||
output_file: str,
|
||||
merge_arrays: bool = True,
|
||||
overwrite_duplicates: bool = True
|
||||
) -> bool:
|
||||
"""Merge multiple JSON files.
|
||||
|
||||
Args:
|
||||
files: List of JSON files to merge
|
||||
output_file: Output file path
|
||||
merge_arrays: Merge arrays instead of overwriting
|
||||
overwrite_duplicates: Overwrite duplicate keys
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if len(files) < 2:
|
||||
raise ValueError("At least two files are required for merging")
|
||||
|
||||
# Read first file
|
||||
result = self.read_json(files[0])
|
||||
|
||||
# Merge with remaining files
|
||||
for file in files[1:]:
|
||||
data = self.read_json(file)
|
||||
result = self._deep_merge(
|
||||
result,
|
||||
data,
|
||||
merge_arrays=merge_arrays,
|
||||
overwrite_duplicates=overwrite_duplicates
|
||||
)
|
||||
|
||||
# Write merged result
|
||||
return self.write_json(result, output_file)
|
||||
except Exception as e:
|
||||
error_msg = f"Error merging JSON files: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def _deep_merge(
|
||||
self,
|
||||
dict1: Dict[str, Any],
|
||||
dict2: Dict[str, Any],
|
||||
merge_arrays: bool = True,
|
||||
overwrite_duplicates: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""Deep merge two dictionaries.
|
||||
|
||||
Args:
|
||||
dict1: First dictionary
|
||||
dict2: Second dictionary
|
||||
merge_arrays: Merge arrays instead of overwriting
|
||||
overwrite_duplicates: Overwrite duplicate keys
|
||||
|
||||
Returns:
|
||||
Merged dictionary
|
||||
"""
|
||||
result = dict1.copy()
|
||||
|
||||
for key, value in dict2.items():
|
||||
if key in result:
|
||||
if isinstance(result[key], dict) and isinstance(value, dict):
|
||||
result[key] = self._deep_merge(
|
||||
result[key],
|
||||
value,
|
||||
merge_arrays=merge_arrays,
|
||||
overwrite_duplicates=overwrite_duplicates
|
||||
)
|
||||
elif isinstance(result[key], list) and isinstance(value, list):
|
||||
if merge_arrays:
|
||||
result[key].extend(value)
|
||||
elif overwrite_duplicates:
|
||||
result[key] = value
|
||||
elif overwrite_duplicates:
|
||||
result[key] = value
|
||||
else:
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
def validate_json(
|
||||
self,
|
||||
data: Union[Dict[str, Any], str],
|
||||
schema: Dict[str, Any]
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""Validate JSON data against a schema.
|
||||
|
||||
Args:
|
||||
data: JSON data or filepath
|
||||
schema: JSON schema for validation
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_message)
|
||||
"""
|
||||
try:
|
||||
if util.find_spec('jsonschema') is None:
|
||||
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
|
||||
logging.error(error_msg)
|
||||
return False, error_msg
|
||||
import jsonschema
|
||||
|
||||
# Load data if filepath provided
|
||||
if isinstance(data, str):
|
||||
with open(data, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
jsonschema.validate(instance=data, schema=schema)
|
||||
return True, None
|
||||
|
||||
except jsonschema.exceptions.ValidationError as e:
|
||||
error_msg = f"JSON validation failed: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error validating JSON: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
def analyze_json(
|
||||
self,
|
||||
data: Union[Dict[str, Any], str],
|
||||
max_depth: int = 10
|
||||
) -> Dict[str, Any]:
|
||||
"""Analyze JSON data structure.
|
||||
|
||||
Args:
|
||||
data: JSON data or filepath
|
||||
max_depth: Maximum depth to analyze
|
||||
|
||||
Returns:
|
||||
Dict with analysis results
|
||||
"""
|
||||
try:
|
||||
# Load data if filepath provided
|
||||
if isinstance(data, str):
|
||||
data = self.read_json(data)
|
||||
|
||||
def analyze_value(value: Any, depth: int = 0) -> Dict[str, Any]:
|
||||
if depth >= max_depth:
|
||||
return {'type': 'max_depth_reached'}
|
||||
|
||||
result = {'type': type(value).__name__}
|
||||
|
||||
if isinstance(value, dict):
|
||||
result['size'] = len(value)
|
||||
result['keys'] = list(value.keys())
|
||||
if depth < max_depth - 1:
|
||||
result['children'] = {
|
||||
k: analyze_value(v, depth + 1)
|
||||
for k, v in value.items()
|
||||
}
|
||||
|
||||
elif isinstance(value, list):
|
||||
result['length'] = len(value)
|
||||
if value:
|
||||
result['element_types'] = list(set(
|
||||
type(x).__name__ for x in value
|
||||
))
|
||||
if depth < max_depth - 1:
|
||||
result['sample_elements'] = [
|
||||
analyze_value(x, depth + 1)
|
||||
for x in value[:5]
|
||||
]
|
||||
|
||||
elif isinstance(value, (int, float)):
|
||||
result.update({
|
||||
'value': value,
|
||||
'is_integer': isinstance(value, int)
|
||||
})
|
||||
|
||||
elif isinstance(value, str):
|
||||
result.update({
|
||||
'length': len(value),
|
||||
'sample': value[:100] if len(value) > 100 else value
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
return {
|
||||
'analysis_time': datetime.now().isoformat(),
|
||||
'structure': analyze_value(data)
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error analyzing JSON: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {}
|
||||
|
||||
def transform_json(
|
||||
self,
|
||||
data: Union[Dict[str, Any], str],
|
||||
transformations: List[Dict[str, Any]]
|
||||
) -> Dict[str, Any]:
|
||||
"""Transform JSON data using a list of operations.
|
||||
|
||||
Args:
|
||||
data: JSON data or filepath
|
||||
transformations: List of transformation operations
|
||||
|
||||
Returns:
|
||||
Transformed JSON data
|
||||
"""
|
||||
try:
|
||||
# Load data if filepath provided
|
||||
if isinstance(data, str):
|
||||
data = self.read_json(data)
|
||||
|
||||
result = data.copy()
|
||||
|
||||
for transform in transformations:
|
||||
op = transform.get('operation')
|
||||
path = transform.get('path', '').split('.')
|
||||
value = transform.get('value')
|
||||
|
||||
if op == 'set':
|
||||
self._set_value(result, path, value)
|
||||
elif op == 'delete':
|
||||
self._delete_value(result, path)
|
||||
elif op == 'rename':
|
||||
old_path = path
|
||||
new_path = value.split('.')
|
||||
self._rename_key(result, old_path, new_path)
|
||||
elif op == 'move':
|
||||
old_path = path
|
||||
new_path = value.split('.')
|
||||
self._move_value(result, old_path, new_path)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error transforming JSON: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return data
|
||||
|
||||
def _set_value(self, data: Dict[str, Any], path: List[str], value: Any):
|
||||
"""Set a value at the specified path."""
|
||||
current = data
|
||||
for key in path[:-1]:
|
||||
if key not in current:
|
||||
current[key] = {}
|
||||
current = current[key]
|
||||
current[path[-1]] = value
|
||||
|
||||
def _delete_value(self, data: Dict[str, Any], path: List[str]):
|
||||
"""Delete a value at the specified path."""
|
||||
current = data
|
||||
for key in path[:-1]:
|
||||
if key not in current:
|
||||
return
|
||||
current = current[key]
|
||||
if path[-1] in current:
|
||||
del current[path[-1]]
|
||||
|
||||
def _rename_key(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
old_path: List[str],
|
||||
new_path: List[str]
|
||||
):
|
||||
"""Rename a key at the specified path."""
|
||||
value = self._get_value(data, old_path)
|
||||
if value is not None:
|
||||
self._delete_value(data, old_path)
|
||||
self._set_value(data, new_path, value)
|
||||
|
||||
def _move_value(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
old_path: List[str],
|
||||
new_path: List[str]
|
||||
):
|
||||
"""Move a value from one path to another."""
|
||||
self._rename_key(data, old_path, new_path)
|
||||
|
||||
def _get_value(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
path: List[str]
|
||||
) -> Optional[Any]:
|
||||
"""Get a value at the specified path."""
|
||||
current = data
|
||||
for key in path:
|
||||
if key not in current:
|
||||
return None
|
||||
current = current[key]
|
||||
return current
|
||||
|
||||
# Create instance for direct function access
|
||||
_json_tools = JSONTools()
|
||||
read_json = _json_tools.read_json
|
||||
write_json = _json_tools.write_json
|
||||
merge_json = _json_tools.merge_json
|
||||
validate_json = _json_tools.validate_json
|
||||
analyze_json = _json_tools.analyze_json
|
||||
transform_json = _json_tools.transform_json
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("JSONTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Sample data
|
||||
data1 = {
|
||||
'id': 1,
|
||||
'name': 'Alice',
|
||||
'scores': [95, 87, 92],
|
||||
'details': {
|
||||
'age': 25,
|
||||
'city': 'New York'
|
||||
}
|
||||
}
|
||||
|
||||
data2 = {
|
||||
'id': 2,
|
||||
'name': 'Bob',
|
||||
'scores': [88, 90, 85],
|
||||
'details': {
|
||||
'age': 30,
|
||||
'country': 'USA'
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Write JSON files
|
||||
print("1. Writing JSON Files")
|
||||
print("------------------------------")
|
||||
success = write_json(data1, 'test1.json')
|
||||
print(f"First file written: {success}")
|
||||
success = write_json(data2, 'test2.json')
|
||||
print(f"Second file written: {success}")
|
||||
print()
|
||||
|
||||
# 2. Read JSON file
|
||||
print("2. Reading JSON File")
|
||||
print("------------------------------")
|
||||
data = read_json('test1.json')
|
||||
print("Contents of test1.json:")
|
||||
print(json.dumps(data, indent=2))
|
||||
print()
|
||||
|
||||
# 3. Merge JSON files
|
||||
print("3. Merging JSON Files")
|
||||
print("------------------------------")
|
||||
success = merge_json(['test1.json', 'test2.json'], 'merged.json')
|
||||
print(f"Files merged: {success}")
|
||||
if success:
|
||||
print("Merged contents:")
|
||||
print(json.dumps(read_json('merged.json'), indent=2))
|
||||
print()
|
||||
|
||||
# 4. Validate JSON
|
||||
print("4. Validating JSON")
|
||||
print("------------------------------")
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'integer'},
|
||||
'name': {'type': 'string'},
|
||||
'scores': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'number'}
|
||||
},
|
||||
'details': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'age': {'type': 'integer'},
|
||||
'city': {'type': 'string'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['id', 'name']
|
||||
}
|
||||
|
||||
is_valid, error = validate_json(data1, schema)
|
||||
print(f"Validation result: {is_valid}")
|
||||
if error:
|
||||
print(f"Validation error: {error}")
|
||||
print()
|
||||
|
||||
# 5. Analyze JSON
|
||||
print("5. Analyzing JSON")
|
||||
print("------------------------------")
|
||||
analysis = analyze_json(data1)
|
||||
print("Analysis results:")
|
||||
print(json.dumps(analysis, indent=2))
|
||||
print()
|
||||
|
||||
# 6. Transform JSON
|
||||
print("6. Transforming JSON")
|
||||
print("------------------------------")
|
||||
transformations = [
|
||||
{
|
||||
'operation': 'set',
|
||||
'path': 'details.status',
|
||||
'value': 'active'
|
||||
},
|
||||
{
|
||||
'operation': 'rename',
|
||||
'path': 'details.city',
|
||||
'value': 'details.location'
|
||||
}
|
||||
]
|
||||
|
||||
transformed = transform_json(data1, transformations)
|
||||
print("Transformed data:")
|
||||
print(json.dumps(transformed, indent=2))
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,354 @@
|
||||
"""Newspaper tools for scraping and parsing news articles.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import newspaper_tools
|
||||
article = newspaper_tools.get_article("https://example.com/article")
|
||||
sources = newspaper_tools.get_news_sources("technology")
|
||||
articles = newspaper_tools.get_articles_from_source("https://techcrunch.com")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import get_article, get_news_sources
|
||||
article = get_article("https://example.com/article")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# Predefined list of popular news sources
|
||||
POPULAR_NEWS_SOURCES = {
|
||||
'technology': [
|
||||
'https://techcrunch.com',
|
||||
'https://www.theverge.com',
|
||||
'https://www.wired.com',
|
||||
'https://www.engadget.com',
|
||||
'https://arstechnica.com'
|
||||
],
|
||||
'business': [
|
||||
'https://www.bloomberg.com',
|
||||
'https://www.reuters.com',
|
||||
'https://www.wsj.com',
|
||||
'https://www.ft.com',
|
||||
'https://www.cnbc.com'
|
||||
],
|
||||
'general': [
|
||||
'https://www.nytimes.com',
|
||||
'https://www.theguardian.com',
|
||||
'https://www.washingtonpost.com',
|
||||
'https://www.bbc.com',
|
||||
'https://www.cnn.com'
|
||||
],
|
||||
'sports': [
|
||||
'https://www.espn.com',
|
||||
'https://sports.yahoo.com',
|
||||
'https://www.cbssports.com',
|
||||
'https://www.skysports.com',
|
||||
'https://www.bleacherreport.com'
|
||||
],
|
||||
'entertainment': [
|
||||
'https://variety.com',
|
||||
'https://www.hollywoodreporter.com',
|
||||
'https://www.ew.com',
|
||||
'https://www.deadline.com',
|
||||
'https://www.imdb.com/news'
|
||||
],
|
||||
'science': [
|
||||
'https://www.scientificamerican.com',
|
||||
'https://www.sciencedaily.com',
|
||||
'https://www.newscientist.com',
|
||||
'https://www.sciencemag.org',
|
||||
'https://www.nature.com/news'
|
||||
]
|
||||
}
|
||||
|
||||
class NewspaperTools:
|
||||
"""Tools for scraping and parsing news articles."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize NewspaperTools and check for newspaper package."""
|
||||
self._check_newspaper()
|
||||
|
||||
def _check_newspaper(self):
|
||||
"""Check if newspaper package is installed."""
|
||||
if util.find_spec("newspaper") is None:
|
||||
raise ImportError("newspaper3k package is not available. Please install it using: pip install newspaper3k")
|
||||
global newspaper
|
||||
import newspaper
|
||||
|
||||
def get_article(
|
||||
self,
|
||||
url: str,
|
||||
language: str = 'en'
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract and parse a news article from a URL.
|
||||
|
||||
Args:
|
||||
url: URL of the article
|
||||
language: Language code (e.g., 'en' for English)
|
||||
|
||||
Returns:
|
||||
Dict: Article information including title, text, authors, etc.
|
||||
"""
|
||||
try:
|
||||
from newspaper import Article, Config
|
||||
|
||||
# Configure article download
|
||||
config = Config()
|
||||
config.browser_user_agent = 'Mozilla/5.0'
|
||||
config.language = language
|
||||
|
||||
# Download and parse article
|
||||
article = Article(url, config=config)
|
||||
article.download()
|
||||
article.parse()
|
||||
|
||||
# Try to extract additional information
|
||||
try:
|
||||
article.nlp()
|
||||
except Exception as e:
|
||||
logging.warning(f"NLP processing failed: {str(e)}")
|
||||
|
||||
# Build response
|
||||
response = {
|
||||
"url": url,
|
||||
"title": article.title,
|
||||
"text": article.text,
|
||||
"authors": article.authors,
|
||||
"publish_date": article.publish_date.isoformat() if article.publish_date else None,
|
||||
"top_image": article.top_image,
|
||||
"images": list(article.images),
|
||||
"movies": list(article.movies),
|
||||
"source_domain": urlparse(url).netloc,
|
||||
}
|
||||
|
||||
# Add NLP results if available
|
||||
if hasattr(article, 'keywords') and article.keywords:
|
||||
response["keywords"] = article.keywords
|
||||
if hasattr(article, 'summary') and article.summary:
|
||||
response["summary"] = article.summary
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
error_msg = f"Error extracting article from {url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_news_sources(
|
||||
self,
|
||||
category: Optional[str] = None,
|
||||
language: str = 'en',
|
||||
country: Optional[str] = None
|
||||
) -> Union[List[Dict[str, str]], Dict[str, str]]:
|
||||
"""
|
||||
Get a list of news sources, optionally filtered by category.
|
||||
|
||||
Args:
|
||||
category: Category to filter by (e.g., 'technology', 'sports')
|
||||
language: Language code
|
||||
country: Country code
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of news sources or error dict
|
||||
"""
|
||||
try:
|
||||
sources = []
|
||||
|
||||
# Get sources for the specified category or all categories
|
||||
if category:
|
||||
category = category.lower()
|
||||
if category in POPULAR_NEWS_SOURCES:
|
||||
urls = POPULAR_NEWS_SOURCES[category]
|
||||
else:
|
||||
urls = []
|
||||
for cat_urls in POPULAR_NEWS_SOURCES.values():
|
||||
urls.extend(cat_urls)
|
||||
else:
|
||||
urls = []
|
||||
for cat_urls in POPULAR_NEWS_SOURCES.values():
|
||||
urls.extend(cat_urls)
|
||||
|
||||
# Create source objects
|
||||
for url in urls:
|
||||
domain = urlparse(url).netloc
|
||||
source = {
|
||||
"url": url,
|
||||
"domain": domain,
|
||||
"name": domain.replace("www.", "").split(".")[0].title(),
|
||||
"category": category if category else "general"
|
||||
}
|
||||
sources.append(source)
|
||||
|
||||
return sources
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting news sources: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_articles_from_source(
|
||||
self,
|
||||
source_url: str,
|
||||
limit: int = 10,
|
||||
language: str = 'en'
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Get recent articles from a news source.
|
||||
|
||||
Args:
|
||||
source_url: URL of the news source
|
||||
limit: Maximum number of articles to return
|
||||
language: Language code
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of articles or error dict
|
||||
"""
|
||||
try:
|
||||
from newspaper import Source, Config
|
||||
|
||||
# Configure source scraping
|
||||
config = Config()
|
||||
config.browser_user_agent = 'Mozilla/5.0'
|
||||
config.language = language
|
||||
config.fetch_images = False # Speed up processing
|
||||
|
||||
# Build news source
|
||||
source = Source(source_url, config=config)
|
||||
source.build()
|
||||
|
||||
# Get articles
|
||||
articles = []
|
||||
for article_url in source.article_urls()[:limit]:
|
||||
try:
|
||||
article = self.get_article(article_url, language)
|
||||
if "error" not in article:
|
||||
articles.append(article)
|
||||
except Exception as e:
|
||||
logging.warning(f"Error processing article {article_url}: {str(e)}")
|
||||
continue
|
||||
|
||||
if len(articles) >= limit:
|
||||
break
|
||||
|
||||
return articles
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting articles from {source_url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_trending_topics(
|
||||
self,
|
||||
sources: Optional[List[str]] = None,
|
||||
limit: int = 10,
|
||||
language: str = 'en'
|
||||
) -> Union[List[str], Dict[str, str]]:
|
||||
"""
|
||||
Get trending topics across news sources.
|
||||
|
||||
Args:
|
||||
sources: List of source URLs to analyze
|
||||
limit: Maximum number of trending topics to return
|
||||
language: Language code
|
||||
|
||||
Returns:
|
||||
List[str] or Dict: List of trending topics or error dict
|
||||
"""
|
||||
try:
|
||||
from collections import Counter
|
||||
|
||||
# Use default sources if none provided
|
||||
if not sources:
|
||||
sources_data = self.get_news_sources(language=language)
|
||||
if isinstance(sources_data, dict) and "error" in sources_data:
|
||||
return sources_data
|
||||
sources = [s["url"] for s in sources_data[:5]] # Use top 5 sources
|
||||
|
||||
# Collect keywords from articles
|
||||
all_keywords = []
|
||||
for source_url in sources:
|
||||
try:
|
||||
articles = self.get_articles_from_source(source_url, limit=5, language=language)
|
||||
if isinstance(articles, list):
|
||||
for article in articles:
|
||||
if "keywords" in article:
|
||||
all_keywords.extend(article["keywords"])
|
||||
except Exception as e:
|
||||
logging.warning(f"Error processing source {source_url}: {str(e)}")
|
||||
continue
|
||||
|
||||
# Get most common keywords
|
||||
trending = Counter(all_keywords).most_common(limit)
|
||||
return [topic for topic, count in trending]
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting trending topics: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Create instance for direct function access
|
||||
_newspaper_tools = NewspaperTools()
|
||||
get_article = _newspaper_tools.get_article
|
||||
get_news_sources = _newspaper_tools.get_news_sources
|
||||
get_articles_from_source = _newspaper_tools.get_articles_from_source
|
||||
get_trending_topics = _newspaper_tools.get_trending_topics
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("NewspaperTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Get news sources
|
||||
print("1. Getting News Sources")
|
||||
print("------------------------------")
|
||||
tech_sources = get_news_sources("technology")
|
||||
print("Technology news sources:")
|
||||
if isinstance(tech_sources, list):
|
||||
print(json.dumps(tech_sources[:3], indent=2)) # Show first 3 sources
|
||||
else:
|
||||
print(tech_sources) # Show error
|
||||
print()
|
||||
|
||||
if isinstance(tech_sources, list) and tech_sources:
|
||||
source_url = tech_sources[0]["url"]
|
||||
|
||||
# 2. Get articles from a source
|
||||
print("2. Getting Articles from Source")
|
||||
print("------------------------------")
|
||||
articles = get_articles_from_source(source_url, limit=2)
|
||||
print(f"Articles from {source_url}:")
|
||||
if isinstance(articles, list):
|
||||
for article in articles:
|
||||
print(f"- {article['title']}")
|
||||
if "summary" in article:
|
||||
print(f" Summary: {article['summary'][:200]}...")
|
||||
else:
|
||||
print(articles) # Show error
|
||||
print()
|
||||
|
||||
# 3. Get a single article
|
||||
print("3. Getting Single Article")
|
||||
print("------------------------------")
|
||||
if isinstance(articles, list) and articles:
|
||||
article_url = articles[0]["url"]
|
||||
article = get_article(article_url)
|
||||
if "error" not in article:
|
||||
print(f"Article: {article['title']}")
|
||||
if "summary" in article:
|
||||
print(f"Summary: {article['summary'][:200]}...")
|
||||
print(f"Authors: {', '.join(article['authors'])}")
|
||||
print(f"Date: {article['publish_date']}")
|
||||
else:
|
||||
print(article) # Show error
|
||||
print()
|
||||
|
||||
# 4. Get trending topics
|
||||
print("4. Getting Trending Topics")
|
||||
print("------------------------------")
|
||||
topics = get_trending_topics([source_url], limit=5)
|
||||
print("Trending topics:")
|
||||
print(json.dumps(topics, indent=2))
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,326 @@
|
||||
"""Pandas tools for data manipulation and analysis.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import pandas_tools
|
||||
df = pandas_tools.read_csv("data.csv")
|
||||
df = pandas_tools.filter_data(df, "column > 5")
|
||||
summary = pandas_tools.get_summary(df)
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_csv, filter_data, get_summary
|
||||
df = read_csv("data.csv")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import json
|
||||
import os
|
||||
|
||||
# Import pandas for type hints, but don't use it until we check it's installed
|
||||
if util.find_spec("pandas") is not None:
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
else:
|
||||
# Create a placeholder for type hints
|
||||
class pd:
|
||||
DataFrame = None
|
||||
|
||||
def _convert_to_serializable(obj: Any) -> Any:
|
||||
"""Convert numpy/pandas types to JSON serializable Python types."""
|
||||
if isinstance(obj, (np.integer, np.floating)):
|
||||
return obj.item()
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return obj.tolist()
|
||||
elif isinstance(obj, pd.Series):
|
||||
return obj.to_list()
|
||||
elif isinstance(obj, pd.DataFrame):
|
||||
return obj.to_dict(orient='records')
|
||||
return obj
|
||||
|
||||
class PandasTools:
|
||||
"""Tools for data manipulation and analysis using pandas."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize PandasTools and check for pandas installation."""
|
||||
self._check_pandas()
|
||||
|
||||
def _check_pandas(self):
|
||||
"""Check if pandas is installed."""
|
||||
if util.find_spec("pandas") is None:
|
||||
raise ImportError("pandas is not available. Please install it using: pip install pandas")
|
||||
global pd, np
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
def read_csv(self, filepath: str, **kwargs) -> Union[pd.DataFrame, Dict[str, str]]:
|
||||
"""
|
||||
Read a CSV file into a pandas DataFrame.
|
||||
|
||||
Args:
|
||||
filepath: Path to the CSV file
|
||||
**kwargs: Additional arguments to pass to pd.read_csv()
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or Dict: DataFrame if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return pd.read_csv(filepath, **kwargs)
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def read_excel(self, filepath: str, **kwargs) -> Union[pd.DataFrame, Dict[str, str]]:
|
||||
"""
|
||||
Read an Excel file into a pandas DataFrame.
|
||||
|
||||
Args:
|
||||
filepath: Path to the Excel file
|
||||
**kwargs: Additional arguments to pass to pd.read_excel()
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or Dict: DataFrame if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return pd.read_excel(filepath, **kwargs)
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading Excel file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def write_csv(self, df: pd.DataFrame, filepath: str, **kwargs) -> bool:
|
||||
"""
|
||||
Write DataFrame to a CSV file.
|
||||
|
||||
Args:
|
||||
df: DataFrame to write
|
||||
filepath: Output file path
|
||||
**kwargs: Additional arguments to pass to df.to_csv()
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
df.to_csv(filepath, **kwargs)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing CSV file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def write_excel(self, df: pd.DataFrame, filepath: str, **kwargs) -> bool:
|
||||
"""
|
||||
Write DataFrame to an Excel file.
|
||||
|
||||
Args:
|
||||
df: DataFrame to write
|
||||
filepath: Output file path
|
||||
**kwargs: Additional arguments to pass to df.to_excel()
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
df.to_excel(filepath, **kwargs)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing Excel file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def filter_data(self, df: pd.DataFrame, query: str) -> Union[pd.DataFrame, Dict[str, str]]:
|
||||
"""
|
||||
Filter DataFrame using a query string.
|
||||
|
||||
Args:
|
||||
df: Input DataFrame
|
||||
query: Query string (e.g., "column > 5 and other_column == 'value'")
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or Dict: Filtered DataFrame if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return df.query(query)
|
||||
except Exception as e:
|
||||
error_msg = f"Error filtering data with query '{query}': {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_summary(self, df: pd.DataFrame) -> Dict[str, Any]:
|
||||
"""
|
||||
Get a summary of the DataFrame including basic statistics and info.
|
||||
|
||||
Args:
|
||||
df: Input DataFrame
|
||||
|
||||
Returns:
|
||||
Dict: Summary statistics and information
|
||||
"""
|
||||
try:
|
||||
numeric_summary = df.describe().to_dict()
|
||||
# Convert numpy types to native Python types
|
||||
for col in numeric_summary:
|
||||
numeric_summary[col] = {k: _convert_to_serializable(v)
|
||||
for k, v in numeric_summary[col].items()}
|
||||
|
||||
summary = {
|
||||
"shape": list(df.shape),
|
||||
"columns": list(df.columns),
|
||||
"dtypes": df.dtypes.astype(str).to_dict(),
|
||||
"null_counts": df.isnull().sum().to_dict(),
|
||||
"numeric_summary": numeric_summary,
|
||||
"memory_usage": int(df.memory_usage(deep=True).sum()),
|
||||
}
|
||||
return summary
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting data summary: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def group_by(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
columns: Union[str, List[str]],
|
||||
agg_dict: Dict[str, Union[str, List[str]]]
|
||||
) -> Union[pd.DataFrame, Dict[str, str]]:
|
||||
"""
|
||||
Group DataFrame by columns and apply aggregation functions.
|
||||
|
||||
Args:
|
||||
df: Input DataFrame
|
||||
columns: Column(s) to group by
|
||||
agg_dict: Dictionary of column:function pairs for aggregation
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or Dict: Grouped DataFrame if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return df.groupby(columns).agg(agg_dict).reset_index()
|
||||
except Exception as e:
|
||||
error_msg = f"Error grouping data: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def pivot_table(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
index: Union[str, List[str]],
|
||||
columns: Optional[Union[str, List[str]]] = None,
|
||||
values: Optional[Union[str, List[str]]] = None,
|
||||
aggfunc: str = "mean"
|
||||
) -> Union[pd.DataFrame, Dict[str, str]]:
|
||||
"""
|
||||
Create a pivot table from DataFrame.
|
||||
|
||||
Args:
|
||||
df: Input DataFrame
|
||||
index: Column(s) to use as index
|
||||
columns: Column(s) to use as columns
|
||||
values: Column(s) to aggregate
|
||||
aggfunc: Aggregation function to use
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or Dict: Pivot table if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return pd.pivot_table(
|
||||
df,
|
||||
index=index,
|
||||
columns=columns,
|
||||
values=values,
|
||||
aggfunc=aggfunc
|
||||
).reset_index()
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating pivot table: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Create instance for direct function access
|
||||
_pandas_tools = PandasTools()
|
||||
read_csv = _pandas_tools.read_csv
|
||||
read_excel = _pandas_tools.read_excel
|
||||
write_csv = _pandas_tools.write_csv
|
||||
write_excel = _pandas_tools.write_excel
|
||||
filter_data = _pandas_tools.filter_data
|
||||
get_summary = _pandas_tools.get_summary
|
||||
group_by = _pandas_tools.group_by
|
||||
pivot_table = _pandas_tools.pivot_table
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("PandasTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create a test directory
|
||||
test_dir = os.path.join(os.getcwd(), "test_files")
|
||||
os.makedirs(test_dir, exist_ok=True)
|
||||
|
||||
# Create a sample DataFrame
|
||||
df = pd.DataFrame({
|
||||
'name': ['John', 'Jane', 'Bob', 'Alice', 'Charlie'],
|
||||
'age': [25, 30, 35, 28, 32],
|
||||
'city': ['New York', 'London', 'Paris', 'Tokyo', 'London'],
|
||||
'salary': [50000, 60000, 75000, 65000, 55000]
|
||||
})
|
||||
|
||||
# 1. Write to CSV
|
||||
print("1. Writing to CSV")
|
||||
print("------------------------------")
|
||||
csv_file = os.path.join(test_dir, "sample.csv")
|
||||
success = write_csv(df, csv_file, index=False)
|
||||
print(f"Write successful: {success}\n")
|
||||
|
||||
# 2. Read from CSV
|
||||
print("2. Reading from CSV")
|
||||
print("------------------------------")
|
||||
df_read = read_csv(csv_file)
|
||||
print("First few rows:")
|
||||
print(df_read.head())
|
||||
print()
|
||||
|
||||
# 3. Filter Data
|
||||
print("3. Filtering Data")
|
||||
print("------------------------------")
|
||||
filtered_df = filter_data(df, "age > 30 and salary > 60000")
|
||||
print("People over 30 with salary > 60000:")
|
||||
print(filtered_df)
|
||||
print()
|
||||
|
||||
# 4. Get Summary
|
||||
print("4. Data Summary")
|
||||
print("------------------------------")
|
||||
summary = get_summary(df)
|
||||
print(json.dumps(summary, indent=2))
|
||||
print()
|
||||
|
||||
# 5. Group By
|
||||
print("5. Group By")
|
||||
print("------------------------------")
|
||||
grouped = group_by(df, "city", {"salary": ["mean", "count"], "age": "mean"})
|
||||
print("Statistics by city:")
|
||||
print(grouped)
|
||||
print()
|
||||
|
||||
# 6. Pivot Table
|
||||
print("6. Pivot Table")
|
||||
print("------------------------------")
|
||||
pivoted = pivot_table(df, index="city", values=["salary", "age"])
|
||||
print("Pivot table by city:")
|
||||
print(pivoted)
|
||||
print()
|
||||
|
||||
# Clean up test directory
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(test_dir)
|
||||
print("Test directory cleaned up successfully")
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up test directory: {str(e)}")
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,423 @@
|
||||
"""Tools for Python code execution and analysis.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import python_tools
|
||||
result = python_tools.execute_code("print('Hello, World!')")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import execute_code, analyze_code, format_code
|
||||
result = execute_code("print('Hello, World!')")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from importlib import util
|
||||
import io
|
||||
from contextlib import redirect_stdout, redirect_stderr
|
||||
import traceback
|
||||
|
||||
class PythonTools:
|
||||
"""Tools for Python code execution and analysis."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize PythonTools."""
|
||||
self._check_dependencies()
|
||||
|
||||
def _check_dependencies(self):
|
||||
"""Check if required packages are installed."""
|
||||
missing = []
|
||||
for package in ['black', 'pylint', 'autopep8']:
|
||||
if util.find_spec(package) is None:
|
||||
missing.append(package)
|
||||
|
||||
if missing:
|
||||
raise ImportError(
|
||||
f"Required packages not available. Please install: {', '.join(missing)}\n"
|
||||
f"Run: pip install {' '.join(missing)}"
|
||||
)
|
||||
|
||||
def execute_code(
|
||||
self,
|
||||
code: str,
|
||||
globals_dict: Optional[Dict[str, Any]] = None,
|
||||
locals_dict: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 30,
|
||||
max_output_size: int = 10000
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute Python code safely."""
|
||||
try:
|
||||
# Set up execution environment
|
||||
if globals_dict is None:
|
||||
globals_dict = {'__builtins__': __builtins__}
|
||||
if locals_dict is None:
|
||||
locals_dict = {}
|
||||
|
||||
# Capture output
|
||||
stdout_buffer = io.StringIO()
|
||||
stderr_buffer = io.StringIO()
|
||||
|
||||
try:
|
||||
# Compile code
|
||||
compiled_code = compile(code, '<string>', 'exec')
|
||||
|
||||
# Execute with output capture
|
||||
with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
|
||||
exec(compiled_code, globals_dict, locals_dict)
|
||||
|
||||
# Get last expression value if any
|
||||
import ast
|
||||
tree = ast.parse(code)
|
||||
if tree.body and isinstance(tree.body[-1], ast.Expr):
|
||||
result = eval(
|
||||
compile(ast.Expression(tree.body[-1].value), '<string>', 'eval'),
|
||||
globals_dict,
|
||||
locals_dict
|
||||
)
|
||||
else:
|
||||
result = None
|
||||
|
||||
# Get output
|
||||
stdout = stdout_buffer.getvalue()
|
||||
stderr = stderr_buffer.getvalue()
|
||||
|
||||
# Truncate output if too large
|
||||
if len(stdout) > max_output_size:
|
||||
stdout = stdout[:max_output_size] + "...[truncated]"
|
||||
if len(stderr) > max_output_size:
|
||||
stderr = stderr[:max_output_size] + "...[truncated]"
|
||||
|
||||
return {
|
||||
'result': result,
|
||||
'stdout': stdout,
|
||||
'stderr': stderr,
|
||||
'success': True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {
|
||||
'result': None,
|
||||
'stdout': stdout_buffer.getvalue(),
|
||||
'stderr': error_msg,
|
||||
'success': False
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {
|
||||
'result': None,
|
||||
'stdout': '',
|
||||
'stderr': error_msg,
|
||||
'success': False
|
||||
}
|
||||
|
||||
def analyze_code(
|
||||
self,
|
||||
code: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Analyze Python code structure and quality.
|
||||
|
||||
Args:
|
||||
code: Python code to analyze
|
||||
|
||||
Returns:
|
||||
Dictionary with analysis results
|
||||
"""
|
||||
try:
|
||||
# Import ast only when needed
|
||||
import ast
|
||||
|
||||
# Parse code
|
||||
tree = ast.parse(code)
|
||||
|
||||
# Analyze structure
|
||||
analysis = {
|
||||
'imports': [],
|
||||
'functions': [],
|
||||
'classes': [],
|
||||
'variables': [],
|
||||
'complexity': {
|
||||
'lines': len(code.splitlines()),
|
||||
'functions': 0,
|
||||
'classes': 0,
|
||||
'branches': 0
|
||||
}
|
||||
}
|
||||
|
||||
# Analyze nodes
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for name in node.names:
|
||||
analysis['imports'].append(name.name)
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
module = node.module or ''
|
||||
for name in node.names:
|
||||
analysis['imports'].append(f"{module}.{name.name}")
|
||||
elif isinstance(node, ast.FunctionDef):
|
||||
analysis['functions'].append({
|
||||
'name': node.name,
|
||||
'args': [
|
||||
arg.arg for arg in node.args.args
|
||||
],
|
||||
'decorators': [
|
||||
ast.unparse(d) for d in node.decorator_list
|
||||
]
|
||||
})
|
||||
analysis['complexity']['functions'] += 1
|
||||
elif isinstance(node, ast.ClassDef):
|
||||
analysis['classes'].append({
|
||||
'name': node.name,
|
||||
'bases': [
|
||||
ast.unparse(base) for base in node.bases
|
||||
],
|
||||
'decorators': [
|
||||
ast.unparse(d) for d in node.decorator_list
|
||||
]
|
||||
})
|
||||
analysis['complexity']['classes'] += 1
|
||||
elif isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
analysis['variables'].append(target.id)
|
||||
elif isinstance(node, (ast.If, ast.While, ast.For)):
|
||||
analysis['complexity']['branches'] += 1
|
||||
|
||||
return analysis
|
||||
except Exception as e:
|
||||
error_msg = f"Error analyzing code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def format_code(
|
||||
self,
|
||||
code: str,
|
||||
style: str = 'black',
|
||||
line_length: int = 88
|
||||
) -> Optional[str]:
|
||||
"""Format Python code according to style guide.
|
||||
|
||||
Args:
|
||||
code: Python code to format
|
||||
style: Formatting style ('black' or 'pep8')
|
||||
line_length: Maximum line length
|
||||
|
||||
Returns:
|
||||
Formatted code
|
||||
"""
|
||||
try:
|
||||
if util.find_spec(style) is None:
|
||||
error_msg = f"{style} package is not available. Please install it using: pip install {style}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
if style == 'black':
|
||||
import black
|
||||
return black.format_str(
|
||||
code,
|
||||
mode=black.FileMode(
|
||||
line_length=line_length
|
||||
)
|
||||
)
|
||||
else: # pep8
|
||||
import autopep8
|
||||
return autopep8.fix_code(
|
||||
code,
|
||||
options={
|
||||
'max_line_length': line_length
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"Error formatting code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def lint_code(
|
||||
self,
|
||||
code: str
|
||||
) -> Optional[Dict[str, List[Dict[str, Any]]]]:
|
||||
"""Lint Python code for potential issues.
|
||||
|
||||
Args:
|
||||
code: Python code to lint
|
||||
|
||||
Returns:
|
||||
Dictionary with linting results
|
||||
"""
|
||||
try:
|
||||
if util.find_spec('pylint') is None:
|
||||
error_msg = "pylint package is not available. Please install it using: pip install pylint"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
# Import pylint only when needed
|
||||
from pylint.reporters import JSONReporter
|
||||
from pylint.lint.run import Run
|
||||
|
||||
# Create temporary file for pylint
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w',
|
||||
suffix='.py',
|
||||
delete=False
|
||||
) as f:
|
||||
f.write(code)
|
||||
temp_path = f.name
|
||||
|
||||
# Run pylint
|
||||
reporter = JSONReporter()
|
||||
Run(
|
||||
[temp_path],
|
||||
reporter=reporter,
|
||||
exit=False
|
||||
)
|
||||
|
||||
# Process results
|
||||
results = {
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'conventions': []
|
||||
}
|
||||
|
||||
for msg in reporter.messages:
|
||||
item = {
|
||||
'type': msg.category,
|
||||
'module': msg.module,
|
||||
'obj': msg.obj,
|
||||
'line': msg.line,
|
||||
'column': msg.column,
|
||||
'path': msg.path,
|
||||
'symbol': msg.symbol,
|
||||
'message': msg.msg,
|
||||
'message-id': msg.msg_id
|
||||
}
|
||||
|
||||
if msg.category in ['error', 'fatal']:
|
||||
results['errors'].append(item)
|
||||
elif msg.category == 'warning':
|
||||
results['warnings'].append(item)
|
||||
else:
|
||||
results['conventions'].append(item)
|
||||
|
||||
# Clean up
|
||||
import os
|
||||
os.unlink(temp_path)
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error linting code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {
|
||||
'errors': [],
|
||||
'warnings': [],
|
||||
'conventions': []
|
||||
}
|
||||
|
||||
def disassemble_code(
|
||||
self,
|
||||
code: str
|
||||
) -> Optional[str]:
|
||||
"""Disassemble Python code to bytecode.
|
||||
|
||||
Args:
|
||||
code: Python code to disassemble
|
||||
|
||||
Returns:
|
||||
Disassembled bytecode as string
|
||||
"""
|
||||
try:
|
||||
# Import dis only when needed
|
||||
import dis
|
||||
|
||||
# Compile code
|
||||
compiled_code = compile(code, '<string>', 'exec')
|
||||
|
||||
# Capture disassembly
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
dis.dis(compiled_code)
|
||||
|
||||
return output.getvalue()
|
||||
except Exception as e:
|
||||
error_msg = f"Error disassembling code: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
# Create instance for direct function access
|
||||
_python_tools = PythonTools()
|
||||
execute_code = _python_tools.execute_code
|
||||
analyze_code = _python_tools.analyze_code
|
||||
format_code = _python_tools.format_code
|
||||
lint_code = _python_tools.lint_code
|
||||
disassemble_code = _python_tools.disassemble_code
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("PythonTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
print("1. Execute Python Code")
|
||||
print("------------------------------")
|
||||
code = """
|
||||
def greet(name):
|
||||
return f"Hello, {name}!"
|
||||
|
||||
result = greet("World")
|
||||
print(result)
|
||||
"""
|
||||
print("Code to execute:")
|
||||
print(code)
|
||||
print("\nOutput:")
|
||||
result = execute_code(code)
|
||||
print(result["stdout"])
|
||||
print()
|
||||
|
||||
print("2. Format Python Code")
|
||||
print("------------------------------")
|
||||
code = """
|
||||
def messy_function(x,y, z):
|
||||
if x>0:
|
||||
return y+z
|
||||
else:
|
||||
return y-z
|
||||
"""
|
||||
print("Before formatting:")
|
||||
print(code)
|
||||
print("\nAfter formatting:")
|
||||
result = format_code(code)
|
||||
print(result)
|
||||
print()
|
||||
|
||||
print("3. Lint Python Code")
|
||||
print("------------------------------")
|
||||
code = """
|
||||
def bad_function():
|
||||
unused_var = 42
|
||||
return 'result'
|
||||
"""
|
||||
print("Code to lint:")
|
||||
print(code)
|
||||
print("\nLinting results:")
|
||||
result = lint_code(code)
|
||||
print(result)
|
||||
print()
|
||||
|
||||
print("4. Execute Code with Variables")
|
||||
print("------------------------------")
|
||||
code = """
|
||||
x = 10
|
||||
y = 20
|
||||
result = x + y
|
||||
print(f"The sum of {x} and {y} is {result}")
|
||||
"""
|
||||
print("Code to execute:")
|
||||
print(code)
|
||||
print("\nOutput:")
|
||||
result = execute_code(code)
|
||||
print(result["stdout"])
|
||||
print()
|
||||
|
||||
print("==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,278 @@
|
||||
"""Tools for executing shell commands safely.
|
||||
|
||||
This module provides a safe interface for executing shell commands with:
|
||||
- Timeout control
|
||||
- Output capture
|
||||
- Error handling
|
||||
- Resource limits
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import shlex
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
class ShellTools:
|
||||
"""Tools for executing shell commands safely."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize ShellTools."""
|
||||
self._check_dependencies()
|
||||
|
||||
def _check_dependencies(self):
|
||||
"""Check if required packages are installed."""
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Required package not available. Please install: psutil\n"
|
||||
"Run: pip install psutil"
|
||||
)
|
||||
|
||||
def execute_command(
|
||||
self,
|
||||
command: str,
|
||||
cwd: Optional[str] = None,
|
||||
timeout: int = 30,
|
||||
shell: bool = False,
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
max_output_size: int = 10000
|
||||
) -> Dict[str, Union[str, int, bool]]:
|
||||
"""Execute a shell command safely.
|
||||
|
||||
Args:
|
||||
command: Command to execute
|
||||
cwd: Working directory
|
||||
timeout: Maximum execution time in seconds
|
||||
shell: Whether to run command in shell
|
||||
env: Environment variables
|
||||
max_output_size: Maximum output size in bytes
|
||||
|
||||
Returns:
|
||||
Dictionary with execution results
|
||||
"""
|
||||
try:
|
||||
# Split command if not using shell
|
||||
if not shell:
|
||||
command = shlex.split(command)
|
||||
|
||||
# Set up process environment
|
||||
process_env = os.environ.copy()
|
||||
if env:
|
||||
process_env.update(env)
|
||||
|
||||
# Start process
|
||||
start_time = time.time()
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=cwd,
|
||||
shell=shell,
|
||||
env=process_env,
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
# Wait for process with timeout
|
||||
stdout, stderr = process.communicate(timeout=timeout)
|
||||
|
||||
# Truncate output if too large
|
||||
if len(stdout) > max_output_size:
|
||||
stdout = stdout[:max_output_size] + "...[truncated]"
|
||||
if len(stderr) > max_output_size:
|
||||
stderr = stderr[:max_output_size] + "...[truncated]"
|
||||
|
||||
return {
|
||||
'stdout': stdout,
|
||||
'stderr': stderr,
|
||||
'exit_code': process.returncode,
|
||||
'success': process.returncode == 0,
|
||||
'execution_time': time.time() - start_time
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
# Kill process on timeout
|
||||
parent = psutil.Process(process.pid)
|
||||
children = parent.children(recursive=True)
|
||||
for child in children:
|
||||
child.kill()
|
||||
parent.kill()
|
||||
|
||||
return {
|
||||
'stdout': '',
|
||||
'stderr': f'Command timed out after {timeout} seconds',
|
||||
'exit_code': -1,
|
||||
'success': False,
|
||||
'execution_time': timeout
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing command: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {
|
||||
'stdout': '',
|
||||
'stderr': error_msg,
|
||||
'exit_code': -1,
|
||||
'success': False,
|
||||
'execution_time': 0
|
||||
}
|
||||
|
||||
def list_processes(self) -> List[Dict[str, Union[int, str, float]]]:
|
||||
"""List running processes with their details.
|
||||
|
||||
Returns:
|
||||
List of process information dictionaries
|
||||
"""
|
||||
try:
|
||||
processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'username', 'memory_percent', 'cpu_percent']):
|
||||
try:
|
||||
pinfo = proc.info
|
||||
processes.append({
|
||||
'pid': pinfo['pid'],
|
||||
'name': pinfo['name'],
|
||||
'username': pinfo['username'],
|
||||
'memory_percent': round(pinfo['memory_percent'], 2),
|
||||
'cpu_percent': round(pinfo['cpu_percent'], 2)
|
||||
})
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
pass
|
||||
return processes
|
||||
except Exception as e:
|
||||
error_msg = f"Error listing processes: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return []
|
||||
|
||||
def kill_process(
|
||||
self,
|
||||
pid: int,
|
||||
force: bool = False
|
||||
) -> Dict[str, Union[bool, str]]:
|
||||
"""Kill a process by its PID.
|
||||
|
||||
Args:
|
||||
pid: Process ID to kill
|
||||
force: Whether to force kill (-9)
|
||||
|
||||
Returns:
|
||||
Dictionary with operation results
|
||||
"""
|
||||
try:
|
||||
process = psutil.Process(pid)
|
||||
if force:
|
||||
process.kill() # SIGKILL
|
||||
else:
|
||||
process.terminate() # SIGTERM
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': f'Process {pid} killed successfully'
|
||||
}
|
||||
except psutil.NoSuchProcess:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'No process found with PID {pid}'
|
||||
}
|
||||
except psutil.AccessDenied:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Access denied to kill process {pid}'
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error killing process: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {
|
||||
'success': False,
|
||||
'message': error_msg
|
||||
}
|
||||
|
||||
def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]:
|
||||
"""Get system information.
|
||||
|
||||
Returns:
|
||||
Dictionary with system information
|
||||
"""
|
||||
try:
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
memory = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
return {
|
||||
'cpu': {
|
||||
'percent': cpu_percent,
|
||||
'cores': psutil.cpu_count(),
|
||||
'physical_cores': psutil.cpu_count(logical=False)
|
||||
},
|
||||
'memory': {
|
||||
'total': memory.total,
|
||||
'available': memory.available,
|
||||
'percent': memory.percent,
|
||||
'used': memory.used,
|
||||
'free': memory.free
|
||||
},
|
||||
'disk': {
|
||||
'total': disk.total,
|
||||
'used': disk.used,
|
||||
'free': disk.free,
|
||||
'percent': disk.percent
|
||||
},
|
||||
'boot_time': psutil.boot_time(),
|
||||
'platform': os.uname().sysname
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting system info: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {}
|
||||
|
||||
# Create instance for direct function access
|
||||
_shell_tools = ShellTools()
|
||||
execute_command = _shell_tools.execute_command
|
||||
list_processes = _shell_tools.list_processes
|
||||
kill_process = _shell_tools.kill_process
|
||||
get_system_info = _shell_tools.get_system_info
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("ShellTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Execute command
|
||||
print("1. Command Execution")
|
||||
print("------------------------------")
|
||||
result = execute_command("ls -la")
|
||||
print(f"Success: {result['success']}")
|
||||
print(f"Output:\n{result['stdout']}")
|
||||
if result['stderr']:
|
||||
print(f"Errors:\n{result['stderr']}")
|
||||
print(f"Execution time: {result['execution_time']:.2f}s")
|
||||
print()
|
||||
|
||||
# 2. System Information
|
||||
print("2. System Information")
|
||||
print("------------------------------")
|
||||
info = get_system_info()
|
||||
print(f"CPU Usage: {info['cpu']['percent']}%")
|
||||
print(f"Memory Usage: {info['memory']['percent']}%")
|
||||
print(f"Disk Usage: {info['disk']['percent']}%")
|
||||
print(f"Platform: {info['platform']}")
|
||||
print()
|
||||
|
||||
# 3. Process List
|
||||
print("3. Process List (top 5 by CPU)")
|
||||
print("------------------------------")
|
||||
processes = sorted(
|
||||
list_processes(),
|
||||
key=lambda x: x['cpu_percent'],
|
||||
reverse=True
|
||||
)[:5]
|
||||
for proc in processes:
|
||||
print(f"PID: {proc['pid']}, Name: {proc['name']}, CPU: {proc['cpu_percent']}%")
|
||||
print()
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,431 @@
|
||||
"""Tools for web scraping and crawling.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import spider_tools
|
||||
content = spider_tools.scrape_page("https://example.com")
|
||||
links = spider_tools.extract_links("https://example.com")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import scrape_page, extract_links
|
||||
content = scrape_page("https://example.com")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import json
|
||||
from urllib.parse import urljoin, urlparse
|
||||
import re
|
||||
import os
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
class SpiderTools:
|
||||
"""Tools for web scraping and crawling."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize SpiderTools and check for required packages."""
|
||||
self._session = None
|
||||
|
||||
def _get_session(self):
|
||||
"""Get or create requests session with common headers."""
|
||||
if util.find_spec('requests') is None:
|
||||
error_msg = "requests package is not available. Please install it using: pip install requests"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
if self._session is None:
|
||||
import requests
|
||||
self._session = requests.Session()
|
||||
self._session.headers.update({
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; PraisonAI/1.0; +http://praisonai.com/bot)',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
})
|
||||
return self._session
|
||||
|
||||
def scrape_page(
|
||||
self,
|
||||
url: str,
|
||||
selector: Optional[str] = None,
|
||||
extract_images: bool = False,
|
||||
extract_links: bool = False,
|
||||
timeout: int = 30,
|
||||
verify_ssl: bool = True
|
||||
) -> Union[Dict[str, Any], Dict[str, str]]:
|
||||
"""
|
||||
Scrape content from a webpage.
|
||||
|
||||
Args:
|
||||
url: URL to scrape
|
||||
selector: Optional CSS selector to extract specific content
|
||||
extract_images: Whether to extract image URLs
|
||||
extract_links: Whether to extract links
|
||||
timeout: Request timeout in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
|
||||
Returns:
|
||||
Dict: Scraped content or error dict
|
||||
"""
|
||||
try:
|
||||
session = self._get_session()
|
||||
if session is None:
|
||||
return {"error": "requests package not available"}
|
||||
|
||||
# Import BeautifulSoup only when needed
|
||||
if util.find_spec('bs4') is None:
|
||||
error_msg = "bs4 package is not available. Please install it using: pip install beautifulsoup4"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Make request
|
||||
response = session.get(
|
||||
url,
|
||||
timeout=timeout,
|
||||
verify=verify_ssl
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Parse HTML
|
||||
soup = BeautifulSoup(response.text, 'lxml')
|
||||
|
||||
# Remove unwanted elements
|
||||
for element in soup(['script', 'style']):
|
||||
element.decompose()
|
||||
|
||||
# Initialize result
|
||||
result = {
|
||||
'url': url,
|
||||
'status_code': response.status_code,
|
||||
'encoding': response.encoding,
|
||||
'headers': dict(response.headers),
|
||||
}
|
||||
|
||||
# Extract content based on selector
|
||||
if selector:
|
||||
elements = soup.select(selector)
|
||||
result['content'] = [elem.get_text(strip=True) for elem in elements]
|
||||
result['html'] = [str(elem) for elem in elements]
|
||||
else:
|
||||
result['title'] = soup.title.string if soup.title else None
|
||||
result['content'] = soup.get_text(separator=' ', strip=True)
|
||||
result['html'] = str(soup)
|
||||
|
||||
# Extract metadata
|
||||
meta_tags = {}
|
||||
for meta in soup.find_all('meta'):
|
||||
name = meta.get('name') or meta.get('property')
|
||||
if name:
|
||||
meta_tags[name] = meta.get('content')
|
||||
result['meta_tags'] = meta_tags
|
||||
|
||||
# Extract images if requested
|
||||
if extract_images:
|
||||
images = []
|
||||
for img in soup.find_all('img'):
|
||||
src = img.get('src')
|
||||
if src:
|
||||
images.append({
|
||||
'src': urljoin(url, src),
|
||||
'alt': img.get('alt', ''),
|
||||
'title': img.get('title', '')
|
||||
})
|
||||
result['images'] = images
|
||||
|
||||
# Extract links if requested
|
||||
if extract_links:
|
||||
links = []
|
||||
for link in soup.find_all('a'):
|
||||
href = link.get('href')
|
||||
if href:
|
||||
links.append({
|
||||
'url': urljoin(url, href),
|
||||
'text': link.get_text(strip=True),
|
||||
'title': link.get('title', '')
|
||||
})
|
||||
result['links'] = links
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error scraping {url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def extract_links(
|
||||
self,
|
||||
url: str,
|
||||
same_domain: bool = True,
|
||||
exclude_patterns: Optional[List[str]] = None,
|
||||
timeout: int = 30,
|
||||
verify_ssl: bool = True
|
||||
) -> Union[List[Dict[str, str]], Dict[str, str]]:
|
||||
"""
|
||||
Extract all links from a webpage.
|
||||
|
||||
Args:
|
||||
url: URL to extract links from
|
||||
same_domain: Only return links from the same domain
|
||||
exclude_patterns: List of regex patterns to exclude
|
||||
timeout: Request timeout in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: List of links or error dict
|
||||
"""
|
||||
try:
|
||||
# Compile exclude patterns
|
||||
if exclude_patterns:
|
||||
exclude_patterns = [re.compile(p) for p in exclude_patterns]
|
||||
|
||||
# Get base domain
|
||||
base_domain = urlparse(url).netloc
|
||||
|
||||
# Scrape page
|
||||
result = self.scrape_page(
|
||||
url,
|
||||
extract_links=True,
|
||||
timeout=timeout,
|
||||
verify_ssl=verify_ssl
|
||||
)
|
||||
|
||||
if "error" in result:
|
||||
return result
|
||||
|
||||
# Filter and clean links
|
||||
links = []
|
||||
seen_urls = set()
|
||||
|
||||
for link in result.get('links', []):
|
||||
link_url = link['url']
|
||||
|
||||
# Skip if already seen
|
||||
if link_url in seen_urls:
|
||||
continue
|
||||
|
||||
# Parse URL
|
||||
parsed = urlparse(link_url)
|
||||
|
||||
# Skip if not same domain and same_domain is True
|
||||
if same_domain and parsed.netloc != base_domain:
|
||||
continue
|
||||
|
||||
# Skip if matches exclude patterns
|
||||
if exclude_patterns and any(p.search(link_url) for p in exclude_patterns):
|
||||
continue
|
||||
|
||||
# Add to results
|
||||
links.append(link)
|
||||
seen_urls.add(link_url)
|
||||
|
||||
return links
|
||||
except Exception as e:
|
||||
error_msg = f"Error extracting links from {url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def crawl(
|
||||
self,
|
||||
start_url: str,
|
||||
max_pages: int = 10,
|
||||
same_domain: bool = True,
|
||||
exclude_patterns: Optional[List[str]] = None,
|
||||
delay: float = 1.0,
|
||||
timeout: int = 30,
|
||||
verify_ssl: bool = True,
|
||||
output_dir: Optional[str] = None
|
||||
) -> Union[List[Dict[str, Any]], Dict[str, str]]:
|
||||
"""
|
||||
Crawl multiple pages starting from a URL.
|
||||
|
||||
Args:
|
||||
start_url: Starting URL
|
||||
max_pages: Maximum number of pages to crawl
|
||||
same_domain: Only crawl pages from the same domain
|
||||
exclude_patterns: List of regex patterns to exclude
|
||||
delay: Delay between requests in seconds
|
||||
timeout: Request timeout in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
output_dir: Directory to save crawled pages
|
||||
|
||||
Returns:
|
||||
List[Dict] or Dict: Crawled pages or error dict
|
||||
"""
|
||||
try:
|
||||
# Create output directory if needed
|
||||
if output_dir:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Initialize crawl state
|
||||
to_visit = {start_url}
|
||||
visited = set()
|
||||
results = []
|
||||
|
||||
while to_visit and len(visited) < max_pages:
|
||||
# Get next URL
|
||||
url = to_visit.pop()
|
||||
|
||||
# Skip if already visited
|
||||
if url in visited:
|
||||
continue
|
||||
|
||||
# Add to visited
|
||||
visited.add(url)
|
||||
|
||||
# Delay if not first request
|
||||
if len(visited) > 1:
|
||||
time.sleep(delay)
|
||||
|
||||
# Scrape page
|
||||
result = self.scrape_page(
|
||||
url,
|
||||
extract_links=True,
|
||||
timeout=timeout,
|
||||
verify_ssl=verify_ssl
|
||||
)
|
||||
|
||||
if "error" in result:
|
||||
logging.warning(f"Error crawling {url}: {result['error']}")
|
||||
continue
|
||||
|
||||
# Save result
|
||||
results.append(result)
|
||||
|
||||
# Save to file if requested
|
||||
if output_dir:
|
||||
filename = hashlib.md5(url.encode()).hexdigest() + '.json'
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Add new links to visit
|
||||
for link in result.get('links', []):
|
||||
link_url = link['url']
|
||||
parsed = urlparse(link_url)
|
||||
|
||||
# Skip if not same domain and same_domain is True
|
||||
if same_domain and parsed.netloc != urlparse(start_url).netloc:
|
||||
continue
|
||||
|
||||
# Skip if matches exclude patterns
|
||||
if exclude_patterns and any(
|
||||
re.compile(p).search(link_url) for p in exclude_patterns
|
||||
):
|
||||
continue
|
||||
|
||||
# Add to visit if not visited
|
||||
if link_url not in visited:
|
||||
to_visit.add(link_url)
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error crawling from {start_url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def extract_text(
|
||||
self,
|
||||
url: str,
|
||||
selector: Optional[str] = None,
|
||||
timeout: int = 30,
|
||||
verify_ssl: bool = True
|
||||
) -> Union[str, Dict[str, str]]:
|
||||
"""
|
||||
Extract clean text content from a webpage.
|
||||
|
||||
Args:
|
||||
url: URL to extract text from
|
||||
selector: Optional CSS selector to extract specific content
|
||||
timeout: Request timeout in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
|
||||
Returns:
|
||||
str or Dict: Extracted text or error dict
|
||||
"""
|
||||
try:
|
||||
result = self.scrape_page(
|
||||
url,
|
||||
selector=selector,
|
||||
timeout=timeout,
|
||||
verify_ssl=verify_ssl
|
||||
)
|
||||
|
||||
if "error" in result:
|
||||
return result
|
||||
|
||||
if selector:
|
||||
return '\n'.join(result['content'])
|
||||
return result['content']
|
||||
except Exception as e:
|
||||
error_msg = f"Error extracting text from {url}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
# Create instance for direct function access
|
||||
_spider_tools = SpiderTools()
|
||||
scrape_page = _spider_tools.scrape_page
|
||||
extract_links = _spider_tools.extract_links
|
||||
crawl = _spider_tools.crawl
|
||||
extract_text = _spider_tools.extract_text
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("SpiderTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Scrape a webpage
|
||||
print("1. Scraping Webpage")
|
||||
print("------------------------------")
|
||||
url = "https://example.com"
|
||||
result = scrape_page(url, extract_images=True, extract_links=True)
|
||||
print(f"Content from {url}:")
|
||||
if "error" not in result:
|
||||
print(f"Title: {result['title']}")
|
||||
print(f"Content length: {len(result['content'])} characters")
|
||||
print(f"Number of images: {len(result.get('images', []))}")
|
||||
print(f"Number of links: {len(result.get('links', []))}")
|
||||
else:
|
||||
print(result) # Show error
|
||||
print()
|
||||
|
||||
# 2. Extract links
|
||||
print("2. Extracting Links")
|
||||
print("------------------------------")
|
||||
links = extract_links(url)
|
||||
print(f"Links from {url}:")
|
||||
if isinstance(links, list):
|
||||
for link in links:
|
||||
print(f"- {link['url']} ({link['text']})")
|
||||
else:
|
||||
print(links) # Show error
|
||||
print()
|
||||
|
||||
# 3. Extract text
|
||||
print("3. Extracting Text")
|
||||
print("------------------------------")
|
||||
text = extract_text(url)
|
||||
print(f"Text from {url}:")
|
||||
if isinstance(text, str):
|
||||
print(text[:500] + "..." if len(text) > 500 else text)
|
||||
else:
|
||||
print(text) # Show error
|
||||
print()
|
||||
|
||||
# 4. Crawl multiple pages
|
||||
print("4. Crawling Multiple Pages")
|
||||
print("------------------------------")
|
||||
results = crawl(url, max_pages=2, delay=1.0)
|
||||
print(f"Crawl results from {url}:")
|
||||
if isinstance(results, list):
|
||||
print(f"Crawled {len(results)} pages")
|
||||
for result in results:
|
||||
print(f"- {result['url']}: {result['title']}")
|
||||
else:
|
||||
print(results) # Show error
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test runner for all tools."""
|
||||
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def main():
|
||||
"""Run all tool files."""
|
||||
logger.info("Starting tests...")
|
||||
|
||||
# Get all *_tools.py files
|
||||
tools_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
tool_files = glob.glob(os.path.join(tools_dir, "*_tools.py"))
|
||||
|
||||
# Run each tool file
|
||||
for tool_file in sorted(tool_files):
|
||||
module_name = os.path.basename(tool_file)
|
||||
logger.info(f"\nRunning {module_name}...")
|
||||
|
||||
try:
|
||||
# Run the tool file directly
|
||||
result = subprocess.run(
|
||||
["python3", tool_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=tools_dir
|
||||
)
|
||||
|
||||
# Log output
|
||||
if result.stdout:
|
||||
logger.info(f"Output:\n{result.stdout}")
|
||||
if result.stderr:
|
||||
logger.error(f"Errors:\n{result.stderr}")
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f" {module_name} completed successfully")
|
||||
else:
|
||||
logger.error(f" {module_name} failed with return code {result.returncode}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error running {module_name}: {str(e)}")
|
||||
continue
|
||||
|
||||
logger.info("\nAll tests completed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Tools module for PraisonAI Agents"""
|
||||
from .duckduckgo_tools import internet_search
|
||||
|
||||
class Tools:
|
||||
"""Tools class for backward compatibility"""
|
||||
internet_search = staticmethod(internet_search)
|
||||
|
||||
# Re-export the function
|
||||
__all__ = ['Tools', 'internet_search']
|
||||
@@ -0,0 +1,272 @@
|
||||
"""Wikipedia tools for accessing and searching Wikipedia content.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import wikipedia_tools
|
||||
summary = wikipedia_tools.wiki_summary("Python programming language")
|
||||
page = wikipedia_tools.wiki_page("Python programming language")
|
||||
results = wikipedia_tools.wiki_search("Python programming")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import wiki_search, wiki_summary, wiki_page
|
||||
summary = wiki_summary("Python programming language")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Any
|
||||
from importlib import util
|
||||
import json
|
||||
|
||||
class WikipediaTools:
|
||||
"""Tools for accessing and searching Wikipedia content."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize WikipediaTools and check for wikipedia package."""
|
||||
self._check_wikipedia()
|
||||
|
||||
def _check_wikipedia(self):
|
||||
"""Check if wikipedia package is installed."""
|
||||
if util.find_spec("wikipedia") is None:
|
||||
raise ImportError("wikipedia package is not available. Please install it using: pip install wikipedia")
|
||||
global wikipedia
|
||||
import wikipedia
|
||||
# Set default language to English
|
||||
wikipedia.set_lang("en")
|
||||
|
||||
def wiki_search(
|
||||
self,
|
||||
query: str,
|
||||
results: int = 10,
|
||||
suggestion: bool = True
|
||||
) -> Union[List[str], Dict[str, str]]:
|
||||
"""
|
||||
Search Wikipedia for a query.
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
results: Maximum number of results to return
|
||||
suggestion: Whether to suggest similar queries
|
||||
|
||||
Returns:
|
||||
List[str] or Dict: List of search results or error dict
|
||||
"""
|
||||
try:
|
||||
search_results = wikipedia.search(query, results=results, suggestion=suggestion)
|
||||
if isinstance(search_results, tuple):
|
||||
# If suggestion is True, returns (results, suggestion)
|
||||
return {
|
||||
"results": search_results[0],
|
||||
"suggestion": search_results[1] if search_results[1] else None
|
||||
}
|
||||
return {"results": search_results, "suggestion": None}
|
||||
except Exception as e:
|
||||
error_msg = f"Error searching Wikipedia for '{query}': {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def wiki_summary(
|
||||
self,
|
||||
title: str,
|
||||
sentences: int = 5,
|
||||
auto_suggest: bool = True
|
||||
) -> Union[str, Dict[str, str]]:
|
||||
"""
|
||||
Get a summary of a Wikipedia page.
|
||||
|
||||
Args:
|
||||
title: Title of the Wikipedia page
|
||||
sentences: Number of sentences to return
|
||||
auto_suggest: Whether to auto-suggest similar titles
|
||||
|
||||
Returns:
|
||||
str or Dict: Page summary if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
return wikipedia.summary(title, sentences=sentences, auto_suggest=auto_suggest)
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
return {
|
||||
"error": "Disambiguation page",
|
||||
"options": e.options[:10] # Limit to first 10 options
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting summary for '{title}': {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def wiki_page(
|
||||
self,
|
||||
title: str,
|
||||
auto_suggest: bool = True
|
||||
) -> Union[Dict[str, Any], Dict[str, str]]:
|
||||
"""
|
||||
Get detailed information about a Wikipedia page.
|
||||
|
||||
Args:
|
||||
title: Title of the Wikipedia page
|
||||
auto_suggest: Whether to auto-suggest similar titles
|
||||
|
||||
Returns:
|
||||
Dict: Page information if successful, error dict if failed
|
||||
"""
|
||||
try:
|
||||
page = wikipedia.page(title, auto_suggest=auto_suggest)
|
||||
return {
|
||||
"title": page.title,
|
||||
"url": page.url,
|
||||
"content": page.content,
|
||||
"summary": page.summary,
|
||||
"references": page.references,
|
||||
"categories": page.categories,
|
||||
"links": page.links,
|
||||
"images": page.images,
|
||||
"sections": self.get_sections(page)
|
||||
}
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
return {
|
||||
"error": "Disambiguation page",
|
||||
"options": e.options[:10] # Limit to first 10 options
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting page for '{title}': {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_sections(self, page: Any) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract sections and their content from a Wikipedia page.
|
||||
|
||||
Args:
|
||||
page: Wikipedia page object
|
||||
|
||||
Returns:
|
||||
List[Dict]: List of sections with their titles and content
|
||||
"""
|
||||
sections = []
|
||||
content = page.content
|
||||
section_titles = [s.strip() for s in content.split("==")[1::2]]
|
||||
section_contents = [s.strip() for s in content.split("==")[2::2]]
|
||||
|
||||
# Add introduction section
|
||||
intro = content.split("==")[0].strip()
|
||||
if intro:
|
||||
sections.append({
|
||||
"title": "Introduction",
|
||||
"level": 0,
|
||||
"content": intro
|
||||
})
|
||||
|
||||
# Process remaining sections
|
||||
for title, content in zip(section_titles, section_contents):
|
||||
# Count leading '=' to determine section level
|
||||
level = title.count("=") // 2
|
||||
clean_title = title.strip("= ")
|
||||
|
||||
sections.append({
|
||||
"title": clean_title,
|
||||
"level": level,
|
||||
"content": content.strip()
|
||||
})
|
||||
|
||||
return sections
|
||||
|
||||
def wiki_random(self, pages: int = 1) -> Union[List[str], Dict[str, str]]:
|
||||
"""
|
||||
Get random Wikipedia page titles.
|
||||
|
||||
Args:
|
||||
pages: Number of random pages to return
|
||||
|
||||
Returns:
|
||||
List[str] or Dict: List of random page titles or error dict
|
||||
"""
|
||||
try:
|
||||
return wikipedia.random(pages)
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting random pages: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def wiki_language(self, language: str) -> bool:
|
||||
"""
|
||||
Set the language for Wikipedia searches.
|
||||
|
||||
Args:
|
||||
language: Language code (e.g., 'en' for English, 'es' for Spanish)
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False if failed
|
||||
"""
|
||||
try:
|
||||
wikipedia.set_lang(language)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting language to '{language}': {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Create instance for direct function access
|
||||
_wikipedia_tools = WikipediaTools()
|
||||
wiki_search = _wikipedia_tools.wiki_search
|
||||
wiki_summary = _wikipedia_tools.wiki_summary
|
||||
wiki_page = _wikipedia_tools.wiki_page
|
||||
wiki_random = _wikipedia_tools.wiki_random
|
||||
wiki_language = _wikipedia_tools.wiki_language
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("WikipediaTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# 1. Search for a topic
|
||||
print("1. Searching Wikipedia")
|
||||
print("------------------------------")
|
||||
search_results = wiki_search("Python programming language")
|
||||
print("Search results:")
|
||||
print(json.dumps(search_results, indent=2))
|
||||
print()
|
||||
|
||||
# 2. Get a page summary
|
||||
print("2. Getting Page Summary")
|
||||
print("------------------------------")
|
||||
summary = wiki_summary("Python programming language", sentences=3)
|
||||
print("Summary:")
|
||||
print(summary)
|
||||
print()
|
||||
|
||||
# 3. Get detailed page information
|
||||
print("3. Getting Full Page Information")
|
||||
print("------------------------------")
|
||||
page = wiki_page("Python programming language")
|
||||
if isinstance(page, dict) and "error" not in page:
|
||||
print("Page sections:")
|
||||
for section in page["sections"]:
|
||||
print(f"- {section['title']} (Level {section['level']})")
|
||||
print(f"\nNumber of references: {len(page['references'])}")
|
||||
print(f"Number of categories: {len(page['categories'])}")
|
||||
print(f"Number of links: {len(page['links'])}")
|
||||
print(f"Number of images: {len(page['images'])}")
|
||||
else:
|
||||
print("Error getting page:", page.get("error"))
|
||||
print()
|
||||
|
||||
# 4. Get random pages
|
||||
print("4. Getting Random Pages")
|
||||
print("------------------------------")
|
||||
random_pages = wiki_random(3)
|
||||
print("Random page titles:")
|
||||
print(json.dumps(random_pages, indent=2))
|
||||
print()
|
||||
|
||||
# 5. Try different language
|
||||
print("5. Changing Language")
|
||||
print("------------------------------")
|
||||
success = wiki_language("es")
|
||||
if success:
|
||||
summary = wiki_summary("Python (lenguaje de programación)", sentences=1)
|
||||
print("Spanish summary:")
|
||||
print(summary)
|
||||
print()
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,498 @@
|
||||
"""Tools for working with XML files.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import xml_tools
|
||||
tree = xml_tools.read_xml("data.xml")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_xml, write_xml, transform_xml
|
||||
tree = read_xml("data.xml")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any, Tuple
|
||||
from importlib import util
|
||||
import xml.etree.ElementTree as ET
|
||||
import xml.dom.minidom as minidom
|
||||
from io import StringIO
|
||||
import json
|
||||
|
||||
class XMLTools:
|
||||
"""Tools for working with XML files."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize XMLTools."""
|
||||
pass
|
||||
|
||||
def read_xml(
|
||||
self,
|
||||
filepath: str,
|
||||
encoding: str = 'utf-8',
|
||||
validate_schema: Optional[str] = None,
|
||||
parser: str = 'lxml'
|
||||
) -> ET.Element:
|
||||
"""Read an XML file with optional schema validation.
|
||||
|
||||
Args:
|
||||
filepath: Path to XML file
|
||||
encoding: File encoding
|
||||
validate_schema: Optional path to XSD schema file
|
||||
parser: XML parser to use ('lxml' or 'etree')
|
||||
|
||||
Returns:
|
||||
ElementTree root element
|
||||
"""
|
||||
try:
|
||||
if parser == 'lxml':
|
||||
if util.find_spec('lxml') is None:
|
||||
error_msg = "lxml package is not available. Please install it using: pip install lxml"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import lxml.etree as lxml_etree
|
||||
tree = lxml_etree.parse(filepath)
|
||||
root = tree.getroot()
|
||||
else:
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
|
||||
if validate_schema:
|
||||
if util.find_spec('xmlschema') is None:
|
||||
error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import xmlschema
|
||||
schema = xmlschema.XMLSchema(validate_schema)
|
||||
if not schema.is_valid(filepath):
|
||||
error_msg = f"XML file does not validate against schema: {schema.validate(filepath)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
return root
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading XML file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def write_xml(
|
||||
self,
|
||||
root: ET.Element,
|
||||
filepath: str,
|
||||
encoding: str = 'utf-8',
|
||||
pretty: bool = True,
|
||||
xml_declaration: bool = True
|
||||
) -> bool:
|
||||
"""Write XML Element tree to file.
|
||||
|
||||
Args:
|
||||
root: XML Element tree root
|
||||
filepath: Output file path
|
||||
encoding: File encoding
|
||||
pretty: Format output with proper indentation
|
||||
xml_declaration: Include XML declaration
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Convert to string
|
||||
if pretty:
|
||||
xml_str = minidom.parseString(
|
||||
ET.tostring(root, encoding='unicode')
|
||||
).toprettyxml(indent=' ')
|
||||
else:
|
||||
xml_str = ET.tostring(
|
||||
root,
|
||||
encoding='unicode'
|
||||
)
|
||||
|
||||
# Add declaration if requested
|
||||
if xml_declaration:
|
||||
if not xml_str.startswith('<?xml'):
|
||||
xml_str = (
|
||||
f'<?xml version="1.0" encoding="{encoding}"?>\n'
|
||||
+ xml_str
|
||||
)
|
||||
|
||||
# Write to file
|
||||
with open(filepath, 'w', encoding=encoding) as f:
|
||||
f.write(xml_str)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing XML file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def transform_xml(
|
||||
self,
|
||||
xml_file: str,
|
||||
xslt_file: str,
|
||||
output_file: str,
|
||||
params: Optional[Dict[str, str]] = None
|
||||
) -> bool:
|
||||
"""Transform XML using XSLT stylesheet.
|
||||
|
||||
Args:
|
||||
xml_file: Input XML file
|
||||
xslt_file: XSLT stylesheet file
|
||||
output_file: Output file path
|
||||
params: Optional parameters for transformation
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Parse XML and XSLT
|
||||
if util.find_spec('lxml') is None:
|
||||
error_msg = "lxml package is not available. Please install it using: pip install lxml"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
import lxml.etree as lxml_etree
|
||||
xml_doc = lxml_etree.parse(xml_file)
|
||||
xslt_doc = lxml_etree.parse(xslt_file)
|
||||
transform = lxml_etree.XSLT(xslt_doc)
|
||||
|
||||
# Apply transformation
|
||||
if params:
|
||||
result = transform(xml_doc, **params)
|
||||
else:
|
||||
result = transform(xml_doc)
|
||||
|
||||
# Write result
|
||||
result.write(
|
||||
output_file,
|
||||
pretty_print=True,
|
||||
xml_declaration=True,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error transforming XML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def validate_xml(
|
||||
self,
|
||||
xml_file: str,
|
||||
schema_file: str
|
||||
) -> Tuple[bool, Optional[str]]:
|
||||
"""Validate XML against XSD schema.
|
||||
|
||||
Args:
|
||||
xml_file: XML file to validate
|
||||
schema_file: XSD schema file
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_message)
|
||||
"""
|
||||
try:
|
||||
if util.find_spec('xmlschema') is None:
|
||||
error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
|
||||
logging.error(error_msg)
|
||||
return False, error_msg
|
||||
import xmlschema
|
||||
schema = xmlschema.XMLSchema(schema_file)
|
||||
schema.validate(xml_file)
|
||||
return True, None
|
||||
except xmlschema.validators.exceptions.XMLSchemaValidationError as e:
|
||||
return False, str(e)
|
||||
except Exception as e:
|
||||
error_msg = f"Error validating XML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
def xml_to_dict(
|
||||
self,
|
||||
root: Union[str, ET.Element],
|
||||
preserve_attrs: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""Convert XML to dictionary.
|
||||
|
||||
Args:
|
||||
root: XML string or Element tree root
|
||||
preserve_attrs: Keep XML attributes in result
|
||||
|
||||
Returns:
|
||||
Dict representation of XML
|
||||
"""
|
||||
try:
|
||||
# Parse XML if string
|
||||
if isinstance(root, str):
|
||||
if root.startswith('<'):
|
||||
root = ET.fromstring(root)
|
||||
else:
|
||||
root = ET.parse(root).getroot()
|
||||
|
||||
result = {}
|
||||
|
||||
# Add attributes if present and requested
|
||||
if preserve_attrs and root.attrib:
|
||||
result['@attributes'] = dict(root.attrib)
|
||||
|
||||
# Add children
|
||||
children = list(root)
|
||||
if not children:
|
||||
text = root.text
|
||||
if text is not None and text.strip():
|
||||
result = text.strip()
|
||||
else:
|
||||
for child in children:
|
||||
child_data = self.xml_to_dict(child, preserve_attrs)
|
||||
if child.tag in result:
|
||||
if not isinstance(result[child.tag], list):
|
||||
result[child.tag] = [result[child.tag]]
|
||||
result[child.tag].append(child_data)
|
||||
else:
|
||||
result[child.tag] = child_data
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error converting XML to dict: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {}
|
||||
|
||||
def dict_to_xml(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
root_tag: str = 'root'
|
||||
) -> Optional[ET.Element]:
|
||||
"""Convert dictionary to XML.
|
||||
|
||||
Args:
|
||||
data: Dictionary to convert
|
||||
root_tag: Tag for root element
|
||||
|
||||
Returns:
|
||||
XML Element tree root
|
||||
"""
|
||||
try:
|
||||
def _create_element(
|
||||
parent: ET.Element,
|
||||
key: str,
|
||||
value: Any
|
||||
):
|
||||
"""Create XML element from key-value pair."""
|
||||
if key == '@attributes':
|
||||
for attr_key, attr_val in value.items():
|
||||
parent.set(attr_key, str(attr_val))
|
||||
elif isinstance(value, dict):
|
||||
child = ET.SubElement(parent, key)
|
||||
for k, v in value.items():
|
||||
_create_element(child, k, v)
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
child = ET.SubElement(parent, key)
|
||||
if isinstance(item, dict):
|
||||
for k, v in item.items():
|
||||
_create_element(child, k, v)
|
||||
else:
|
||||
child.text = str(item)
|
||||
else:
|
||||
child = ET.SubElement(parent, key)
|
||||
child.text = str(value)
|
||||
|
||||
root = ET.Element(root_tag)
|
||||
for key, value in data.items():
|
||||
_create_element(root, key, value)
|
||||
|
||||
return root
|
||||
except Exception as e:
|
||||
error_msg = f"Error converting dict to XML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def xpath_query(
|
||||
self,
|
||||
root: Union[str, ET.Element],
|
||||
query: str,
|
||||
namespaces: Optional[Dict[str, str]] = None
|
||||
) -> List[ET.Element]:
|
||||
"""Execute XPath query on XML.
|
||||
|
||||
Args:
|
||||
root: XML string or Element tree root
|
||||
query: XPath query string
|
||||
namespaces: Optional namespace mappings
|
||||
|
||||
Returns:
|
||||
List of matching elements
|
||||
"""
|
||||
try:
|
||||
# Parse XML if string
|
||||
if isinstance(root, str):
|
||||
if root.startswith('<'):
|
||||
if util.find_spec('lxml') is None:
|
||||
error_msg = "lxml package is not available. Please install it using: pip install lxml"
|
||||
logging.error(error_msg)
|
||||
return []
|
||||
import lxml.etree as lxml_etree
|
||||
tree = lxml_etree.fromstring(root)
|
||||
else:
|
||||
tree = ET.parse(root)
|
||||
else:
|
||||
if util.find_spec('lxml') is None:
|
||||
error_msg = "lxml package is not available. Please install it using: pip install lxml"
|
||||
logging.error(error_msg)
|
||||
return []
|
||||
import lxml.etree as lxml_etree
|
||||
tree = lxml_etree.fromstring(
|
||||
ET.tostring(root, encoding='unicode')
|
||||
)
|
||||
|
||||
# Execute query
|
||||
results = tree.xpath(
|
||||
query,
|
||||
namespaces=namespaces or {}
|
||||
)
|
||||
|
||||
# Convert results to standard ElementTree elements
|
||||
return [
|
||||
ET.fromstring(lxml_etree.tostring(elem, encoding='unicode'))
|
||||
for elem in results
|
||||
]
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing XPath query: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return []
|
||||
|
||||
# Create instance for direct function access
|
||||
_xml_tools = XMLTools()
|
||||
read_xml = _xml_tools.read_xml
|
||||
write_xml = _xml_tools.write_xml
|
||||
transform_xml = _xml_tools.transform_xml
|
||||
validate_xml = _xml_tools.validate_xml
|
||||
xml_to_dict = _xml_tools.xml_to_dict
|
||||
dict_to_xml = _xml_tools.dict_to_xml
|
||||
xpath_query = _xml_tools.xpath_query
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n==================================================")
|
||||
print("XMLTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Create temporary files
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
temp_file = tempfile.mktemp(suffix='.xml')
|
||||
try:
|
||||
print("1. Creating XML Document")
|
||||
print("------------------------------")
|
||||
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bookstore>
|
||||
<book category="fiction">
|
||||
<title>The Great Gatsby</title>
|
||||
<author>F. Scott Fitzgerald</author>
|
||||
<year>1925</year>
|
||||
<price>10.99</price>
|
||||
</book>
|
||||
<book category="non-fiction">
|
||||
<title>A Brief History of Time</title>
|
||||
<author>Stephen Hawking</author>
|
||||
<year>1988</year>
|
||||
<price>15.99</price>
|
||||
</book>
|
||||
</bookstore>"""
|
||||
|
||||
with open(temp_file, 'w') as f:
|
||||
f.write(xml_content)
|
||||
print("Sample XML file created")
|
||||
print()
|
||||
|
||||
print("2. Parsing XML")
|
||||
print("------------------------------")
|
||||
result = read_xml(temp_file)
|
||||
print("XML structure:")
|
||||
print(minidom.parseString(ET.tostring(result, encoding='unicode')).toprettyxml(indent=' '))
|
||||
print()
|
||||
|
||||
print("3. Querying XML")
|
||||
print("------------------------------")
|
||||
xpath = "//book[@category='fiction']/title/text()"
|
||||
result = xpath_query(result, xpath)
|
||||
print(f"Fiction book titles (XPath: {xpath}):")
|
||||
for title in result:
|
||||
print(title.text)
|
||||
print()
|
||||
|
||||
print("4. Validating XML")
|
||||
print("------------------------------")
|
||||
schema_file = tempfile.mktemp(suffix='.xsd')
|
||||
schema_content = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="bookstore">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="book" maxOccurs="unbounded">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="title" type="xs:string"/>
|
||||
<xs:element name="author" type="xs:string"/>
|
||||
<xs:element name="year" type="xs:integer"/>
|
||||
<xs:element name="price" type="xs:decimal"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="category" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>"""
|
||||
with open(schema_file, 'w') as f:
|
||||
f.write(schema_content)
|
||||
result, error = validate_xml(temp_file, schema_file)
|
||||
print(f"XML validation result: {result}")
|
||||
if error:
|
||||
print(f"Error: {error}")
|
||||
print()
|
||||
|
||||
print("5. Transforming XML")
|
||||
print("------------------------------")
|
||||
xslt_content = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:template match="/">
|
||||
<html>
|
||||
<body>
|
||||
<h2>Bookstore Inventory</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Author</th>
|
||||
<th>Price</th>
|
||||
</tr>
|
||||
<xsl:for-each select="bookstore/book">
|
||||
<tr>
|
||||
<td><xsl:value-of select="title"/></td>
|
||||
<td><xsl:value-of select="author"/></td>
|
||||
<td><xsl:value-of select="price"/></td>
|
||||
</tr>
|
||||
</xsl:for-each>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>"""
|
||||
|
||||
xslt_file = tempfile.mktemp(suffix='.xslt')
|
||||
with open(xslt_file, 'w') as f:
|
||||
f.write(xslt_content)
|
||||
|
||||
output_file = tempfile.mktemp(suffix='.html')
|
||||
result = transform_xml(temp_file, xslt_file, output_file)
|
||||
print(f"XML transformation result: {result}")
|
||||
if result and os.path.exists(output_file):
|
||||
print("\nTransformed HTML content:")
|
||||
with open(output_file, 'r') as f:
|
||||
print(f.read())
|
||||
|
||||
finally:
|
||||
# Clean up temporary files
|
||||
for file in [temp_file, schema_file, xslt_file, output_file]:
|
||||
if os.path.exists(file):
|
||||
os.unlink(file)
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,417 @@
|
||||
"""Tools for working with YAML files.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import yaml_tools
|
||||
data = yaml_tools.read_yaml("config.yaml")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import read_yaml, write_yaml, merge_yaml
|
||||
data = read_yaml("config.yaml")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Union, Optional, Any
|
||||
from importlib import util
|
||||
import os
|
||||
from copy import deepcopy
|
||||
|
||||
class YAMLTools:
|
||||
"""Tools for working with YAML files."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize YAMLTools."""
|
||||
self._check_dependencies()
|
||||
|
||||
def _check_dependencies(self):
|
||||
"""Check if required packages are installed."""
|
||||
missing = []
|
||||
for package, module in [('pyyaml', 'yaml'), ('jsonschema', 'jsonschema')]:
|
||||
if util.find_spec(module) is None:
|
||||
missing.append(package)
|
||||
|
||||
if missing:
|
||||
raise ImportError(
|
||||
f"Required packages not available. Please install: {', '.join(missing)}\n"
|
||||
f"Run: pip install {' '.join(missing)}"
|
||||
)
|
||||
|
||||
def read_yaml(
|
||||
self,
|
||||
filepath: str,
|
||||
safe_load: bool = True,
|
||||
encoding: str = 'utf-8'
|
||||
) -> Optional[Union[Dict[str, Any], List[Any]]]:
|
||||
"""Read a YAML file."""
|
||||
try:
|
||||
if util.find_spec('yaml') is None:
|
||||
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
# Import yaml only when needed
|
||||
import yaml
|
||||
|
||||
with open(filepath, 'r', encoding=encoding) as f:
|
||||
if safe_load:
|
||||
return yaml.safe_load(f)
|
||||
return yaml.load(f, Loader=yaml.FullLoader)
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading YAML file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def write_yaml(
|
||||
self,
|
||||
data: Union[Dict[str, Any], List[Any]],
|
||||
filepath: str,
|
||||
default_flow_style: bool = False,
|
||||
encoding: str = 'utf-8',
|
||||
sort_keys: bool = False,
|
||||
allow_unicode: bool = True
|
||||
) -> bool:
|
||||
"""Write data to YAML file."""
|
||||
try:
|
||||
if util.find_spec('yaml') is None:
|
||||
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Import yaml only when needed
|
||||
import yaml
|
||||
|
||||
with open(filepath, 'w', encoding=encoding) as f:
|
||||
yaml.dump(
|
||||
data,
|
||||
f,
|
||||
default_flow_style=default_flow_style,
|
||||
sort_keys=sort_keys,
|
||||
allow_unicode=allow_unicode
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
error_msg = f"Error writing YAML file {filepath}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def merge_yaml(
|
||||
self,
|
||||
files: List[str],
|
||||
output_file: Optional[str] = None,
|
||||
strategy: str = 'deep'
|
||||
) -> Union[Dict[str, Any], List[Any], None]:
|
||||
"""Merge multiple YAML files."""
|
||||
try:
|
||||
if util.find_spec('yaml') is None:
|
||||
error_msg = "pyyaml package is not available. Please install it using: pip install pyyaml"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def deep_merge(source: dict, destination: dict) -> dict:
|
||||
"""Deep merge two dictionaries."""
|
||||
for key, value in source.items():
|
||||
if key in destination:
|
||||
if isinstance(value, dict) and isinstance(destination[key], dict):
|
||||
destination[key] = deep_merge(value, destination[key])
|
||||
elif isinstance(value, list) and isinstance(destination[key], list):
|
||||
destination[key].extend(value)
|
||||
else:
|
||||
destination[key] = value
|
||||
else:
|
||||
destination[key] = value
|
||||
return destination
|
||||
|
||||
result = {}
|
||||
for file in files:
|
||||
data = self.read_yaml(file)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
if strategy == 'deep':
|
||||
if isinstance(data, dict):
|
||||
result = deep_merge(data, result)
|
||||
elif isinstance(data, list):
|
||||
if not result:
|
||||
result = []
|
||||
result.extend(data)
|
||||
else: # shallow
|
||||
if isinstance(data, dict):
|
||||
result.update(data)
|
||||
elif isinstance(data, list):
|
||||
if not result:
|
||||
result = []
|
||||
result.extend(data)
|
||||
|
||||
if output_file:
|
||||
self.write_yaml(result, output_file)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error merging YAML files: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def validate_yaml(
|
||||
self,
|
||||
data: Union[Dict[str, Any], List[Any], str],
|
||||
schema: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""Validate YAML data against a schema."""
|
||||
try:
|
||||
if util.find_spec('jsonschema') is None:
|
||||
error_msg = "jsonschema package is not available. Please install it using: pip install jsonschema"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
# Import jsonschema only when needed
|
||||
from jsonschema import validate, ValidationError
|
||||
|
||||
# Load data if file path
|
||||
if isinstance(data, str):
|
||||
data = self.read_yaml(data)
|
||||
if data is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
validate(instance=data, schema=schema)
|
||||
return True
|
||||
except ValidationError as e:
|
||||
logging.error(f"YAML validation error: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
error_msg = f"Error validating YAML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return False
|
||||
|
||||
def analyze_yaml(
|
||||
self,
|
||||
data: Union[Dict[str, Any], List[Any], str]
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Analyze YAML data structure."""
|
||||
try:
|
||||
# Load data if file path
|
||||
if isinstance(data, str):
|
||||
data = self.read_yaml(data)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
def analyze_value(value: Any) -> Dict[str, Any]:
|
||||
"""Analyze a single value."""
|
||||
result = {
|
||||
'type': type(value).__name__,
|
||||
'size': len(value) if hasattr(value, '__len__') else 1
|
||||
}
|
||||
|
||||
if isinstance(value, dict):
|
||||
result['keys'] = list(value.keys())
|
||||
result['nested_types'] = {
|
||||
k: type(v).__name__
|
||||
for k, v in value.items()
|
||||
}
|
||||
elif isinstance(value, list):
|
||||
result['element_types'] = list(set(
|
||||
type(x).__name__ for x in value
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
result = {
|
||||
'structure': analyze_value(data),
|
||||
'stats': {
|
||||
'total_keys': sum(1 for _ in self._walk_dict(data))
|
||||
if isinstance(data, dict) else len(data)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error analyzing YAML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
def _walk_dict(self, d: Dict[str, Any]) -> Any:
|
||||
"""Walk through nested dictionary."""
|
||||
for k, v in d.items():
|
||||
yield k
|
||||
if isinstance(v, dict):
|
||||
yield from self._walk_dict(v)
|
||||
|
||||
def transform_yaml(
|
||||
self,
|
||||
data: Union[Dict[str, Any], List[Any], str],
|
||||
operations: List[Dict[str, Any]]
|
||||
) -> Optional[Union[Dict[str, Any], List[Any]]]:
|
||||
"""Transform YAML data using specified operations."""
|
||||
try:
|
||||
# Load data if file path
|
||||
if isinstance(data, str):
|
||||
data = self.read_yaml(data)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
result = deepcopy(data)
|
||||
|
||||
for op in operations:
|
||||
op_type = op.get('type')
|
||||
path = op.get('path', '')
|
||||
value = op.get('value')
|
||||
|
||||
if not op_type:
|
||||
continue
|
||||
|
||||
# Split path into parts
|
||||
parts = [p for p in path.split('/') if p]
|
||||
|
||||
# Get reference to target location
|
||||
target = result
|
||||
parent = None
|
||||
last_key = None
|
||||
|
||||
for part in parts[:-1]:
|
||||
if isinstance(target, dict):
|
||||
if part not in target:
|
||||
target[part] = {}
|
||||
parent = target
|
||||
target = target[part]
|
||||
last_key = part
|
||||
elif isinstance(target, list):
|
||||
idx = int(part)
|
||||
while len(target) <= idx:
|
||||
target.append({})
|
||||
parent = target
|
||||
target = target[idx]
|
||||
last_key = idx
|
||||
|
||||
if parts:
|
||||
last_part = parts[-1]
|
||||
if isinstance(target, dict):
|
||||
if op_type == 'set':
|
||||
target[last_part] = value
|
||||
elif op_type == 'delete':
|
||||
target.pop(last_part, None)
|
||||
elif op_type == 'append' and isinstance(target.get(last_part), list):
|
||||
target[last_part].append(value)
|
||||
elif isinstance(target, list):
|
||||
idx = int(last_part)
|
||||
if op_type == 'set':
|
||||
while len(target) <= idx:
|
||||
target.append(None)
|
||||
target[idx] = value
|
||||
elif op_type == 'delete' and idx < len(target):
|
||||
del target[idx]
|
||||
elif op_type == 'append':
|
||||
target.append(value)
|
||||
else:
|
||||
if op_type == 'set':
|
||||
result = value
|
||||
elif op_type == 'append' and isinstance(result, list):
|
||||
result.append(value)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
error_msg = f"Error transforming YAML: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
|
||||
# Create instance for direct function access
|
||||
_yaml_tools = YAMLTools()
|
||||
read_yaml = _yaml_tools.read_yaml
|
||||
write_yaml = _yaml_tools.write_yaml
|
||||
merge_yaml = _yaml_tools.merge_yaml
|
||||
validate_yaml = _yaml_tools.validate_yaml
|
||||
analyze_yaml = _yaml_tools.analyze_yaml
|
||||
transform_yaml = _yaml_tools.transform_yaml
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("YAMLTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
# Sample data
|
||||
data1 = {
|
||||
'server': {
|
||||
'host': 'localhost',
|
||||
'port': 8080,
|
||||
'debug': True
|
||||
},
|
||||
'database': {
|
||||
'url': 'postgresql://localhost:5432/db',
|
||||
'pool_size': 5
|
||||
}
|
||||
}
|
||||
|
||||
data2 = {
|
||||
'server': {
|
||||
'workers': 4,
|
||||
'timeout': 30
|
||||
},
|
||||
'logging': {
|
||||
'level': 'INFO',
|
||||
'file': 'app.log'
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Write YAML files
|
||||
print("1. Writing YAML Files")
|
||||
print("------------------------------")
|
||||
success1 = write_yaml(data1, 'config1.yaml')
|
||||
success2 = write_yaml(data2, 'config2.yaml')
|
||||
print(f"First file written: {success1}")
|
||||
print(f"Second file written: {success2}")
|
||||
print()
|
||||
|
||||
# 2. Read YAML file
|
||||
print("2. Reading YAML File")
|
||||
print("------------------------------")
|
||||
config = read_yaml('config1.yaml')
|
||||
print("YAML content:")
|
||||
print(config)
|
||||
print()
|
||||
|
||||
# 3. Merge YAML files
|
||||
print("3. Merging YAML Files")
|
||||
print("------------------------------")
|
||||
merged = merge_yaml(['config1.yaml', 'config2.yaml'])
|
||||
print("Merged content:")
|
||||
print(merged)
|
||||
print()
|
||||
|
||||
# 4. Analyze YAML
|
||||
print("4. Analyzing YAML")
|
||||
print("------------------------------")
|
||||
analysis = analyze_yaml(merged)
|
||||
print("Analysis results:")
|
||||
print(analysis)
|
||||
print()
|
||||
|
||||
# 5. Transform YAML
|
||||
print("5. Transforming YAML")
|
||||
print("------------------------------")
|
||||
operations = [
|
||||
{
|
||||
'type': 'set',
|
||||
'path': 'server/host',
|
||||
'value': '0.0.0.0'
|
||||
},
|
||||
{
|
||||
'type': 'delete',
|
||||
'path': 'server/debug'
|
||||
},
|
||||
{
|
||||
'type': 'set',
|
||||
'path': 'logging/handlers',
|
||||
'value': ['console', 'file']
|
||||
}
|
||||
]
|
||||
transformed = transform_yaml(merged, operations)
|
||||
print("Transformed content:")
|
||||
print(transformed)
|
||||
|
||||
print("\n==================================================")
|
||||
print("Demonstration Complete")
|
||||
print("==================================================")
|
||||
|
||||
# Cleanup
|
||||
for file in ['config1.yaml', 'config2.yaml']:
|
||||
if os.path.exists(file):
|
||||
os.remove(file)
|
||||
@@ -0,0 +1,213 @@
|
||||
"""YFinance tools for stock market data.
|
||||
|
||||
Usage:
|
||||
from praisonaiagents.tools import get_stock_price, get_stock_info
|
||||
price = get_stock_price("AAPL")
|
||||
info = get_stock_info("AAPL")
|
||||
|
||||
or
|
||||
from praisonaiagents.tools import yfinance
|
||||
price = yfinance.get_stock_price("AAPL")
|
||||
info = yfinance.get_stock_info("AAPL")
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional, Any
|
||||
import logging
|
||||
from importlib import util
|
||||
from datetime import datetime
|
||||
|
||||
class YFinanceTools:
|
||||
"""A comprehensive tool for financial data analysis using yfinance"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize YFinanceTools"""
|
||||
self._tickers = {}
|
||||
|
||||
def _get_yfinance(self):
|
||||
"""Get yfinance module, installing if needed"""
|
||||
if util.find_spec('yfinance') is None:
|
||||
error_msg = "yfinance package is not available. Please install it using: pip install yfinance"
|
||||
logging.error(error_msg)
|
||||
return None
|
||||
import yfinance as yf
|
||||
return yf
|
||||
|
||||
def _get_ticker(self, symbol: str):
|
||||
"""Get or create ticker instance"""
|
||||
if symbol not in self._tickers:
|
||||
yf = self._get_yfinance()
|
||||
if yf is None:
|
||||
return None
|
||||
self._tickers[symbol] = yf.Ticker(symbol)
|
||||
return self._tickers[symbol]
|
||||
|
||||
def get_stock_price(self, symbol: str) -> Dict[str, float]:
|
||||
"""
|
||||
Get current stock price and related price metrics
|
||||
|
||||
Args:
|
||||
symbol (str): Stock ticker symbol
|
||||
|
||||
Returns:
|
||||
Dict[str, float]: Current price information
|
||||
"""
|
||||
try:
|
||||
ticker = self._get_ticker(symbol)
|
||||
if ticker is None:
|
||||
return {"error": "yfinance package not available"}
|
||||
|
||||
info = ticker.info
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'price': info.get('regularMarketPrice', 0.0),
|
||||
'open': info.get('regularMarketOpen', 0.0),
|
||||
'high': info.get('regularMarketDayHigh', 0.0),
|
||||
'low': info.get('regularMarketDayLow', 0.0),
|
||||
'volume': info.get('regularMarketVolume', 0),
|
||||
'previous_close': info.get('regularMarketPreviousClose', 0.0),
|
||||
'change': info.get('regularMarketChange', 0.0),
|
||||
'change_percent': info.get('regularMarketChangePercent', 0.0)
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting stock price for {symbol}: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_stock_info(self, symbol: str) -> Dict:
|
||||
"""
|
||||
Get detailed information about a stock
|
||||
|
||||
Args:
|
||||
symbol (str): Stock ticker symbol
|
||||
|
||||
Returns:
|
||||
Dict: Stock information including company details and key metrics
|
||||
"""
|
||||
try:
|
||||
ticker = self._get_ticker(symbol)
|
||||
if ticker is None:
|
||||
return {"error": "yfinance package not available"}
|
||||
|
||||
info = ticker.info
|
||||
|
||||
relevant_info = {
|
||||
"longName": info.get("longName"),
|
||||
"symbol": info.get("symbol"),
|
||||
"sector": info.get("sector"),
|
||||
"industry": info.get("industry"),
|
||||
"country": info.get("country"),
|
||||
"marketCap": info.get("marketCap"),
|
||||
"currentPrice": info.get("currentPrice"),
|
||||
"currency": info.get("currency"),
|
||||
"exchange": info.get("exchange"),
|
||||
"fiftyTwoWeekHigh": info.get("fiftyTwoWeekHigh"),
|
||||
"fiftyTwoWeekLow": info.get("fiftyTwoWeekLow"),
|
||||
"trailingPE": info.get("trailingPE"),
|
||||
"forwardPE": info.get("forwardPE"),
|
||||
"dividendYield": info.get("dividendYield"),
|
||||
"beta": info.get("beta"),
|
||||
"volume": info.get("volume"),
|
||||
"averageVolume": info.get("averageVolume"),
|
||||
"sharesOutstanding": info.get("sharesOutstanding"),
|
||||
"website": info.get("website"),
|
||||
"longBusinessSummary": info.get("longBusinessSummary")
|
||||
}
|
||||
return {k: v for k, v in relevant_info.items() if v is not None}
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching stock info: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_historical_data(
|
||||
self,
|
||||
symbol: str,
|
||||
period: str = "1y",
|
||||
interval: str = "1d",
|
||||
start: Optional[datetime] = None,
|
||||
end: Optional[datetime] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get historical price data for a stock
|
||||
|
||||
Args:
|
||||
symbol (str): Stock ticker symbol
|
||||
period (str): Data period e.g. 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max
|
||||
interval (str): Data interval e.g. 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo
|
||||
start (datetime, optional): Start date for data
|
||||
end (datetime, optional): End date for data
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: List of historical price data points
|
||||
"""
|
||||
try:
|
||||
ticker = self._get_ticker(symbol)
|
||||
if ticker is None:
|
||||
return [{"error": "yfinance package not available"}]
|
||||
|
||||
hist = ticker.history(period=period, interval=interval, start=start, end=end)
|
||||
return hist.reset_index().to_dict('records')
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching historical data: {str(e)}"
|
||||
logging.error(error_msg)
|
||||
return [{"error": error_msg}]
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
print("\n==================================================")
|
||||
print("YFinanceTools Demonstration")
|
||||
print("==================================================\n")
|
||||
|
||||
try:
|
||||
yf_tools = YFinanceTools()
|
||||
|
||||
# 1. Basic Stock Information
|
||||
print("1. Basic Stock Information for AAPL")
|
||||
print("------------------------------")
|
||||
info = yf_tools.get_stock_info("AAPL")
|
||||
if "error" not in info:
|
||||
print(f"Company: {info.get('longName')}")
|
||||
print(f"Sector: {info.get('sector')}")
|
||||
print(f"Industry: {info.get('industry')}")
|
||||
print(f"Country: {info.get('country')}\n")
|
||||
else:
|
||||
print(f"Error: {info['error']}\n")
|
||||
|
||||
# 2. Current Price Data
|
||||
print("2. Current Price Data")
|
||||
print("------------------------------")
|
||||
price_data = yf_tools.get_stock_price("AAPL")
|
||||
if "error" not in price_data:
|
||||
print(f"Current Price: ${price_data.get('price', 'N/A')}")
|
||||
print(f"Previous Close: ${price_data.get('previous_close', 'N/A')}")
|
||||
print(f"Day Range: ${price_data.get('low', 'N/A')} - ${price_data.get('high', 'N/A')}\n")
|
||||
else:
|
||||
print(f"Error: {price_data['error']}\n")
|
||||
|
||||
# 3. Historical Data
|
||||
print("3. Historical Data (Last 5 Days)")
|
||||
print("------------------------------")
|
||||
hist_data = yf_tools.get_historical_data("AAPL", period="5d")
|
||||
if not any("error" in d for d in hist_data):
|
||||
for day in hist_data:
|
||||
print(f"Date: {day.get('Date')}, Close: ${day.get('Close', 'N/A'):.2f}")
|
||||
print()
|
||||
else:
|
||||
print(f"{hist_data[0]['error']}\n")
|
||||
|
||||
# 4. Multi-Stock Example
|
||||
print("5. Multi-Stock Current Prices")
|
||||
print("------------------------------")
|
||||
for symbol in ["AAPL", "MSFT", "GOOGL"]:
|
||||
price_data = yf_tools.get_stock_price(symbol)
|
||||
if "error" not in price_data:
|
||||
print(f"{symbol}: ${price_data.get('price', 'N/A')}")
|
||||
else:
|
||||
print(f"{symbol}: Error fetching price")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {str(e)}")
|
||||
|
||||
print("==================================================")
|
||||
print("Analysis Complete")
|
||||
print("==================================================")
|
||||
@@ -0,0 +1,91 @@
|
||||
from praisonaiagents.agent import Agent
|
||||
from praisonaiagents.task import Task
|
||||
from praisonaiagents.agents import PraisonAIAgents
|
||||
from typing import List, Dict
|
||||
import time
|
||||
|
||||
def get_time_check():
|
||||
current_time = int(time.time())
|
||||
result = "even" if current_time % 2 == 0 else "odd"
|
||||
print(f"Time check: {current_time} is {result}")
|
||||
return result
|
||||
|
||||
def create_prompt_chain():
|
||||
# Create agents for each step in the chain
|
||||
agent1 = Agent(
|
||||
name="Time Checker",
|
||||
role="Time checker",
|
||||
goal="Check if the time is even or odd",
|
||||
instructions="Check if the time is even or odd",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
agent2 = Agent(
|
||||
name="Advanced Analyzer",
|
||||
role="Advanced data analyzer",
|
||||
goal="Perform in-depth analysis of processed data",
|
||||
instructions="Analyze the processed data in detail"
|
||||
)
|
||||
|
||||
agent3 = Agent(
|
||||
name="Final Processor",
|
||||
role="Final data processor",
|
||||
goal="Generate final output based on analysis",
|
||||
instructions="Create final output based on analyzed data"
|
||||
)
|
||||
|
||||
# Create tasks for each step
|
||||
initial_task = Task(
|
||||
name="time_check",
|
||||
description="Getting time check and checking if it is even or odd",
|
||||
expected_output="Getting time check and checking if it is even or odd",
|
||||
agent=agent1,
|
||||
is_start=True, # Mark as the starting task
|
||||
task_type="decision", # This task will make a decision
|
||||
next_tasks=["advanced_analysis"], # Next task if condition passes
|
||||
condition={
|
||||
"even": ["advanced_analysis"], # If passes, go to advanced analysis
|
||||
"odd": ["final_processing"] # If fails, exit the chain
|
||||
}
|
||||
)
|
||||
|
||||
analysis_task = Task(
|
||||
name="advanced_analysis",
|
||||
description="Perform advanced analysis on the processed data",
|
||||
expected_output="Analyzed data ready for final processing",
|
||||
agent=agent2,
|
||||
next_tasks=["final_processing"]
|
||||
)
|
||||
|
||||
final_task = Task(
|
||||
name="final_processing",
|
||||
description="Generate final output",
|
||||
expected_output="Final processed result",
|
||||
agent=agent3
|
||||
)
|
||||
|
||||
# Create the workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[agent1, agent2, agent3],
|
||||
tasks=[initial_task, analysis_task, final_task],
|
||||
process="workflow", # Use workflow process type
|
||||
verbose=True
|
||||
)
|
||||
|
||||
return workflow
|
||||
|
||||
def main():
|
||||
# Create and run the prompt chain
|
||||
workflow = create_prompt_chain()
|
||||
|
||||
# Run the workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nWorkflow Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,78 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_time_check():
|
||||
current_time = int(time.time())
|
||||
result = "even" if current_time % 2 == 0 else "odd"
|
||||
print(f"Time check: {current_time} is {result}")
|
||||
return result
|
||||
|
||||
# Create agents for each step in the chain
|
||||
agent1 = Agent(
|
||||
name="Time Checker",
|
||||
role="Time checker",
|
||||
goal="Check if the time is even or odd",
|
||||
instructions="Check if the time is even or odd",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
agent2 = Agent(
|
||||
name="Advanced Analyzer",
|
||||
role="Advanced data analyzer",
|
||||
goal="Perform in-depth analysis of processed data",
|
||||
instructions="Analyze the processed data in detail"
|
||||
)
|
||||
|
||||
agent3 = Agent(
|
||||
name="Final Processor",
|
||||
role="Final data processor",
|
||||
goal="Generate final output based on analysis",
|
||||
instructions="Create final output based on analyzed data"
|
||||
)
|
||||
|
||||
# Create tasks for each step
|
||||
initial_task = Task(
|
||||
name="time_check",
|
||||
description="Getting time check and checking if it is even or odd",
|
||||
expected_output="Getting time check and checking if it is even or odd",
|
||||
agent=agent1,
|
||||
is_start=True, # Mark as the starting task
|
||||
task_type="decision", # This task will make a decision
|
||||
next_tasks=["advanced_analysis"], # Next task if condition passes
|
||||
condition={
|
||||
"even": ["advanced_analysis"], # If passes, go to advanced analysis
|
||||
"odd": "exit" # If fails, exit the chain
|
||||
}
|
||||
)
|
||||
|
||||
analysis_task = Task(
|
||||
name="advanced_analysis",
|
||||
description="Perform advanced analysis on the processed data",
|
||||
expected_output="Analyzed data ready for final processing",
|
||||
agent=agent2,
|
||||
next_tasks=["final_processing"]
|
||||
)
|
||||
|
||||
final_task = Task(
|
||||
name="final_processing",
|
||||
description="Generate final output",
|
||||
expected_output="Final processed result",
|
||||
agent=agent3
|
||||
)
|
||||
|
||||
# Create the workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[agent1, agent2, agent3],
|
||||
tasks=[initial_task, analysis_task, final_task],
|
||||
process="workflow", # Use workflow process type
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run the workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nWorkflow Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,21 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.35"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
authors = [
|
||||
{ name="Mervin Praison" }
|
||||
]
|
||||
dependencies = [
|
||||
"pydantic",
|
||||
"rich",
|
||||
"openai"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
memory = [
|
||||
"chromadb>=0.6.0"
|
||||
]
|
||||
@@ -0,0 +1,82 @@
|
||||
from praisonaiagents.agent import Agent
|
||||
from praisonaiagents.task import Task
|
||||
from praisonaiagents.agents import PraisonAIAgents
|
||||
import time
|
||||
|
||||
current_time = int(time.time())
|
||||
result = "even" if current_time % 2 == 0 else "odd"
|
||||
print(f"Time check: {current_time} is {result}")
|
||||
|
||||
# Create specialized agents
|
||||
router = Agent(
|
||||
name="Router",
|
||||
role="Input Router",
|
||||
goal="Evaluate input and determine routing path",
|
||||
instructions="Analyze input and decide whether to proceed or exit",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
processor1 = Agent(
|
||||
name="Processor 1",
|
||||
role="Secondary Processor",
|
||||
goal="Process valid inputs that passed initial check",
|
||||
instructions="Process data that passed the routing check"
|
||||
)
|
||||
|
||||
processor2 = Agent(
|
||||
name="Processor 2",
|
||||
role="Final Processor",
|
||||
goal="Perform final processing on validated data",
|
||||
instructions="Generate final output for processed data"
|
||||
)
|
||||
|
||||
# Create tasks with routing logic
|
||||
routing_task = Task(
|
||||
name="initial_routing",
|
||||
description="check the time and return according to what is returned",
|
||||
expected_output="pass or fail based on what is returned",
|
||||
agent=router,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
condition={
|
||||
"pass": ["process_valid"],
|
||||
"fail": "exit"
|
||||
}
|
||||
)
|
||||
|
||||
processing_task = Task(
|
||||
name="process_valid",
|
||||
description="Process validated input",
|
||||
expected_output="Processed data ready for final step",
|
||||
agent=processor1,
|
||||
next_tasks=["final_process"]
|
||||
)
|
||||
|
||||
final_task = Task(
|
||||
name="final_process",
|
||||
description="Generate final output",
|
||||
expected_output="Final processed result",
|
||||
agent=processor2
|
||||
)
|
||||
|
||||
# Create and run workflow
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[router, processor1, processor2],
|
||||
tasks=[routing_task, processing_task, final_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print("\nStarting Routing Workflow...")
|
||||
print("=" * 50)
|
||||
|
||||
results = workflow.start()
|
||||
|
||||
print("\nWorkflow Results:")
|
||||
print("=" * 50)
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
task_name = result.description
|
||||
print(f"\nTask: {task_name}")
|
||||
print(f"Result: {result.raw}")
|
||||
print("-" * 50)
|
||||
@@ -0,0 +1,83 @@
|
||||
from praisonaiagents.agent import Agent
|
||||
from praisonaiagents.task import Task
|
||||
from praisonaiagents.agents import PraisonAIAgents
|
||||
import time
|
||||
|
||||
def get_time_check():
|
||||
current_time = int(time.time())
|
||||
result = "even" if current_time % 2 == 0 else "odd"
|
||||
print(f"Time check: {current_time} is {result}")
|
||||
return result
|
||||
|
||||
# Create specialized agents
|
||||
router = Agent(
|
||||
name="Router",
|
||||
role="Input Router",
|
||||
goal="Evaluate input and determine routing path",
|
||||
instructions="Analyze input and decide whether to proceed or exit",
|
||||
tools=[get_time_check]
|
||||
)
|
||||
|
||||
processor1 = Agent(
|
||||
name="Processor 1",
|
||||
role="Secondary Processor",
|
||||
goal="Process valid inputs that passed initial check",
|
||||
instructions="Process data that passed the routing check"
|
||||
)
|
||||
|
||||
processor2 = Agent(
|
||||
name="Processor 2",
|
||||
role="Final Processor",
|
||||
goal="Perform final processing on validated data",
|
||||
instructions="Generate final output for processed data"
|
||||
)
|
||||
|
||||
# Create tasks with routing logic
|
||||
routing_task = Task(
|
||||
name="initial_routing",
|
||||
description="check the time and return according to what is returned",
|
||||
expected_output="pass or fail based on what is returned",
|
||||
agent=router,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
condition={
|
||||
"pass": ["process_valid"],
|
||||
"fail": ["process_invalid"]
|
||||
}
|
||||
)
|
||||
|
||||
processing_task = Task(
|
||||
name="process_valid",
|
||||
description="Process validated input",
|
||||
expected_output="Processed data ready for final step",
|
||||
agent=processor1,
|
||||
)
|
||||
|
||||
final_task = Task(
|
||||
name="process_invalid",
|
||||
description="Generate final output",
|
||||
expected_output="Final processed result",
|
||||
agent=processor2
|
||||
)
|
||||
|
||||
# Create and run workflow
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[router, processor1, processor2],
|
||||
tasks=[routing_task, processing_task, final_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
print("\nStarting Routing Workflow...")
|
||||
print("=" * 50)
|
||||
|
||||
results = workflow.start()
|
||||
|
||||
print("\nWorkflow Results:")
|
||||
print("=" * 50)
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
task_name = result.description
|
||||
print(f"\nTask: {task_name}")
|
||||
print(f"Result: {result.raw}")
|
||||
print("-" * 50)
|
||||
@@ -0,0 +1,56 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, Tools
|
||||
from pydantic import BaseModel
|
||||
|
||||
class AnalysisReport(BaseModel):
|
||||
title: str
|
||||
findings: str
|
||||
summary: str
|
||||
|
||||
# Create a researcher agent
|
||||
researcher = Agent(
|
||||
name="AIResearcher",
|
||||
role="Technology Research Analyst",
|
||||
goal="Analyze and structure information about AI developments",
|
||||
backstory="Expert analyst specializing in AI technology trends",
|
||||
verbose=True,
|
||||
llm="gpt-4o-mini",
|
||||
tools=[Tools.internet_search],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Create an analyst agent
|
||||
analyst = Agent(
|
||||
name="DataAnalyst",
|
||||
role="Data Insights Specialist",
|
||||
goal="Structure and analyze research findings",
|
||||
backstory="Senior data analyst with expertise in pattern recognition",
|
||||
verbose=True,
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Define structured tasks
|
||||
research_task = Task(
|
||||
name="gather_research",
|
||||
description="Research recent AI developments in 2024",
|
||||
agent=researcher,
|
||||
expected_output="Research findings"
|
||||
)
|
||||
|
||||
analysis_task = Task(
|
||||
name="analyze_findings",
|
||||
description="Analyze research findings and create structured report. No additional text or explanation.",
|
||||
agent=analyst,
|
||||
output_json=AnalysisReport,
|
||||
expected_output="JSON object"
|
||||
)
|
||||
|
||||
# Initialize and run agents
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, analyst],
|
||||
tasks=[research_task, analysis_task],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
result = agents.start()
|
||||
print(result)
|
||||
@@ -0,0 +1,95 @@
|
||||
from praisonaiagents import Agent
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
# Define structured output models
|
||||
class SearchResult(BaseModel):
|
||||
title: str
|
||||
url: Optional[str]
|
||||
snippet: str
|
||||
|
||||
class AnalysisReport(BaseModel):
|
||||
topic: str
|
||||
key_findings: List[str]
|
||||
search_results: List[SearchResult]
|
||||
summary: str
|
||||
confidence_score: float
|
||||
|
||||
def get_structured_analysis(query: str, verbose: bool = True) -> AnalysisReport:
|
||||
"""
|
||||
Performs a search and returns a structured analysis using an AI agent.
|
||||
|
||||
Args:
|
||||
query (str): The search query or topic to analyze
|
||||
verbose (bool): Whether to show detailed output
|
||||
|
||||
Returns:
|
||||
AnalysisReport: A structured report containing the analysis
|
||||
"""
|
||||
# Create an agent with search capabilities
|
||||
agent = Agent(
|
||||
name="StructuredAnalyst",
|
||||
role="Research Analyst",
|
||||
goal="Analyze topics and provide structured reports",
|
||||
backstory="Expert at gathering information and providing structured analysis",
|
||||
verbose=verbose,
|
||||
self_reflect=True, # Enable self-reflection for better quality
|
||||
markdown=True,
|
||||
llm="gpt-4o-mini"
|
||||
)
|
||||
|
||||
# Create the analysis prompt
|
||||
prompt = f"""
|
||||
Analyze the following topic: "{query}"
|
||||
|
||||
Provide a structured analysis in JSON format that matches this schema:
|
||||
{{
|
||||
"topic": "string",
|
||||
"key_findings": ["string"],
|
||||
"search_results": [
|
||||
{{
|
||||
"title": "string",
|
||||
"url": "string",
|
||||
"snippet": "string"
|
||||
}}
|
||||
],
|
||||
"summary": "string",
|
||||
"confidence_score": float (0-1)
|
||||
}}
|
||||
|
||||
Requirements:
|
||||
1. Include at least 3 key findings
|
||||
2. Include at least 2 relevant search results
|
||||
3. Provide a comprehensive summary
|
||||
4. Set confidence score based on quality of sources (0-1)
|
||||
|
||||
Return ONLY the JSON object, no other text.
|
||||
"""
|
||||
|
||||
# Get structured response from agent
|
||||
response = agent.chat(
|
||||
prompt=prompt,
|
||||
output_json=AnalysisReport # This ensures response matches our model
|
||||
)
|
||||
|
||||
return AnalysisReport.model_validate_json(response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
analysis = get_structured_analysis("Latest developments in AI agents and autonomous systems")
|
||||
print(analysis)
|
||||
# Print the structured results
|
||||
print("\n=== Structured Analysis Report ===")
|
||||
print(f"Topic: {analysis.topic}")
|
||||
print("\nKey Findings:")
|
||||
for i, finding in enumerate(analysis.key_findings, 1):
|
||||
print(f"{i}. {finding}")
|
||||
|
||||
print("\nSearch Results:")
|
||||
for result in analysis.search_results:
|
||||
print(f"\nTitle: {result.title}")
|
||||
print(f"URL: {result.url}")
|
||||
print(f"Snippet: {result.snippet}")
|
||||
|
||||
print(f"\nSummary: {analysis.summary}")
|
||||
print(f"Confidence Score: {analysis.confidence_score:.2f}")
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
from typing import List, Dict
|
||||
from pydantic import BaseModel
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from praisonaiagents.main import TaskOutput
|
||||
|
||||
# Pydantic models for structured output
|
||||
class Chapter(BaseModel):
|
||||
chapter_number: int
|
||||
title: str
|
||||
outline: str
|
||||
|
||||
class StoryOutline(BaseModel):
|
||||
title: str
|
||||
total_chapters: int
|
||||
chapters: List[Chapter]
|
||||
|
||||
class ChapterContent(BaseModel):
|
||||
chapter_number: int
|
||||
title: str
|
||||
content: str
|
||||
|
||||
# Create the story planner agent
|
||||
planner_agent = Agent(
|
||||
name="Story Planner",
|
||||
role="Creative Writing Architect",
|
||||
goal="Create detailed story outlines with chapter breakdowns",
|
||||
backstory="Expert storyteller skilled in narrative structure and plot development",
|
||||
verbose=True,
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Create the writer agent
|
||||
writer_agent = Agent(
|
||||
name="Story Writer",
|
||||
role="Creative Writer",
|
||||
goal="Write engaging and cohesive story chapters",
|
||||
backstory="Experienced writer skilled in bringing stories to life with vivid details and engaging narrative",
|
||||
verbose=True,
|
||||
llm="gpt-4o-mini",
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Story title
|
||||
story_title = "The Curious Cat"
|
||||
|
||||
# Step 1: Create outline task
|
||||
outline_task = Task(
|
||||
name="story_outline",
|
||||
description=f"""Create a detailed story outline for a story titled '{story_title}'.
|
||||
The outline should include:
|
||||
1. A compelling story arc
|
||||
2. Clear chapter breakdown
|
||||
3. Brief description of what happens in each chapter
|
||||
4. Character introductions and development points
|
||||
|
||||
Provide the response in this exact JSON format:
|
||||
{{
|
||||
"title": "Story title",
|
||||
"total_chapters": number,
|
||||
"chapters": [
|
||||
{{
|
||||
"chapter_number": 1,
|
||||
"title": "Chapter title",
|
||||
"outline": "Detailed chapter outline"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
""",
|
||||
expected_output="Complete story outline",
|
||||
agent=planner_agent,
|
||||
output_pydantic=StoryOutline
|
||||
)
|
||||
|
||||
# Create initial workflow for outline
|
||||
outline_workflow = PraisonAIAgents(
|
||||
agents=[planner_agent],
|
||||
tasks=[outline_task],
|
||||
verbose=True,
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
# Get the outline
|
||||
outline_result = outline_workflow.start()
|
||||
story_outline = outline_result["task_results"][0].pydantic
|
||||
|
||||
# Step 2: Create tasks for each chapter
|
||||
chapter_tasks = []
|
||||
chapters_content = []
|
||||
|
||||
# Create a task for each chapter
|
||||
for chapter in story_outline.chapters:
|
||||
chapter_task = Task(
|
||||
name=f"write_chapter_{chapter.chapter_number}",
|
||||
description=f"""Write chapter {chapter.chapter_number}: {chapter.title}
|
||||
Chapter outline: {chapter.outline}
|
||||
|
||||
Write a complete, engaging chapter following this outline.
|
||||
Return in this exact JSON format:
|
||||
{{
|
||||
"chapter_number": {chapter.chapter_number},
|
||||
"title": "{chapter.title}",
|
||||
"content": "The full chapter content..."
|
||||
}}
|
||||
""",
|
||||
expected_output="Complete chapter content",
|
||||
agent=writer_agent,
|
||||
output_pydantic=ChapterContent,
|
||||
context=[outline_task]
|
||||
)
|
||||
chapter_tasks.append(chapter_task)
|
||||
|
||||
# Create sequential workflow for chapters
|
||||
chapter_workflow = PraisonAIAgents(
|
||||
agents=[writer_agent],
|
||||
tasks=chapter_tasks,
|
||||
verbose=True,
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
# Run the chapter workflow
|
||||
chapter_results = chapter_workflow.start()
|
||||
|
||||
# Collect and validate all chapter contents
|
||||
for task_result in chapter_results["task_results"].values():
|
||||
if task_result and task_result.pydantic:
|
||||
chapters_content.append(task_result.pydantic)
|
||||
|
||||
# Sort chapters by chapter number
|
||||
chapters_content.sort(key=lambda x: x.chapter_number)
|
||||
|
||||
# Format final result
|
||||
story = {
|
||||
"title": story_outline.title,
|
||||
"outline": story_outline.model_dump(),
|
||||
"chapters": [chapter.model_dump() for chapter in chapters_content]
|
||||
}
|
||||
|
||||
# Print results
|
||||
print("\n=== Story Generation Results ===")
|
||||
print(f"\nTitle: {story['title']}")
|
||||
print("\nOutline:")
|
||||
print(story['outline'])
|
||||
print("\nChapters:")
|
||||
for chapter in story['chapters']:
|
||||
print(f"\nChapter {chapter['chapter_number']}: {chapter['title']}")
|
||||
print(chapter['content'][:200] + "...") # Print first 200 chars of each chapter
|
||||
@@ -0,0 +1,30 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from praisonaiagents.tools import get_article, get_news_sources, get_articles_from_source, get_trending_topics
|
||||
|
||||
# Create Wikipedia agent
|
||||
news_agent = Agent(
|
||||
name="NewsAgent",
|
||||
role="News Analyst",
|
||||
goal="Collect and analyze news articles from various sources.",
|
||||
backstory="Expert in news gathering and content analysis.",
|
||||
tools=[get_article, get_news_sources, get_articles_from_source, get_trending_topics],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
|
||||
# Define research task
|
||||
news_task = Task(
|
||||
description="Analyze news articles about 'AI developments' from major tech news sources.",
|
||||
expected_output="Summary of key AI developments with source articles.",
|
||||
agent=news_agent,
|
||||
name="ai_news"
|
||||
)
|
||||
|
||||
|
||||
# Run agent
|
||||
agents = PraisonAIAgents(
|
||||
agents=[news_agent],
|
||||
tasks=[news_task],
|
||||
process="sequential"
|
||||
)
|
||||
agents.start()
|
||||
Generated
+1590
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,74 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
from typing import List, Dict
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
# 1. Tool
|
||||
def internet_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles, URLs, and snippets.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
# 2. Agent
|
||||
data_agent = Agent(
|
||||
name="DataCollector",
|
||||
role="Search Specialist",
|
||||
goal="Perform internet searches to collect relevant information.",
|
||||
backstory="Expert in finding and organising internet data.",
|
||||
tools=[internet_search_tool],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# 3. Tasks
|
||||
collect_task = Task(
|
||||
description="Perform an internet search using the query: 'AI job trends in 2024'. Return results as a list of title, URL, and snippet.",
|
||||
expected_output="List of search results with titles, URLs, and snippets.",
|
||||
agent=data_agent,
|
||||
name="collect_data",
|
||||
is_start=True,
|
||||
next_tasks=["validate_data"]
|
||||
)
|
||||
|
||||
validate_task = Task(
|
||||
description="""Validate the collected data. Check if:
|
||||
1. At least 5 results are returned.
|
||||
2. Each result contains a title and a URL.
|
||||
Return validation_result as 'valid' or 'invalid' only no other text.""",
|
||||
expected_output="Validation result indicating if data is valid or invalid.",
|
||||
agent=data_agent,
|
||||
name="validate_data",
|
||||
task_type="decision",
|
||||
condition={
|
||||
"valid": [], # End the workflow on valid data
|
||||
"invalid": ["collect_data"] # Retry data collection on invalid data
|
||||
},
|
||||
)
|
||||
|
||||
# 4. AI Agents Workflow
|
||||
agents = PraisonAIAgents(
|
||||
agents=[data_agent],
|
||||
tasks=[collect_task, validate_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,253 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import random
|
||||
from typing import List, Dict, Union
|
||||
import json
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Add Pydantic models for data validation
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
job: str
|
||||
city: str
|
||||
salary: int
|
||||
|
||||
class ProcessedPerson(Person):
|
||||
salary_category: str
|
||||
age_group: str
|
||||
processing_status: str
|
||||
|
||||
class DataList(BaseModel):
|
||||
items: List[Dict]
|
||||
|
||||
class ValidationResult(BaseModel):
|
||||
validation_result: str
|
||||
details: str = ""
|
||||
|
||||
def random_data_of_individuals() -> List[Dict]:
|
||||
"""Generate random individual data"""
|
||||
names = ["John", "Jane", "Mike", "Sarah", "David", "Emma"]
|
||||
jobs = ["Engineer", "Doctor", "Teacher", "Artist", "Developer"]
|
||||
cities = ["New York", "London", "Tokyo", "Paris", "Berlin"]
|
||||
|
||||
data = []
|
||||
for _ in range(random.randint(3, 7)):
|
||||
person = {
|
||||
"name": random.choice(names),
|
||||
"age": random.randint(25, 65),
|
||||
"job": random.choice(jobs),
|
||||
"city": random.choice(cities),
|
||||
"salary": random.randint(30000, 120000)
|
||||
}
|
||||
data.append(person)
|
||||
return data
|
||||
|
||||
def process_data_of_individuals(data: Union[List[Dict], Dict, str]) -> Dict:
|
||||
"""Process individual data by adding categories and analysis"""
|
||||
try:
|
||||
print("\n[DEBUG] process_data_of_individuals input:", data)
|
||||
print("[DEBUG] Current workflow state:")
|
||||
print("- total_records:", workflow.get_state("total_records"))
|
||||
print("- current_index:", workflow.get_state("current_index"))
|
||||
print("- remaining:", workflow.get_state("remaining"))
|
||||
|
||||
# Get the items list from the collect_task result
|
||||
collect_result = None
|
||||
for task in workflow.tasks.values():
|
||||
if task.name == "collect_data" and task.result:
|
||||
try:
|
||||
collect_data = json.loads(task.result.raw)
|
||||
collect_result = collect_data.get("items", [])
|
||||
print("[DEBUG] Found collect_data items:", len(collect_result))
|
||||
except:
|
||||
print("[DEBUG] Failed to parse collect_data result")
|
||||
|
||||
# Handle string input by trying to parse it as JSON
|
||||
if isinstance(data, str):
|
||||
if ":" in data and not data.strip().startswith("{"):
|
||||
# Convert string format to dictionary
|
||||
pairs = [pair.strip() for pair in data.split(",")]
|
||||
data_dict = {}
|
||||
for pair in pairs:
|
||||
key, value = pair.split(":")
|
||||
key = key.strip().lower()
|
||||
value = value.strip()
|
||||
if key == "age" or key == "salary":
|
||||
value = int(value)
|
||||
data_dict[key] = value
|
||||
data = data_dict
|
||||
else:
|
||||
data = json.loads(data)
|
||||
print("[DEBUG] Parsed data:", data)
|
||||
|
||||
# Handle single record
|
||||
if isinstance(data, dict):
|
||||
person = data
|
||||
# Initialize total records if not set
|
||||
total_records = workflow.get_state("total_records")
|
||||
if total_records is None and collect_result:
|
||||
total_records = len(collect_result)
|
||||
workflow.set_state("total_records", total_records)
|
||||
print(f"[DEBUG] Initialized total_records to {total_records}")
|
||||
|
||||
current_index = workflow.get_state("current_index", 0)
|
||||
total_records = total_records or 1
|
||||
remaining = total_records - (current_index + 1)
|
||||
workflow.set_state("remaining", remaining)
|
||||
print(f"[DEBUG] Processing record {current_index + 1}/{total_records}")
|
||||
|
||||
elif isinstance(data, list):
|
||||
if len(data) == 0:
|
||||
raise ValueError("Empty data list")
|
||||
person = data[0]
|
||||
workflow.set_state("total_records", len(data))
|
||||
workflow.set_state("current_index", 0)
|
||||
workflow.set_state("remaining", len(data) - 1)
|
||||
print(f"[DEBUG] First record from list of {len(data)} items")
|
||||
else:
|
||||
raise ValueError("Input must be a dictionary or list with at least one record")
|
||||
|
||||
processed_person = person.copy()
|
||||
|
||||
# Add salary category
|
||||
salary = person.get("salary", 0)
|
||||
if salary < 50000:
|
||||
processed_person["salary_category"] = "entry"
|
||||
elif salary < 90000:
|
||||
processed_person["salary_category"] = "mid"
|
||||
else:
|
||||
processed_person["salary_category"] = "senior"
|
||||
|
||||
# Add age group
|
||||
age = person.get("age", 0)
|
||||
if age < 35:
|
||||
processed_person["age_group"] = "young"
|
||||
elif age < 50:
|
||||
processed_person["age_group"] = "mid"
|
||||
else:
|
||||
processed_person["age_group"] = "senior"
|
||||
|
||||
# Add processing status using workflow state
|
||||
remaining = workflow.get_state("remaining", 0)
|
||||
current_index = workflow.get_state("current_index", 0)
|
||||
total_records = workflow.get_state("total_records", 1)
|
||||
|
||||
# Update current index for next iteration
|
||||
workflow.set_state("current_index", current_index + 1)
|
||||
|
||||
print(f"[DEBUG] Status check - remaining: {remaining}, current_index: {current_index}, total_records: {total_records}")
|
||||
|
||||
if remaining <= 0 and current_index >= total_records - 1:
|
||||
print("[DEBUG] Setting status to 'all records processed'")
|
||||
processed_person["processing_status"] = "all records processed"
|
||||
else:
|
||||
print(f"[DEBUG] More records to process. Remaining: {remaining}")
|
||||
processed_person["processing_status"] = f"more records to process ({remaining} remaining)"
|
||||
|
||||
print("[DEBUG] Final processed person:", processed_person)
|
||||
return processed_person
|
||||
|
||||
except Exception as e:
|
||||
print(f"[DEBUG] Error processing data: {str(e)}")
|
||||
return {"error": str(e), "processing_status": "error occurred"}
|
||||
|
||||
# Create agents
|
||||
data_agent = Agent(
|
||||
name="DataCollector",
|
||||
role="Data collection specialist",
|
||||
goal="Collect and validate data about individuals",
|
||||
backstory="Expert in gathering and validating demographic data",
|
||||
tools=[random_data_of_individuals],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
process_agent = Agent(
|
||||
name="DataProcessor",
|
||||
role="Data processor",
|
||||
goal="Process and categorize individual data",
|
||||
backstory="Expert in data analysis and categorization",
|
||||
tools=[process_data_of_individuals],
|
||||
self_reflect=False
|
||||
)
|
||||
|
||||
# Modify tasks to use Pydantic models
|
||||
collect_task = Task(
|
||||
description="Collect random individual data using the random_data_of_individuals tool. Return as a JSON object with 'items' array.",
|
||||
expected_output="List of individual records with basic information",
|
||||
agent=data_agent,
|
||||
name="collect_data",
|
||||
tools=[random_data_of_individuals],
|
||||
is_start=True,
|
||||
next_tasks=["validate_data"],
|
||||
output_json=DataList
|
||||
)
|
||||
|
||||
validate_task = Task(
|
||||
description="""Validate the collected data. Check if:
|
||||
1. All required fields are present (name, age, job, city, salary)
|
||||
2. Age is between 25 and 65
|
||||
3. Salary is between 30000 and 120000
|
||||
Return validation_result as 'valid' or 'invalid' with optional details.""",
|
||||
expected_output="Validation result indicating if data is valid or invalid",
|
||||
agent=data_agent,
|
||||
name="validate_data",
|
||||
task_type="decision",
|
||||
condition={
|
||||
"valid": ["process_data"],
|
||||
"invalid": ["collect_data"]
|
||||
},
|
||||
output_json=ValidationResult
|
||||
)
|
||||
|
||||
process_task = Task(
|
||||
description="""Process one record at a time from the input data.
|
||||
Current progress will be shown in Loop Status.
|
||||
|
||||
For the current record:
|
||||
1. Use process_data_of_individuals tool to add categories
|
||||
2. Return the processed record with remaining count
|
||||
|
||||
Current remaining: {remaining}
|
||||
Current item: {current_item}
|
||||
|
||||
Process this record and indicate if more records remain.""",
|
||||
expected_output="Processed record with categories and status",
|
||||
agent=process_agent,
|
||||
name="process_data",
|
||||
tools=[process_data_of_individuals],
|
||||
task_type="loop",
|
||||
condition={
|
||||
"more records to process": ["process_data"],
|
||||
"all records processed": []
|
||||
},
|
||||
context=[collect_task],
|
||||
output_json=ProcessedPerson
|
||||
)
|
||||
|
||||
# Create PraisonAIAgents instance with workflow process
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[data_agent, process_agent],
|
||||
tasks=[collect_task, validate_task, process_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
|
||||
# Run the workflow
|
||||
result = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nWorkflow Results:")
|
||||
print("----------------")
|
||||
for task_id, task in workflow.tasks.items():
|
||||
print(f"\nTask: {task.name}")
|
||||
print(f"Status: {task.status}")
|
||||
if task.result:
|
||||
print("Output:")
|
||||
try:
|
||||
# Try to format as pretty JSON
|
||||
import json
|
||||
output = json.loads(task.result.raw)
|
||||
print(json.dumps(output, indent=2))
|
||||
except:
|
||||
# If not JSON, print raw output
|
||||
print(task.result.raw[:500] + "..." if len(task.result.raw) > 500 else task.result.raw)
|
||||
@@ -1 +0,0 @@
|
||||
# Welcome to Praison AI! 🚀🤖
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
ollama_save: "true"
|
||||
huggingface_save: "true"
|
||||
train: "true"
|
||||
|
||||
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
|
||||
hf_model_name: "mervinpraison/llama-3.1-instruct"
|
||||
ollama_model: "mervinpraison/llama3.1-instruct"
|
||||
model_parameters: "8b"
|
||||
|
||||
dataset:
|
||||
- name: "yahma/alpaca-cleaned"
|
||||
split_type: "train"
|
||||
processing_func: "format_prompts"
|
||||
rename:
|
||||
input: "input"
|
||||
output: "output"
|
||||
instruction: "instruction"
|
||||
filter_data: false
|
||||
filter_column_value: "id"
|
||||
filter_value: "alpaca"
|
||||
num_samples: 20000
|
||||
|
||||
dataset_text_field: "text"
|
||||
dataset_num_proc: 2
|
||||
packing: false
|
||||
|
||||
max_seq_length: 2048
|
||||
load_in_4bit: true
|
||||
lora_r: 16
|
||||
lora_target_modules:
|
||||
- "q_proj"
|
||||
- "k_proj"
|
||||
- "v_proj"
|
||||
- "o_proj"
|
||||
- "gate_proj"
|
||||
- "up_proj"
|
||||
- "down_proj"
|
||||
lora_alpha: 16
|
||||
lora_dropout: 0
|
||||
lora_bias: "none"
|
||||
use_gradient_checkpointing: "unsloth"
|
||||
random_state: 3407
|
||||
use_rslora: false
|
||||
loftq_config: null
|
||||
|
||||
per_device_train_batch_size: 2
|
||||
gradient_accumulation_steps: 2
|
||||
warmup_steps: 5
|
||||
num_train_epochs: 1
|
||||
max_steps: 10
|
||||
learning_rate: 2.0e-4
|
||||
logging_steps: 1
|
||||
optim: "adamw_8bit"
|
||||
weight_decay: 0.01
|
||||
lr_scheduler_type: "linear"
|
||||
seed: 3407
|
||||
output_dir: "outputs"
|
||||
|
||||
quantization_method:
|
||||
- "q4_k_m"
|
||||
@@ -0,0 +1,2 @@
|
||||
*.txt
|
||||
*.csv
|
||||
@@ -1,42 +0,0 @@
|
||||
framework: crewai
|
||||
topic: create a movie about cat in mars based on https://blog.celtx.com/how-to-write-a-good-story/
|
||||
roles:
|
||||
story_developer:
|
||||
backstory: Experienced in crafting compelling narratives for a diverse audience.
|
||||
goal: Develop an engaging storyline for a movie about cats in Mars
|
||||
role: Story Developer
|
||||
tasks:
|
||||
storyline_creation:
|
||||
description: Create a captivating storyline for a movie about cats in Mars
|
||||
inspired by the blog post at https://blog.celtx.com/how-to-write-a-good-story/
|
||||
expected_output: Detailed storyline with key plot points, character motivations,
|
||||
and setting descriptions.
|
||||
tools:
|
||||
- 'ScrapeWebsiteTool'
|
||||
screenwriter:
|
||||
backstory: Proficient in translating complex narratives into engaging screenplay
|
||||
format.
|
||||
goal: Transform the storyline into a script
|
||||
role: Screenwriter
|
||||
tasks:
|
||||
script_creation:
|
||||
description: Transform the developed storyline into a script for the movie
|
||||
about cats in Mars.
|
||||
expected_output: Complete script with dialogues, scenes, and stage directions
|
||||
ready for production.
|
||||
tools:
|
||||
- ''
|
||||
director:
|
||||
backstory: Experienced in visual storytelling and bringing scripts to life on
|
||||
screen.
|
||||
goal: Oversee the production of the movie
|
||||
role: Director
|
||||
tasks:
|
||||
movie_production:
|
||||
description: Take the script and lead the team to produce the movie 'Cats
|
||||
in Mars' based on the provided content and script.
|
||||
expected_output: Completed movie ready for distribution with compelling visuals
|
||||
and engaging storytelling.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
@@ -1,42 +0,0 @@
|
||||
framework: crewai
|
||||
topic: create a movie about cat in mars based on https://www.grammarly.com/blog/how-to-write-a-story/
|
||||
roles:
|
||||
story_developer:
|
||||
backstory: Experienced in crafting compelling narratives for a diverse audience.
|
||||
goal: Develop an engaging storyline for a movie about cats in Mars
|
||||
role: Story Developer
|
||||
tasks:
|
||||
storyline_creation:
|
||||
description: Create a captivating storyline for a movie about cats in Mars
|
||||
inspired by the blog post at https://www.grammarly.com/blog/how-to-write-a-story/
|
||||
expected_output: Detailed storyline with key plot points, character motivations,
|
||||
and setting descriptions.
|
||||
tools:
|
||||
- 'ScrapeWebsiteTool'
|
||||
screenwriter:
|
||||
backstory: Proficient in translating complex narratives into engaging screenplay
|
||||
format.
|
||||
goal: Transform the storyline into a script
|
||||
role: Screenwriter
|
||||
tasks:
|
||||
script_creation:
|
||||
description: Transform the developed storyline into a script for the movie
|
||||
about cats in Mars.
|
||||
expected_output: Complete script with dialogues, scenes, and stage directions
|
||||
ready for production.
|
||||
tools:
|
||||
- ''
|
||||
director:
|
||||
backstory: Experienced in visual storytelling and bringing scripts to life on
|
||||
screen.
|
||||
goal: Oversee the production of the movie
|
||||
role: Director
|
||||
tasks:
|
||||
movie_production:
|
||||
description: Take the script and lead the team to produce the movie 'Cats
|
||||
in Mars' based on the provided content and script.
|
||||
expected_output: Completed movie ready for distribution with compelling visuals
|
||||
and engaging storytelling.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
@@ -1,41 +0,0 @@
|
||||
framework: "crewai"
|
||||
topic: "Artificial Intelligence"
|
||||
|
||||
roles:
|
||||
researcher:
|
||||
role: "Senior Data Researcher"
|
||||
goal: "Uncover cutting-edge developments in {topic}"
|
||||
backstory: >
|
||||
You're a seasoned researcher with a knack for uncovering the latest
|
||||
developments in {topic}. Known for your ability to find the most relevant
|
||||
information and present it in a clear and concise manner.
|
||||
tools:
|
||||
- search_tool:
|
||||
engine: google
|
||||
tasks:
|
||||
research_task:
|
||||
description: >
|
||||
Conduct thorough research about {topic}, focusing on the latest
|
||||
trends and technologies.
|
||||
expected_output: >
|
||||
A list of 10 bullet points summarizing key findings with links to
|
||||
relevant sources.
|
||||
|
||||
reporting_analyst:
|
||||
role: "Reporting Analyst"
|
||||
goal: "Create detailed reports based on {topic} data analysis and research findings."
|
||||
backstory: >
|
||||
You're a meticulous analyst with a keen eye for detail. You're known for
|
||||
your ability to turn complex data into clear and concise reports, making
|
||||
it easy for others to understand and act on the information you provide.
|
||||
tasks:
|
||||
reporting_task:
|
||||
description: >
|
||||
Review the research findings and create a comprehensive report on
|
||||
{topic}. Include insights, potential opportunities, and recommendations.
|
||||
expected_output: >
|
||||
A well-structured report in Markdown format, ready for distribution.
|
||||
|
||||
dependencies:
|
||||
- task: reporting_task
|
||||
depends_on: research_task
|
||||
@@ -1,41 +0,0 @@
|
||||
framework: "autogen"
|
||||
topic: "Artificial Intelligence"
|
||||
|
||||
roles:
|
||||
narrative_designer:
|
||||
role: "Narrative Designer"
|
||||
goal: "Craft engaging storylines and narratives for {topic}"
|
||||
backstory: >
|
||||
You're a creative visionary skilled in weaving complex narratives and
|
||||
developing engaging story worlds within {topic}. Your expertise lies in
|
||||
conceptualizing the overarching story and ensuring it resonates with the audience.
|
||||
tasks:
|
||||
story_concept_development:
|
||||
description: >
|
||||
Develop a compelling story concept for {topic}, focusing on originality,
|
||||
thematic depth, and audience engagement. Outline the main narrative arcs,
|
||||
characters, and settings.
|
||||
expected_output: >
|
||||
A comprehensive document outlining the story concept, including key narrative
|
||||
arcs, character bios, and settings descriptions.
|
||||
|
||||
scriptwriter:
|
||||
role: "Scriptwriter"
|
||||
goal: "Transform story concepts into detailed scripts for {topic}"
|
||||
backstory: >
|
||||
With a flair for dialogue and a deep understanding of pacing and structure,
|
||||
you excel at bringing narratives to life through scriptwriting. Your work
|
||||
turns story concepts into blueprints for compelling {topic} experiences.
|
||||
tasks:
|
||||
scriptwriting_task:
|
||||
description: >
|
||||
Based on the narrative concept, write a detailed script for {topic}.
|
||||
Include dialogue, stage directions, and scene descriptions that bring
|
||||
the story to life.
|
||||
expected_output: >
|
||||
A detailed script ready for production, including dialogue, stage
|
||||
directions, and scene descriptions.
|
||||
|
||||
dependencies:
|
||||
- task: scriptwriting_task
|
||||
depends_on: story_concept_development
|
||||
@@ -1,43 +0,0 @@
|
||||
framework: crewai
|
||||
topic: research about nvidia growth
|
||||
roles:
|
||||
data_collector:
|
||||
backstory: An experienced researcher with the ability to efficiently collect and
|
||||
organize vast amounts of data.
|
||||
goal: Gather information on Nvidia's growth
|
||||
role: Data Collector
|
||||
tasks:
|
||||
data_collection_task:
|
||||
description: Collect data on Nvidia's growth from various sources such as
|
||||
financial reports, news articles, and company announcements.
|
||||
expected_output: A comprehensive document detailing data points on Nvidia's
|
||||
growth over the years.
|
||||
tools:
|
||||
- 'YouSearchTool'
|
||||
data_analyst:
|
||||
backstory: Specializes in extracting insights from large datasets, proficient
|
||||
in quantitative and qualitative analysis.
|
||||
goal: Analyze the collected data to identify trends and patterns
|
||||
role: Data Analyst
|
||||
tasks:
|
||||
data_analysis_task:
|
||||
description: Analyze the collected data to identify key trends and patterns
|
||||
in Nvidia's growth.
|
||||
expected_output: An analytical report summarizing trends, patterns, and key
|
||||
growth metrics of Nvidia.
|
||||
tools:
|
||||
- ''
|
||||
report_preparer:
|
||||
backstory: Experienced in creating detailed reports and presentations, turning
|
||||
analytical data into actionable insights.
|
||||
goal: Generate a final report on Nvidia's growth
|
||||
role: Report Preparer
|
||||
tasks:
|
||||
report_preparation_task:
|
||||
description: Create a detailed report based on the analysis, highlighting
|
||||
Nvidia's growth trajectory.
|
||||
expected_output: A polished, comprehensive report summarizing Nvidia's growth
|
||||
with visual aids and key insights.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
@@ -1,26 +0,0 @@
|
||||
framework: crewai
|
||||
topic: research about the latest AI News and prepare a detailed report
|
||||
roles:
|
||||
research_analyst:
|
||||
backstory: Experienced in gathering and analyzing data related to AI news trends.
|
||||
goal: Analyze AI News trends
|
||||
role: Research Analyst
|
||||
tasks:
|
||||
gather_data:
|
||||
description: Conduct in-depth research on the latest AI News trends from reputable
|
||||
sources.
|
||||
expected_output: Comprehensive report on current AI News trends.
|
||||
tools:
|
||||
- 'InternetSearchTool'
|
||||
content_creator:
|
||||
backstory: Skilled in writing informative and engaging content about AI News.
|
||||
goal: Create detailed reports on AI News
|
||||
role: Content Creator
|
||||
tasks:
|
||||
compile_report:
|
||||
description: Compile all gathered data into a detailed report with analysis
|
||||
and insights.
|
||||
expected_output: Well-structured report on the latest AI News with key insights.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
@@ -1,39 +0,0 @@
|
||||
framework: crewai
|
||||
topic: research about the causes of lung disease
|
||||
roles:
|
||||
research_analyst:
|
||||
backstory: Experienced in analyzing scientific data related to respiratory health.
|
||||
goal: Analyze data on lung diseases
|
||||
role: Research Analyst
|
||||
tasks:
|
||||
data_analysis:
|
||||
description: Gather and analyze data on the causes and risk factors of lung
|
||||
diseases.
|
||||
expected_output: Report detailing key findings on lung disease causes.
|
||||
tools:
|
||||
- 'PubmedQueryRun'
|
||||
medical_writer:
|
||||
backstory: Skilled in translating complex medical information into accessible
|
||||
content.
|
||||
goal: Compile comprehensive content on lung disease causes
|
||||
role: Medical Writer
|
||||
tasks:
|
||||
content_creation:
|
||||
description: Create detailed content summarizing the research findings on
|
||||
lung disease causes.
|
||||
expected_output: Document outlining various causes and risk factors of lung
|
||||
diseases.
|
||||
tools:
|
||||
- ''
|
||||
editor:
|
||||
backstory: Proficient in editing medical content for accuracy and clarity.
|
||||
goal: Review and refine content on lung disease causes
|
||||
role: Editor
|
||||
tasks:
|
||||
content_review:
|
||||
description: Edit and refine the compiled content on lung disease causes for
|
||||
accuracy and coherence.
|
||||
expected_output: Finalized document on lung disease causes ready for dissemination.
|
||||
tools:
|
||||
- ''
|
||||
dependencies: []
|
||||
@@ -0,0 +1,233 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from praisonaiagents.main import (
|
||||
display_error,
|
||||
display_interaction,
|
||||
display_tool_call,
|
||||
display_instruction,
|
||||
error_logs,
|
||||
Console
|
||||
)
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
console = Console()
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define both sync and async tools
|
||||
def sync_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Synchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
list: Search results
|
||||
"""
|
||||
display_tool_call(f"Running sync search for: {query}", console)
|
||||
time.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error during sync search: {e}"
|
||||
display_error(error_msg, console)
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
|
||||
async def async_search_tool(query: str) -> List[Dict]:
|
||||
"""
|
||||
Asynchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
list: Search results
|
||||
"""
|
||||
display_tool_call(f"Running async search for: {query}", console)
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
error_msg = f"Error during async search: {e}"
|
||||
display_error(error_msg, console)
|
||||
error_logs.append(error_msg)
|
||||
return []
|
||||
|
||||
# 3. Define both sync and async callbacks
|
||||
def sync_callback(output: TaskOutput):
|
||||
display_interaction("Sync Callback", f"Processing output: {output.raw[:100]}...", markdown=True, console=console)
|
||||
time.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
display_tool_call(f"Processed JSON result: {output.json_dict}", console)
|
||||
elif output.output_format == "Pydantic":
|
||||
display_tool_call(f"Processed Pydantic result: {output.pydantic}", console)
|
||||
|
||||
async def async_callback(output: TaskOutput):
|
||||
display_interaction("Async Callback", f"Processing output: {output.raw[:100]}...", markdown=True, console=console)
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
display_tool_call(f"Processed JSON result: {output.json_dict}", console)
|
||||
elif output.output_format == "Pydantic":
|
||||
display_tool_call(f"Processed Pydantic result: {output.pydantic}", console)
|
||||
|
||||
# 4. Create agents with different tools
|
||||
sync_agent = Agent(
|
||||
name="SyncAgent",
|
||||
role="Synchronous Search Specialist",
|
||||
goal="Perform synchronous searches and return structured results",
|
||||
backstory="Expert in sync operations and data organization",
|
||||
tools=[sync_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
async_agent = Agent(
|
||||
name="AsyncAgent",
|
||||
role="Asynchronous Search Specialist",
|
||||
goal="Perform asynchronous searches and return structured results",
|
||||
backstory="Expert in async operations and data organization",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create tasks with different configurations
|
||||
sync_task = Task(
|
||||
name="sync_search",
|
||||
description="Search for 'Python programming' using sync tool and return structured results",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=sync_agent,
|
||||
async_execution=False,
|
||||
callback=sync_callback,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="Search for 'Async programming' using async tool and return structured results",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
# 6. Create workflow tasks
|
||||
workflow_sync_task = Task(
|
||||
name="workflow_sync",
|
||||
description="Workflow sync search for 'AI trends' with structured output",
|
||||
expected_output="SearchResult model with AI trends data",
|
||||
agent=sync_agent,
|
||||
async_execution=False,
|
||||
is_start=True,
|
||||
next_tasks=["workflow_async"],
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
workflow_async_task = Task(
|
||||
name="workflow_async",
|
||||
description="Workflow async search for 'Future of AI' with structured output",
|
||||
expected_output="SearchResult model with Future of AI data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
output_pydantic=SearchResult
|
||||
)
|
||||
|
||||
# 7. Example usage functions
|
||||
def run_sync_example():
|
||||
"""Run synchronous example"""
|
||||
display_instruction("\nRunning Synchronous Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent],
|
||||
tasks=[sync_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = agents.start()
|
||||
display_interaction("Sync Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_async_example():
|
||||
"""Run asynchronous example"""
|
||||
display_instruction("\nRunning Asynchronous Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=[async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Async Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_mixed_example():
|
||||
"""Run mixed sync/async example"""
|
||||
display_instruction("\nRunning Mixed Sync/Async Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent, async_agent],
|
||||
tasks=[sync_task, async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Mixed Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
async def run_workflow_example():
|
||||
"""Run workflow example with both sync and async tasks"""
|
||||
display_instruction("\nRunning Workflow Example...", console)
|
||||
agents = PraisonAIAgents(
|
||||
agents=[sync_agent, async_agent],
|
||||
tasks=[workflow_sync_task, workflow_async_task],
|
||||
verbose=1,
|
||||
process="workflow"
|
||||
)
|
||||
result = await agents.astart()
|
||||
display_interaction("Workflow Example", f"Result: {result}", markdown=True, console=console)
|
||||
|
||||
# 8. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
display_instruction("Starting PraisonAI Agents Examples...", console)
|
||||
|
||||
try:
|
||||
# Run sync example in a separate thread to not block the event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, run_sync_example)
|
||||
|
||||
# Run async examples
|
||||
await run_async_example()
|
||||
await run_mixed_example()
|
||||
await run_workflow_example()
|
||||
|
||||
if error_logs:
|
||||
display_error("\nErrors encountered during execution:", console)
|
||||
for error in error_logs:
|
||||
display_error(error, console)
|
||||
except Exception as e:
|
||||
display_error(f"Error in main execution: {e}", console)
|
||||
error_logs.append(str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,152 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define async tool
|
||||
async def async_search_tool(query: str) -> Dict:
|
||||
"""Perform asynchronous search and return structured results."""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
|
||||
return {
|
||||
"query": query,
|
||||
"results": results,
|
||||
"total_results": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error during async search: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# 3. Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
print(f"Processed JSON result: {output.json_dict}")
|
||||
elif output.output_format == "Pydantic":
|
||||
print(f"Processed Pydantic result: {output.pydantic}")
|
||||
|
||||
# 4. Create specialized agents
|
||||
async_agent = Agent(
|
||||
name="AsyncSearchAgent",
|
||||
role="Search Specialist",
|
||||
goal="Perform fast parallel searches with structured results",
|
||||
backstory="Expert in efficient data retrieval and parallel search operations",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
name="SummaryAgent",
|
||||
role="Research Synthesizer",
|
||||
goal="Create concise summaries from multiple search results",
|
||||
backstory="Expert in analyzing and synthesizing information from multiple sources",
|
||||
self_reflect=True,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create async tasks
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="Search for 'Async programming' and return results in JSON format with query, results array, and total_results count.",
|
||||
expected_output="SearchResult model with structured data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
)
|
||||
|
||||
async def run_parallel_tasks():
|
||||
"""Run multiple async tasks in parallel"""
|
||||
print("\nRunning Parallel Async Tasks...")
|
||||
|
||||
# Define different search topics
|
||||
search_topics = [
|
||||
"Latest AI Developments 2024",
|
||||
"Machine Learning Best Practices",
|
||||
"Neural Networks Architecture"
|
||||
]
|
||||
|
||||
# Create tasks for different topics
|
||||
parallel_tasks = [
|
||||
Task(
|
||||
name=f"search_task_{i}",
|
||||
description=f"Search for '{topic}' and return structured results with query details and findings.",
|
||||
expected_output="SearchResult model with search data",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summarization task
|
||||
summary_task = Task(
|
||||
name="summary_task",
|
||||
description="Analyze all search results and create a concise summary highlighting key findings, patterns, and implications.",
|
||||
expected_output="Structured summary with key findings and insights",
|
||||
agent=summary_agent,
|
||||
async_execution=False,
|
||||
callback=async_callback,
|
||||
context=parallel_tasks
|
||||
)
|
||||
|
||||
# Create a single PraisonAIAgents instance with both agents
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent, summary_agent],
|
||||
tasks=parallel_tasks + [summary_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
|
||||
# Run all tasks
|
||||
results = await agents.astart()
|
||||
print(f"Tasks Results: {results}")
|
||||
|
||||
# Return results in a serializable format
|
||||
return {
|
||||
"search_results": {
|
||||
"task_status": {k: v for k, v in results["task_status"].items() if k != summary_task.id},
|
||||
"task_results": [str(results["task_results"][i]) if results["task_results"][i] else None
|
||||
for i in range(len(parallel_tasks))]
|
||||
},
|
||||
"summary": str(results["task_results"][summary_task.id]) if results["task_results"].get(summary_task.id) else None,
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
# 6. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
print("Starting Async AI Agents Examples...")
|
||||
|
||||
try:
|
||||
await run_parallel_tasks()
|
||||
except Exception as e:
|
||||
print(f"Error in main execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,248 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Dict
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents, TaskOutput
|
||||
from duckduckgo_search import DDGS
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 1. Define output model for structured results
|
||||
class SearchResult(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, str]]
|
||||
total_results: int
|
||||
|
||||
# 2. Define async tool
|
||||
async def async_search_tool(query: str) -> Dict:
|
||||
"""
|
||||
Asynchronous search using DuckDuckGo.
|
||||
Args:
|
||||
query (str): The search query.
|
||||
Returns:
|
||||
dict: Search results in SearchResult model format
|
||||
"""
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=5):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
|
||||
# Format response to match SearchResult model
|
||||
return {
|
||||
"query": query,
|
||||
"results": results,
|
||||
"total_results": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error during async search: {e}")
|
||||
return {
|
||||
"query": query,
|
||||
"results": [],
|
||||
"total_results": 0
|
||||
}
|
||||
|
||||
# 3. Define async callback
|
||||
async def async_callback(output: TaskOutput):
|
||||
await asyncio.sleep(1) # Simulate processing
|
||||
if output.output_format == "JSON":
|
||||
print(f"Processed JSON result: {output.json_dict}")
|
||||
elif output.output_format == "Pydantic":
|
||||
print(f"Processed Pydantic result: {output.pydantic}")
|
||||
|
||||
# 4. Create specialized agents
|
||||
async_agent = Agent(
|
||||
name="AsyncSearchAgent",
|
||||
role="Asynchronous Search Specialist",
|
||||
goal="Perform fast and efficient asynchronous searches with structured results",
|
||||
backstory="Expert in parallel search operations and data retrieval",
|
||||
tools=[async_search_tool],
|
||||
self_reflect=False,
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
name="SummaryAgent",
|
||||
role="Research Synthesizer",
|
||||
goal="Create comprehensive summaries and identify patterns across multiple search results",
|
||||
backstory="""Expert in analyzing and synthesizing information from multiple sources.
|
||||
Skilled at identifying patterns, trends, and connections between different topics.
|
||||
Specializes in creating clear, structured summaries that highlight key insights.""",
|
||||
self_reflect=True, # Enable self-reflection for better summary quality
|
||||
verbose=True,
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# 5. Create async tasks
|
||||
async_task = Task(
|
||||
name="async_search",
|
||||
description="""Search for 'Async programming' and return results in the following JSON format:
|
||||
{
|
||||
"query": "the search query",
|
||||
"results": [
|
||||
{
|
||||
"title": "result title",
|
||||
"url": "result url",
|
||||
"snippet": "result snippet"
|
||||
}
|
||||
],
|
||||
"total_results": number of results
|
||||
}""",
|
||||
expected_output="SearchResult model with query details and results",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
)
|
||||
|
||||
# 6. Example usage functions
|
||||
async def run_single_task():
|
||||
"""Run single async task"""
|
||||
print("\nRunning Single Async Task...")
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=[async_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
result = await agents.astart()
|
||||
print(f"Single Task Result: {result}")
|
||||
|
||||
async def run_parallel_tasks():
|
||||
"""Run multiple async tasks in parallel"""
|
||||
print("\nRunning Parallel Async Tasks...")
|
||||
|
||||
# Define different search topics
|
||||
search_topics = [
|
||||
"Latest AI Developments 2024",
|
||||
"Machine Learning Best Practices",
|
||||
"Neural Networks Architecture"
|
||||
]
|
||||
|
||||
# Create tasks for different topics
|
||||
parallel_tasks = [
|
||||
Task(
|
||||
name=f"search_task_{i}",
|
||||
description=f"""Search for '{topic}' and return results in the following JSON format:
|
||||
{{
|
||||
"query": "{topic}",
|
||||
"results": [
|
||||
{{
|
||||
"title": "result title",
|
||||
"url": "result url",
|
||||
"snippet": "result snippet"
|
||||
}}
|
||||
],
|
||||
"total_results": number of results
|
||||
}}""",
|
||||
expected_output="SearchResult model with detailed information",
|
||||
agent=async_agent,
|
||||
async_execution=True,
|
||||
callback=async_callback,
|
||||
output_json=SearchResult
|
||||
) for i, topic in enumerate(search_topics)
|
||||
]
|
||||
|
||||
# Create summarization task with the specialized summary agent
|
||||
summary_task = Task(
|
||||
name="summary_task",
|
||||
description="""As a Research Synthesizer, analyze the search results and create a comprehensive summary. Your task:
|
||||
|
||||
1. Analyze Results:
|
||||
- Review all search results thoroughly
|
||||
- Extract key findings from each topic
|
||||
- Identify main themes and concepts
|
||||
|
||||
2. Find Connections:
|
||||
- Identify relationships between topics
|
||||
- Spot common patterns or contradictions
|
||||
- Note emerging trends across sources
|
||||
|
||||
3. Create Structured Summary:
|
||||
- Main findings per topic
|
||||
- Cross-cutting themes
|
||||
- Emerging trends
|
||||
- Practical implications
|
||||
- Future directions
|
||||
|
||||
4. Quality Checks:
|
||||
- Ensure all topics are covered
|
||||
- Verify accuracy of connections
|
||||
- Confirm clarity of insights
|
||||
- Validate practical relevance
|
||||
|
||||
Present the summary in a clear, structured format with sections for findings, patterns, trends, and implications.""",
|
||||
expected_output="""A comprehensive research synthesis containing:
|
||||
- Detailed findings from each search topic
|
||||
- Cross-topic patterns and relationships
|
||||
- Emerging trends and their implications
|
||||
- Practical applications and future directions""",
|
||||
agent=summary_agent, # Use the specialized summary agent
|
||||
async_execution=False, # Run synchronously after search tasks
|
||||
callback=async_callback
|
||||
)
|
||||
|
||||
# First run parallel search tasks
|
||||
agents = PraisonAIAgents(
|
||||
agents=[async_agent],
|
||||
tasks=parallel_tasks, # Only run search tasks first
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
search_results = await agents.astart()
|
||||
print(f"Search Tasks Results: {search_results}")
|
||||
|
||||
# Create task objects with results for context
|
||||
completed_tasks = []
|
||||
for i, topic in enumerate(search_topics):
|
||||
task = Task(
|
||||
name=f"search_task_{i}_result",
|
||||
description=f"Search results for: {topic}",
|
||||
expected_output="Search results from previous task",
|
||||
agent=async_agent,
|
||||
result=search_results["task_results"][i]
|
||||
)
|
||||
completed_tasks.append(task)
|
||||
|
||||
# Update summary task with context from search results
|
||||
summary_task.context = completed_tasks
|
||||
|
||||
# Run summarization task with summary agent
|
||||
summary_agents = PraisonAIAgents(
|
||||
agents=[summary_agent], # Use summary agent for synthesis
|
||||
tasks=[summary_task],
|
||||
verbose=1,
|
||||
process="sequential"
|
||||
)
|
||||
summary_result = await summary_agents.astart()
|
||||
print(f"Summary Task Result: {summary_result}")
|
||||
|
||||
# Return results in a serializable format
|
||||
return {
|
||||
"search_results": {
|
||||
"task_status": search_results["task_status"],
|
||||
"task_results": [str(result) if result else None for result in search_results["task_results"]]
|
||||
},
|
||||
"summary": str(summary_result),
|
||||
"topics": search_topics
|
||||
}
|
||||
|
||||
# 7. Main execution
|
||||
async def main():
|
||||
"""Main execution function"""
|
||||
print("Starting Async AI Agents Examples...")
|
||||
|
||||
try:
|
||||
# Run different async patterns
|
||||
await run_single_task()
|
||||
await run_parallel_tasks()
|
||||
except Exception as e:
|
||||
print(f"Error in main execution: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,11 @@
|
||||
from praisonaiagents import AutoAgents
|
||||
from praisonaiagents.tools import duckduckgo
|
||||
|
||||
agents = AutoAgents(
|
||||
instructions="Search for information about AI Agents",
|
||||
tools=[duckduckgo],
|
||||
process="sequential",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
agents.start()
|
||||
@@ -0,0 +1,50 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
import json
|
||||
from e2b_code_interpreter import Sandbox
|
||||
|
||||
def code_interpreter(code: str):
|
||||
"""
|
||||
A function to demonstrate running Python code dynamically using e2b_code_interpreter.
|
||||
"""
|
||||
print(f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}")
|
||||
exec_result = Sandbox().run_code(code)
|
||||
if exec_result.error:
|
||||
print("[Code Interpreter error]", exec_result.error)
|
||||
return {"error": str(exec_result.error)}
|
||||
else:
|
||||
results = []
|
||||
for result in exec_result.results:
|
||||
if hasattr(result, '__iter__'):
|
||||
results.extend(list(result))
|
||||
else:
|
||||
results.append(str(result))
|
||||
logs = {"stdout": list(exec_result.logs.stdout), "stderr": list(exec_result.logs.stderr)}
|
||||
return json.dumps({"results": results, "logs": logs})
|
||||
|
||||
code_agent = Agent(
|
||||
name="code_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in writing Python scripts",
|
||||
self_reflect=False
|
||||
)
|
||||
execution_agent = Agent(
|
||||
name="execution_agent",
|
||||
llm="gpt-4o-mini",
|
||||
backstory="Expert in executing Python scripts",
|
||||
self_reflect=False,
|
||||
tools=[code_interpreter]
|
||||
)
|
||||
|
||||
code_agent_task = Task(
|
||||
description="Write a simple Python script to print 'Hello, World!'",
|
||||
expected_output="A Python script that prints 'Hello, World!'",
|
||||
agent=code_agent
|
||||
)
|
||||
execution_agent_task = Task(
|
||||
description="Execute the Python script",
|
||||
expected_output="The output of the Python script",
|
||||
agent=execution_agent
|
||||
)
|
||||
|
||||
agents = PraisonAIAgents(agents=[code_agent, execution_agent], tasks=[code_agent_task, execution_agent_task])
|
||||
agents.start()
|
||||
@@ -0,0 +1,70 @@
|
||||
from praisonaiagents import Agent, Task, PraisonAIAgents
|
||||
|
||||
# Create generator and evaluator agents
|
||||
generator = Agent(
|
||||
name="Generator",
|
||||
role="Solution generator",
|
||||
goal="Generate initial solutions and incorporate feedback",
|
||||
instructions=(
|
||||
"1. Look at the context from previous tasks.\n"
|
||||
"2. If you see that you have already produced 2 points, then add another 2 new points "
|
||||
" so that the total becomes 10.\n"
|
||||
"3. Otherwise, just produce the first 2 points.\n"
|
||||
"4. Return only the final list of points, with no extra explanation."
|
||||
)
|
||||
)
|
||||
|
||||
evaluator = Agent(
|
||||
name="Evaluator",
|
||||
role="Solution evaluator",
|
||||
goal="Evaluate solutions and provide improvement feedback",
|
||||
instructions=(
|
||||
"1. Count how many lines in the response start with a number and a period (like '1. ' or '2. ').\n"
|
||||
"2. If there are 10 or more, respond with 'done'.\n"
|
||||
"3. Otherwise, respond with 'more'.\n"
|
||||
"4. Return only the single word: 'done' or 'more'."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Create tasks for the feedback loop
|
||||
generate_task = Task(
|
||||
name="generate",
|
||||
description="Write 2 points about AI incuding if anything exiting from previous points",
|
||||
expected_output="2 points",
|
||||
agent=generator,
|
||||
is_start=True,
|
||||
task_type="decision",
|
||||
next_tasks=["evaluate"]
|
||||
)
|
||||
|
||||
evaluate_task = Task(
|
||||
name="evaluate",
|
||||
description="Check if there are 10 points about AI",
|
||||
expected_output="more or done",
|
||||
agent=evaluator,
|
||||
next_tasks=["generate"],
|
||||
context=[generate_task],
|
||||
task_type="decision",
|
||||
condition={
|
||||
"more": ["generate"], # Continue to generate
|
||||
"done": [""] # Exit when optimization complete
|
||||
}
|
||||
)
|
||||
|
||||
# Create workflow manager
|
||||
workflow = PraisonAIAgents(
|
||||
agents=[generator, evaluator],
|
||||
tasks=[generate_task, evaluate_task],
|
||||
process="workflow",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Run optimization workflow
|
||||
results = workflow.start()
|
||||
|
||||
# Print results
|
||||
print("\nEvaluator-Optimizer Results:")
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"Task {task_id}: {result.raw}")
|
||||
@@ -0,0 +1,204 @@
|
||||
from praisonaiagents import (
|
||||
Agent,
|
||||
Task,
|
||||
PraisonAIAgents,
|
||||
error_logs,
|
||||
register_display_callback,
|
||||
sync_display_callbacks,
|
||||
async_display_callbacks
|
||||
)
|
||||
from duckduckgo_search import DDGS
|
||||
from rich.console import Console
|
||||
import json
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
filename='ai_interactions.log',
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Callback functions for different display types
|
||||
def interaction_callback(message=None, response=None, markdown=None, generation_time=None):
|
||||
"""Callback for display_interaction"""
|
||||
logging.info(f"""
|
||||
=== INTERACTION ===
|
||||
Time: {datetime.now()}
|
||||
Generation Time: {generation_time}s
|
||||
Message: {message}
|
||||
Response: {response}
|
||||
Markdown: {markdown}
|
||||
""")
|
||||
|
||||
def error_callback(message=None):
|
||||
"""Callback for display_error"""
|
||||
logging.error(f"""
|
||||
=== ERROR ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def tool_call_callback(message=None):
|
||||
"""Callback for display_tool_call"""
|
||||
logging.info(f"""
|
||||
=== TOOL CALL ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def instruction_callback(message=None):
|
||||
"""Callback for display_instruction"""
|
||||
logging.info(f"""
|
||||
=== INSTRUCTION ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def self_reflection_callback(message=None):
|
||||
"""Callback for display_self_reflection"""
|
||||
logging.info(f"""
|
||||
=== SELF REFLECTION ===
|
||||
Time: {datetime.now()}
|
||||
Message: {message}
|
||||
""")
|
||||
|
||||
def generating_callback(content=None, elapsed_time=None):
|
||||
"""Callback for display_generating"""
|
||||
logging.info(f"""
|
||||
=== GENERATING ===
|
||||
Time: {datetime.now()}
|
||||
Content: {content}
|
||||
Elapsed Time: {elapsed_time}
|
||||
""")
|
||||
|
||||
# Register all callbacks
|
||||
register_display_callback('interaction', interaction_callback)
|
||||
register_display_callback('error', error_callback)
|
||||
register_display_callback('tool_call', tool_call_callback)
|
||||
register_display_callback('instruction', instruction_callback)
|
||||
register_display_callback('self_reflection', self_reflection_callback)
|
||||
# register_display_callback('generating', generating_callback)
|
||||
|
||||
def task_callback(output):
|
||||
"""Callback for task completion"""
|
||||
logging.info(f"""
|
||||
=== TASK COMPLETED ===
|
||||
Time: {datetime.now()}
|
||||
Description: {output.description}
|
||||
Agent: {output.agent}
|
||||
Output: {output.raw[:200]}...
|
||||
""")
|
||||
|
||||
def internet_search_tool(query) -> list:
|
||||
"""
|
||||
Perform a search using DuckDuckGo.
|
||||
|
||||
Args:
|
||||
query (str): The search query.
|
||||
|
||||
Returns:
|
||||
list: A list of search result titles and URLs.
|
||||
"""
|
||||
try:
|
||||
results = []
|
||||
ddgs = DDGS()
|
||||
for result in ddgs.text(keywords=query, max_results=10):
|
||||
results.append({
|
||||
"title": result.get("title", ""),
|
||||
"url": result.get("href", ""),
|
||||
"snippet": result.get("body", "")
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during DuckDuckGo search: {e}")
|
||||
return []
|
||||
|
||||
def main():
|
||||
# Create agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
role="Senior Research Analyst",
|
||||
goal="Uncover cutting-edge developments in AI and data science",
|
||||
backstory="""You are an expert at a technology research group,
|
||||
skilled in identifying trends and analyzing complex data.""",
|
||||
verbose=True,
|
||||
allow_delegation=False,
|
||||
tools=[internet_search_tool],
|
||||
llm="gpt-4o",
|
||||
markdown=True,
|
||||
reflect_llm="gpt-4o",
|
||||
min_reflect=2,
|
||||
max_reflect=4
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
role="Tech Content Strategist",
|
||||
goal="Craft compelling content on tech advancements",
|
||||
backstory="""You are a content strategist known for
|
||||
making complex tech topics interesting and easy to understand.""",
|
||||
verbose=True,
|
||||
allow_delegation=True,
|
||||
llm="gpt-4o",
|
||||
tools=[],
|
||||
markdown=True
|
||||
)
|
||||
|
||||
# Create tasks with callbacks
|
||||
task1 = Task(
|
||||
name="research_task",
|
||||
description="""Analyze 2024's AI advancements.
|
||||
Find major trends, new technologies, and their effects.""",
|
||||
expected_output="""A detailed report on 2024 AI advancements""",
|
||||
agent=researcher,
|
||||
tools=[internet_search_tool],
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
task2 = Task(
|
||||
name="writing_task",
|
||||
description="""Create a blog post about major AI advancements using the insights you have.
|
||||
Make it interesting, clear, and suited for tech enthusiasts.
|
||||
It should be at least 4 paragraphs long.""",
|
||||
expected_output="A blog post of at least 4 paragraphs",
|
||||
agent=writer,
|
||||
context=[task1],
|
||||
callback=task_callback,
|
||||
tools=[]
|
||||
)
|
||||
|
||||
task3 = Task(
|
||||
name="json_task",
|
||||
description="""Create a json object with a title of "My Task" and content of "My content".""",
|
||||
expected_output="""JSON output with title and content""",
|
||||
agent=researcher,
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
task4 = Task(
|
||||
name="save_output_task",
|
||||
description="""Save the AI blog post to a file""",
|
||||
expected_output="""File saved successfully""",
|
||||
agent=writer,
|
||||
context=[task2],
|
||||
output_file='test.txt',
|
||||
create_directory=True,
|
||||
callback=task_callback
|
||||
)
|
||||
|
||||
# Create and run agents manager
|
||||
agents = PraisonAIAgents(
|
||||
agents=[researcher, writer],
|
||||
tasks=[task1, task2, task3, task4],
|
||||
verbose=True,
|
||||
process="sequential",
|
||||
manager_llm="gpt-4o"
|
||||
)
|
||||
|
||||
agents.start()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loaded 100 of 381 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user