universal-mcp-agents 0.1.14__tar.gz → 0.1.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp_agents-0.1.15/.github/workflows/evals.yml +61 -0
- universal_mcp_agents-0.1.15/.github/workflows/lint.yml +31 -0
- universal_mcp_agents-0.1.15/.github/workflows/release-please.yml +67 -0
- universal_mcp_agents-0.1.15/.github/workflows/tests.yml +39 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/.pre-commit-config.yaml +3 -7
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/PKG-INFO +3 -3
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/bump_and_release.sh +1 -1
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/pyproject.toml +10 -5
- universal_mcp_agents-0.1.15/src/evals/datasets/tasks.jsonl +32 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/prompts.py +2 -2
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/tests/test_agents.py +32 -33
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/__init__.py +1 -1
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/base.py +2 -1
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/__main__.py +4 -3
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/agent.py +1 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/graph.py +7 -4
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/tools.py +4 -5
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/__main__.py +49 -23
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/builder/builder.py +213 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/helper.py +4 -6
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/builder/prompts.py +107 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/state.py +1 -1
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/__init__.py +4 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/agent.py +12 -5
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/langgraph_agent.py +14 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/llm_tool.py +1 -2
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/playbook_agent.py +353 -0
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/prompts.py +241 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/sandbox.py +43 -32
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/state.py +36 -0
- universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/tools.py +180 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/utils.py +53 -18
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/__main__.py +3 -2
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/prompts.py +1 -1
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/tool_node.py +17 -12
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/utils.py +18 -12
- universal_mcp_agents-0.1.15/test.py +25 -0
- universal_mcp_agents-0.1.15/todo.md +157 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/uv.lock +1421 -852
- universal_mcp_agents-0.1.14/src/evals/datasets/tasks.jsonl +0 -21
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/builder/builder.py +0 -214
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/builder/prompts.py +0 -54
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/__init__.py +0 -3
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -17
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/prompts.py +0 -167
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/state.py +0 -12
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/1-unsubscribe.yaml +0 -4
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/10-reddit2.yaml +0 -10
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/11-github.yaml +0 -14
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2-reddit.yaml +0 -27
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2.1-instructions.md +0 -81
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2.2-instructions.md +0 -71
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/3-earnings.yaml +0 -4
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/4-maps.yaml +0 -41
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/5-gmailreply.yaml +0 -8
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/6-contract.yaml +0 -6
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/7-overnight.yaml +0 -14
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/8-sheets_chart.yaml +0 -25
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/9-learning.yaml +0 -9
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/__init__.py +0 -51
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/__main__.py +0 -28
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/graph.py +0 -85
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/prompts.py +0 -14
- universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/state.py +0 -11
- universal_mcp_agents-0.1.14/test.py +0 -61
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/.gitignore +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/GEMINI.md +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/PROMPTS.md +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/README.md +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/__init__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/dataset.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/datasets/codeact.jsonl +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/datasets/exact.jsonl +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/evaluators.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/run.py +1 -1
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/utils.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/context.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/state.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/cli.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/__init__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/__main__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/agent.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/models.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/prompts.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/state.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/utils.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/__main__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/config.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/hil.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/llm.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/react.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/simple.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/llm/__init__.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/llm/app.py +0 -0
- {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/ui/app.py +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: evals
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
eval:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
include:
|
|
16
|
+
- { agent: bigtool, dataset: src/evals/datasets/tasks.jsonl, evaluator: llm_as_judge, difficulty: easy }
|
|
17
|
+
- { agent: codeact-repl, dataset: src/evals/datasets/codeact.jsonl, evaluator: codeact, difficulty: none }
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.11'
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v3
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: |
|
|
32
|
+
uv sync --all-extras
|
|
33
|
+
|
|
34
|
+
- name: Run eval
|
|
35
|
+
env:
|
|
36
|
+
PYTHONUNBUFFERED: '1'
|
|
37
|
+
run: |
|
|
38
|
+
mkdir -p dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}
|
|
39
|
+
if [ "${{ matrix.difficulty }}" = "none" ]; then
|
|
40
|
+
uv run python -u src/evals/run.py \
|
|
41
|
+
${{ matrix.agent }} \
|
|
42
|
+
${{ matrix.dataset }} \
|
|
43
|
+
${{ matrix.evaluator }} \
|
|
44
|
+
2>&1 | tee dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}/run-${{ matrix.agent }}-${{ matrix.evaluator }}-$(basename ${{ matrix.dataset }} .jsonl)-none.log
|
|
45
|
+
else
|
|
46
|
+
uv run python -u src/evals/run.py \
|
|
47
|
+
${{ matrix.agent }} \
|
|
48
|
+
${{ matrix.dataset }} \
|
|
49
|
+
${{ matrix.evaluator }} \
|
|
50
|
+
--difficulty ${{ matrix.difficulty }} \
|
|
51
|
+
2>&1 | tee dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}/run-${{ matrix.agent }}-${{ matrix.evaluator }}-$(basename ${{ matrix.dataset }} .jsonl)-${{ matrix.difficulty }}.log
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Upload eval artifacts
|
|
55
|
+
uses: actions/upload-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: eval-artifacts-${{ matrix.agent }}-${{ matrix.evaluator }}
|
|
58
|
+
path: dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}
|
|
59
|
+
if-no-files-found: warn
|
|
60
|
+
|
|
61
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Check out repository
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: '3.11'
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v3
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
uv sync --all-extras
|
|
27
|
+
|
|
28
|
+
- name: Run ruff
|
|
29
|
+
run: |
|
|
30
|
+
uv run ruff check .
|
|
31
|
+
uv run ruff format --check .
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: release-please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: googleapis/release-please-action@v4
|
|
17
|
+
id: release
|
|
18
|
+
with:
|
|
19
|
+
release-type: python
|
|
20
|
+
package-name: agents
|
|
21
|
+
default-branch: main
|
|
22
|
+
|
|
23
|
+
- name: Checkout repository
|
|
24
|
+
if: ${{ steps.release.outputs.release_created }}
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
with:
|
|
27
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
fetch-depth: 0
|
|
29
|
+
|
|
30
|
+
- name: Add Authors and Update Release
|
|
31
|
+
if: ${{ steps.release.outputs.release_created }}
|
|
32
|
+
env:
|
|
33
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
RELEASE_TAG: ${{ steps.release.outputs.tag_name }}
|
|
35
|
+
run: |
|
|
36
|
+
set -e
|
|
37
|
+
|
|
38
|
+
TEMP_CHANGELOG="CHANGELOG.md.new"
|
|
39
|
+
touch "$TEMP_CHANGELOG"
|
|
40
|
+
|
|
41
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
42
|
+
while IFS= read -r line; do
|
|
43
|
+
if [[ "$line" =~ \*\ (.*)\ \(\[([a-f0-9]{7,40})\]\(.* ]]; then
|
|
44
|
+
commit_hash="${BASH_REMATCH[2]}"
|
|
45
|
+
github_user=$(gh api "repos/${{ github.repository }}/commits/${commit_hash}" | jq -r '.author.login // "unknown"')
|
|
46
|
+
echo "${line} by @${github_user}" >> "$TEMP_CHANGELOG"
|
|
47
|
+
else
|
|
48
|
+
echo "$line" >> "$TEMP_CHANGELOG"
|
|
49
|
+
fi
|
|
50
|
+
done < "CHANGELOG.md"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
mv "$TEMP_CHANGELOG" "CHANGELOG.md"
|
|
54
|
+
|
|
55
|
+
RELEASE_BODY=$(awk "/^## \\[?${RELEASE_TAG#v}/{flag=1;next} /^## / && flag{exit} flag" CHANGELOG.md)
|
|
56
|
+
if [ -z "$RELEASE_BODY" ]; then
|
|
57
|
+
RELEASE_BODY="Release $RELEASE_TAG"
|
|
58
|
+
fi
|
|
59
|
+
gh release edit "$RELEASE_TAG" --notes "$RELEASE_BODY"
|
|
60
|
+
|
|
61
|
+
git config user.name "github-actions[bot]"
|
|
62
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
63
|
+
git add CHANGELOG.md || true
|
|
64
|
+
git commit -m "update CHANGELOG.md with author info [skip ci] [skip release]" || true
|
|
65
|
+
git push || true
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Check out repository
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v3
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
uv sync --all-extras
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: |
|
|
30
|
+
uv run pytest -s -q --maxfail=1 --disable-warnings --junitxml=junit.xml
|
|
31
|
+
env:
|
|
32
|
+
PYTHONUNBUFFERED: "1"
|
|
33
|
+
|
|
34
|
+
- name: Store artifacts
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: test-artifacts
|
|
38
|
+
path: junit.xml
|
|
39
|
+
if-no-files-found: ignore
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
fail_fast: false
|
|
2
2
|
|
|
3
3
|
repos:
|
|
4
|
-
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
5
|
-
rev: v3.1.0
|
|
6
|
-
hooks:
|
|
7
|
-
- id: prettier
|
|
8
|
-
types_or: [yaml, json5]
|
|
9
4
|
|
|
10
5
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
-
rev: v0.
|
|
6
|
+
rev: v0.13.3
|
|
12
7
|
hooks:
|
|
13
8
|
# Run the linter.
|
|
14
9
|
- id: ruff-check
|
|
15
|
-
args: [--fix]
|
|
10
|
+
args: ["--fix", "src", "--config", "pyproject.toml"]
|
|
16
11
|
# Run the formatter.
|
|
17
12
|
- id: ruff-format
|
|
13
|
+
args: ["src", "--config", "pyproject.toml"]
|
|
18
14
|
|
|
19
15
|
# - repo: https://github.com/pre-commit/mirrors-mypy
|
|
20
16
|
# rev: v1.8.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-agents
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Project-URL: Homepage, https://github.com/universal-mcp/applications
|
|
6
6
|
Project-URL: Repository, https://github.com/universal-mcp/applications
|
|
@@ -12,8 +12,8 @@ Requires-Dist: langchain-google-genai>=2.1.10
|
|
|
12
12
|
Requires-Dist: langchain-openai>=0.3.32
|
|
13
13
|
Requires-Dist: langgraph>=0.6.6
|
|
14
14
|
Requires-Dist: typer>=0.17.4
|
|
15
|
-
Requires-Dist: universal-mcp-applications>=0.1.
|
|
16
|
-
Requires-Dist: universal-mcp>=0.1.
|
|
15
|
+
Requires-Dist: universal-mcp-applications>=0.1.24
|
|
16
|
+
Requires-Dist: universal-mcp>=0.1.24rc25
|
|
17
17
|
Provides-Extra: dev
|
|
18
18
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
19
19
|
Requires-Dist: ruff; extra == 'dev'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "universal-mcp-agents"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.15"
|
|
10
10
|
description = "Add your description here"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [
|
|
@@ -19,8 +19,8 @@ dependencies = [
|
|
|
19
19
|
"langchain-openai>=0.3.32",
|
|
20
20
|
"langgraph>=0.6.6",
|
|
21
21
|
"typer>=0.17.4",
|
|
22
|
-
"universal-mcp>=0.1.
|
|
23
|
-
"universal-mcp-applications>=0.1.
|
|
22
|
+
"universal-mcp>=0.1.24rc25",
|
|
23
|
+
"universal-mcp-applications>=0.1.24",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.license]
|
|
@@ -51,7 +51,7 @@ packages = [
|
|
|
51
51
|
|
|
52
52
|
[tool.coverage.run]
|
|
53
53
|
source = [
|
|
54
|
-
"src",
|
|
54
|
+
"src",
|
|
55
55
|
]
|
|
56
56
|
branch = true
|
|
57
57
|
|
|
@@ -68,6 +68,12 @@ lint.ignore = [
|
|
|
68
68
|
"E501", # Ignore line length errors
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
+
[tool.ruff.lint.pylint]
|
|
72
|
+
max-args = 10
|
|
73
|
+
max-statements = 85
|
|
74
|
+
max-returns = 10
|
|
75
|
+
max-branches = 37
|
|
76
|
+
|
|
71
77
|
|
|
72
78
|
[tool.ruff.format]
|
|
73
79
|
quote-style = "double"
|
|
@@ -83,4 +89,3 @@ asyncio_default_fixture_loop_scope = "module"
|
|
|
83
89
|
dev = [
|
|
84
90
|
"ruff>=0.13.0",
|
|
85
91
|
]
|
|
86
|
-
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{"user_input": "Send an email to manoj@agentr.dev with the subject 'Hello' and body 'This is a test of the Gmail agent.' from my Gmail account", "difficulty": 1, "required_tools": {"google_mail": ["send_email"]}}
|
|
2
|
+
{"user_input": "Show me events from today's Google Calendar.", "difficulty": 1, "required_tools": {"google_calendar": ["get_upcoming_events"]}}
|
|
3
|
+
{"user_input": "Fetch my last inbox mail from Microsoft Outlook", "difficulty": 1, "required_tools": {"outlook": ["list_user_messages"]}}
|
|
4
|
+
{"user_input": "Tell me how many meetings I have tomorrow and when they start from my Google Calendar.", "difficulty": 1, "required_tools": {"google_calendar": ["get_upcoming_events", "list_events"]}}
|
|
5
|
+
{"user_input": "Find the best restaurants in Goa using exa web search", "difficulty": 2, "required_tools": {"exa": ["search_with_filters"]}}
|
|
6
|
+
{"user_input": "List the unread emails from the last 24 hours from my Gmail, sorted by sender.", "difficulty": 2, "required_tools": {"google_mail": ["list_messages"]}}
|
|
7
|
+
{"user_input": "Create a meeting with aditakarsh@example.com on the topic of the latest trends in AI at 8PM today using Google Calendar.", "difficulty": 2, "required_tools": {"google_calendar": ["create_event", "create_event_from_text"]}}
|
|
8
|
+
{"user_input": "Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 1 day", "difficulty": 3, "required_tools": {"google_mail": ["list_messages"]}}
|
|
9
|
+
{"user_input": "Create a weekly expense report from my credit card transactions and categorize spending by type (food, transport, entertainment, etc.) in a Google Sheet", "difficulty": 3, "required_tools": {"google_sheet" : ["create_spreadsheet", "add_table"]}}
|
|
10
|
+
{"user_input": "search reddit for posts on elon musk and then post a meme on him on linkedin", "difficulty": 3, "required_tools": {"reddit" : ["search_reddit"], "linkedin": ["create_post"]}}
|
|
11
|
+
{"user_input": "Search for best cafes near IIT bombay using exa and make a google sheet out of it", "difficulty": 3, "required_tools": {"exa": ["search_with_filters"], "google_sheet": ["create_spreadsheet", "write_values_to_sheet", "add_table"]}}
|
|
12
|
+
{"user_input": "Create a Google Doc summarizing the last 5 merged pull requests in my GitHub repo- universal-mcp/universal-mcp, including links and commit highlights.", "difficulty": 4, "required_tools": {"github": ["list_pull_requests", "list_recent_commits"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
|
|
13
|
+
{"user_input": "Summarize the key insights from all marketing emails received yesterday from my Gmail and add a section in a Google Doc with action points.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
|
|
14
|
+
{"user_input": "Give me a report on the earnings of Oklo using web search, and projections for the company revenue, stock price", "difficulty": 4, "required_tools": {"tavily": ["search_and_summarize"]}}
|
|
15
|
+
{"user_input": "Track the top posts in r/startups over the past 7 days using Reddit and create a trend report on what's being discussed most (e.g., hiring, funding, MVPs) in a Google Doc.", "difficulty": 4, "required_tools": {"reddit": ["get_subreddit_posts", "get_subreddit_top_posts"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
|
|
16
|
+
{"user_input": "Generate a comparison table of SaaS tools for project management using web search, including pricing, features, and user ratings in a Google Sheet", "difficulty": 4, "required_tools": {"tavily": ["search_and_summarize"], "google_sheet": ["create_spreadsheet", "add_table"]}}
|
|
17
|
+
{"user_input": "What are the topics of my meetings today from Google Calendar and who are the attendees? Give a 1-line context for each attendee using LinkedIn or web search.", "difficulty": 4, "required_tools": {"google_calendar": ["get_upcoming_events", "list_events"], "scraper": ["linkedin_retrieve_profile"]}}
|
|
18
|
+
{"user_input": "Draft personalized LinkedIn outreach messages for 10 potential collaborators in the fintech space based on their recent posts using LinkedIn data in a Google Sheet", "difficulty": 5, "required_tools": {"scraper": ["linkedin_retrieve_profile", "linkedin_list_profile_posts"], "google_sheet": ["create_spreadsheet", "write_values_to_sheet"]}}
|
|
19
|
+
{"user_input": "Create a content calendar for next month with trending AI/ML topics using web search and optimal posting times based on my audience analytics in Google Sheets", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_sheet": ["get_values", "batch_get_values_by_range", "get_spreadsheet_metadata" , "create_spreadsheet", "add_sheet", "add_table"]}}
|
|
20
|
+
{"user_input": "Research the top 10 Y Combinator startups from the latest batch using web search and create a report on their industries and funding status in Google Docs", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_docs": ["create_document", "insert_text", "insert_table"]}}
|
|
21
|
+
{"user_input": "Find and summarize the key takeaways from the latest earnings calls of FAANG companies using web search and create a report in Google Docs", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_docs": ["create_document", "insert_text", "insert_table"]}}
|
|
22
|
+
{"user_input": "Find and extract unsubscribe links from all emails in my inbox from the last 7 days. List all unsubscribe links found with the email subject and sender.", "difficulty": 3, "required_tools": {"google_mail": ["list_messages", "get_message_details"]}}
|
|
23
|
+
{"user_input": "Process rows 2-5 from the Google Sheet (ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8). For each unprocessed row, extract Reddit post links, fetch post details and comments, analyze content relevance to AgentR/Wingmen products, classify into tiers 1-4, generate appropriate response drafts, and update the sheet with all findings.", "difficulty": 5, "required_tools": {"google_sheet": ["add_table", "append_values", "update_values", "format_cells", "get_spreadsheet_metadata", "batch_get_values_by_range"], "reddit": ["get_post_comments_details"], "google_mail": ["list_messages"]}}
|
|
24
|
+
{"user_input": "Fetch all open issues from the GitHub repository \"microsoft/vscode\" and add them to a new Google Sheet. Then create corresponding tasks in ClickUp for each issue with descriptions, tags, and \"In Progress\" status. Delete processed rows from the sheet after creating ClickUp tasks.", "difficulty": 5, "required_tools": {"google_sheet": ["get_values", "create_spreadsheet", "write_values_to_sheet", "delete_dimensions", "append_values", "update_values"], "clickup": ["tasks_create_new_task", "spaces_get_details", "lists_get_list_details", "tasks_get_list_tasks"], "github": ["search_issues", "update_issue"]}}
|
|
25
|
+
{"user_input": "Goal: Process unprocessed rows in a fixed Google Sheet, scrape Reddit for context, filter posts, and generate short, natural comments linking to AgentR/Wingmen when relevant. Workflow: 1) Sheet & Row Selection: Fixed Sheet ID 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8, tab Posts. Process rows 2-5 (first 4 unprocessed rows) immediately without asking for user input. Only process rows with empty Match Type (Col I) and no Tier 1-4 assigned. 2) Reddit Context Fetch: Extract Post Link & ID. Use reddit to fetch post upvotes + top comments (max 5). Ensure post/comment is active, visible, and unlocked. 3) Filtration & Fit: Classify content (developer, consumer, anecdotal). Apply GTM Filtration to skip irrelevant, negative, political, or low-quality posts. Identify direct or adjacent fit to AgentR (Universal MCP Server) or Wingmen. Decide platform + account type: Direct fit/competitor mention → Technical Q = Team account, Non-technical = Burner account. Adjacent fit → Official account. Decide reply target (original comment/post or parent post). 4) Comment Generation: For Tier 1-3, craft a 2-3 line, context-aware, conversational reply. Mention AgentR/Wingmen organically, avoid sales tone or forced CTAs. Use light imperfections for human tone. Skip negative sentiment entirely. One comment per post. 5) Populate Output: Fill Upvote Count, Match Type, Account Type, Response Draft, Respond on. Return updated Google Sheet link. Tier Definitions: Tier 1 = Deep MCP, AI agent, tool integrations, or architecture discussions where infra is highly relevant. Tier 2 = Specific workflows, automation tooling, or productivity systems where Wingmen or MCP Server could be useful. Tier 3 = Broader ecosystem (LangChain/CrewAI/agent tooling) where a soft recommendation adds value. Tier 4 = Unclear, generic, sarcastic, hostile, or irrelevant mentions — skip. Execute immediately using the fixed Google Sheet ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8, tab \"Posts\". Process rows(first 4 unprocessed rows) without asking for user input. Only process rows where Match Type (Column I) is empty. For each row, extract the Post Link, fetch Reddit data, apply GTM filtration, generate appropriate responses, and update the sheet. Return the updated Google Sheet link when complete.", "difficulty": 5, "required_tools": {"reddit": ["get_post_comments_details"], "google_sheet": ["update_values", "get_values", "get_spreadsheet_metadata", "batch_get_values_by_range"]}}
|
|
26
|
+
{"user_input": "Generate a financial flash report for Apple Inc. Research their latest earnings data including revenue, net income, EPS, and year-over-year changes. Create a formatted report with highlights, upcoming events, and summary. Present the report in chat and email it to adit@agentr.dev.", "difficulty": 4, "required_tools": {"exa": ["answer"], "google_mail": ["send_email"]}}
|
|
27
|
+
{"user_input": "Objective: Find businesses from Google Maps for a given category & location, store them in a Google Sheet, then process unprocessed leads to scrape emails and sync with HubSpot CRM. Stage 1 - Lead Discovery Get coordinates of Area + City. Search on Google Maps with category & coordinates. Extract: Name, Google Maps URL, Address, Phone, Website; leave Email & CRM Status blank. Sheet: Name: {Area}, {City} Leads - {Category} - {dd-mmm} If exists → append non-duplicate rows; else create in folder \"Leads from Google Maps\" (ID: 142QBejJX0jAqzDz_NHdwVTkcmagoog__). Add headers: Name | Google Maps URL | Address | Phone | Website | Email | CRM Status. Populate with businesses found. Edge Cases: No results → return message, skip sheet creation. Missing data → leave blank. Stage 2 - Lead Processing & CRM Sync Locate sheet in Google Drive, ensure headers match. Parse category from sheet name. Identify unprocessed rows (CRM Status blank) — by default process the first, or a specified row/range/count. Scrape Website for Email: If website exists → scrape homepage/contact page; fallback to firecrawl_scrape_url. Save found email in sheet. HubSpot Handling: Search contact by email/website/phone. If not found → create with available details, Lead Status = New, add note {Area, City} — {Category} — {Google Maps URL}. If exists → append note; keep other fields unchanged. Save HubSpot Contact URL/ID in sheet. Update CRM Status: Lead Created, Lead Creation Failed, Website not found, Email not found, etc. Edge Cases: No Website → create with phone; mark Website not found. No Email → create; mark Email not found. Email already in sheet → skip row. Execute immediately for \"Cafes\" near \"IIT Bombay\" in \"Mumbai\" without asking for confirmation.", "difficulty": 5, "required_tools": {"serpapi": ["google_maps_search"], "firecrawl": ["scrape_url"], "google_drive": ["get_file_details", "create_folder", "find_folder_id_by_name", "search_files"], "google_sheet": ["update_values", "get_values", "get_spreadsheet_metadata", "batch_get_values_by_range", "create_spreadsheet", "clear_values"], "hubspot": ["search_contacts_post", "batch_read_contacts_post", "get_contacts", "get_contact_by_id", "update_contact_by_id", "batch_update_contacts", "create_contacts_batch", "create_contact"]}}
|
|
28
|
+
{"user_input": "Process emails from the last 24 hours. Fetch primary inbox emails excluding replied threads, classify with LLM as Reply Required, No Reply Needed, or Ambiguous. For Reply Required/Ambiguous, draft human, on-brand replies for user review. Follow greeting, acknowledge, address concern, invite further questions, and friendly sign-off. Provide end summary of drafts, skipped, and ambiguous emails. Execute immediately without asking for confirmation. Do not send any emails. Just provide me a report.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages", "get_message_details"]}}
|
|
29
|
+
{"user_input": "Analyze a contract from my google drive from the perspective of the Service Provider. Use the search to find it, do not ask me any questions, and assume details that I have not provided. Identify potentially unfavorable clauses such as vague terms, one-sided obligations, IP transfer issues, indemnity clauses, termination conditions, and payment problems. Provide a structured analysis with clause numbers, full text, and explanations of concerns.", "difficulty": 4, "required_tools": {"google_drive": ["get_file_details", "search_files"], "google_docs": ["get_document"], "exa": ["answer"]}}
|
|
30
|
+
{"user_input": "Create a summary of overnight updates from 8:00 PM yesterday to 8:00 AM today in IST. Check Gmail for important emails and ClickUp for mentions and assigned tasks. Organize findings into high priority and other items, then provide a comprehensive summary of all overnight activity.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages"], "clickup": ["comments_get_task_comments", "comments_get_list_comments", "comments_get_view_comments", "tasks_get_list_tasks", "tasks_filter_team_tasks", "time_tracking_get_time_entries_within_date_range", "time_tracking_get_time_entry_history", "authorization_get_workspace_list", "spaces_get_details", "lists_get_list_details"]}}
|
|
31
|
+
{"user_input": "Analyze the data in Google Sheet (ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8) and create 3-5 relevant charts and visualizations. Add pie charts, bar graphs, and other appropriate visualizations based on the data structure. Embed all charts directly into the sheet and provide the updated sheet link.", "difficulty": 4, "required_tools": {"google_sheet": ["create_spreadsheet", "get_spreadsheet_metadata", "batch_get_values_by_range", "append_dimensions", "insert_dimensions", "delete_sheet", "add_sheet", "delete_dimensions", "add_basic_chart", "add_table", "add_pie_chart", "clear_values", "update_values", "clear_basic_filter", "get_values", "discover_tables", "set_basic_filter", "analyze_table_schema", "copy_sheet_to_spreadsheet", "append_values", "batch_get_values_by_data_filter", "batch_clear_values", "format_cells"]}}
|
|
32
|
+
{"user_input": "Create a 7-day learning plan for Python Programming. Research essential concepts and skills, create a detailed day-by-day plan with topics, goals, resources, and exercises. Compile the plan into a Google Doc and schedule daily emails at 8 AM starting today. Send Day 1 immediately to adit@agentr.dev and provide the Google Doc link.", "difficulty": 5, "required_tools": {"google_docs": ["get_document", "create_document", "insert_text"], "google_mail": ["send_email", "send_draft", "create_draft"], "exa": ["answer"]}}
|
|
@@ -53,13 +53,13 @@ CODEACT_EVALUATOR_PROMPT = """
|
|
|
53
53
|
You are a code execution evaluator. You will be given the entire run of an agent, starting with a human input task, the intermediate steps taken, and the final output of the agent given to the user. These steps will contain code written by the agent to solve the problem as well as its outputs. Your job is to check ONLY if the code executes correctly.
|
|
54
54
|
Keep in mind that the agent has access to tools like- ai_classify, call_llm, creative_writer, data_extractor. These calls are to be treated as valid if they run without errors.
|
|
55
55
|
These are the only criteria you should evaluate-
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
<Rubric>
|
|
58
58
|
- The code written by the agent in tool calls should be syntactically correct and use existing objects.
|
|
59
59
|
- The code outputs should not have an error or empty/unexpected outputs
|
|
60
60
|
</Rubric>
|
|
61
61
|
If either of the above are not satisfied, you should give 0.
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
<Reminder>
|
|
64
64
|
You must not judge whether the code is helpful to the task or not, only if the code itself is correct or not.
|
|
65
65
|
</Reminder>
|
|
@@ -145,7 +145,7 @@ class MockToolRegistry(ToolRegistry):
|
|
|
145
145
|
self,
|
|
146
146
|
query: str,
|
|
147
147
|
limit: int = 10,
|
|
148
|
-
|
|
148
|
+
distance_threshold: float = 0.7,
|
|
149
149
|
) -> list[dict[str, Any]]:
|
|
150
150
|
"""
|
|
151
151
|
Search for apps by a query.
|
|
@@ -168,7 +168,7 @@ class MockToolRegistry(ToolRegistry):
|
|
|
168
168
|
query: str,
|
|
169
169
|
limit: int = 10,
|
|
170
170
|
app_id: str | None = None,
|
|
171
|
-
|
|
171
|
+
distance_threshold: float = 0.8,
|
|
172
172
|
) -> list[dict[str, Any]]:
|
|
173
173
|
"""
|
|
174
174
|
Search for tools by a query.
|
|
@@ -177,11 +177,23 @@ class MockToolRegistry(ToolRegistry):
|
|
|
177
177
|
brittle keyword search.
|
|
178
178
|
"""
|
|
179
179
|
if not app_id:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
# General search
|
|
181
|
+
all_tools = []
|
|
182
|
+
for current_app_id, tools in self._tools.items():
|
|
183
|
+
for tool in tools:
|
|
184
|
+
tool_with_app_id = tool.copy()
|
|
185
|
+
tool_with_app_id["id"] = f"{current_app_id}__{tool['name']}"
|
|
186
|
+
all_tools.append(tool_with_app_id)
|
|
187
|
+
return all_tools[:limit]
|
|
188
|
+
|
|
189
|
+
# App-specific search
|
|
183
190
|
all_app_tools = self._tools.get(app_id, [])
|
|
184
|
-
|
|
191
|
+
tools_with_app_id = []
|
|
192
|
+
for tool in all_app_tools:
|
|
193
|
+
tool_with_app_id = tool.copy()
|
|
194
|
+
tool_with_app_id["id"] = f"{app_id}__{tool['name']}"
|
|
195
|
+
tools_with_app_id.append(tool_with_app_id)
|
|
196
|
+
return tools_with_app_id[:limit]
|
|
185
197
|
|
|
186
198
|
async def export_tools(
|
|
187
199
|
self,
|
|
@@ -219,17 +231,6 @@ class TestToolFinderGraph:
|
|
|
219
231
|
def registry(self):
|
|
220
232
|
return MockToolRegistry()
|
|
221
233
|
|
|
222
|
-
def _get_tool_config_from_plan(self, plan: list[dict[str, Any]]) -> dict[str, list[str]]:
|
|
223
|
-
"""
|
|
224
|
-
Helper function to convert a consolidated execution plan to a tool_config dict.
|
|
225
|
-
MODIFIED: This now correctly handles the already-consolidated plan from the graph.
|
|
226
|
-
"""
|
|
227
|
-
if not plan:
|
|
228
|
-
return {}
|
|
229
|
-
|
|
230
|
-
config = {step["app_id"]: step["tool_ids"] for step in plan if step.get("app_id") and step.get("tool_ids")}
|
|
231
|
-
return config
|
|
232
|
-
|
|
233
234
|
@pytest.mark.asyncio
|
|
234
235
|
async def test_simple_case(self, llm, registry):
|
|
235
236
|
"""Test Case 1: Simple task requiring a single app and tool."""
|
|
@@ -239,9 +240,7 @@ class TestToolFinderGraph:
|
|
|
239
240
|
{"original_task": task, "messages": [HumanMessage(content=task)], "decomposition_attempts": 0}
|
|
240
241
|
)
|
|
241
242
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
tool_config = self._get_tool_config_from_plan(plan)
|
|
243
|
+
tool_config = final_state.get("execution_plan")
|
|
245
244
|
|
|
246
245
|
# FIX: Assert against the correct, hyphenated app ID.
|
|
247
246
|
assert "google_mail" in tool_config
|
|
@@ -256,10 +255,8 @@ class TestToolFinderGraph:
|
|
|
256
255
|
{"original_task": task, "messages": [HumanMessage(content=task)], "decomposition_attempts": 0}
|
|
257
256
|
)
|
|
258
257
|
|
|
259
|
-
|
|
260
|
-
assert
|
|
261
|
-
|
|
262
|
-
tool_config = self._get_tool_config_from_plan(plan)
|
|
258
|
+
tool_config = final_state.get("execution_plan")
|
|
259
|
+
assert tool_config, "Execution plan should not be empty"
|
|
263
260
|
|
|
264
261
|
assert "github" in tool_config
|
|
265
262
|
assert "create_issue" in tool_config["github"]
|
|
@@ -277,7 +274,7 @@ class TestToolFinderGraph:
|
|
|
277
274
|
plan = final_state.get("execution_plan")
|
|
278
275
|
assert not plan
|
|
279
276
|
last_message = final_state.get("messages", [])[-1].content
|
|
280
|
-
assert "
|
|
277
|
+
assert "could not create a final plan" in last_message.lower()
|
|
281
278
|
|
|
282
279
|
|
|
283
280
|
@pytest.mark.parametrize(
|
|
@@ -314,19 +311,20 @@ class TestAgents:
|
|
|
314
311
|
await agent.ainit()
|
|
315
312
|
# Invoke the agent graph to get the final state
|
|
316
313
|
final_state = await agent.invoke(
|
|
317
|
-
user_input=task,
|
|
314
|
+
user_input={"userInput": task} if agent.name == "Test builder" else task,
|
|
318
315
|
thread_id=thread_id,
|
|
319
316
|
)
|
|
320
317
|
|
|
321
318
|
# Extract the content of the last message
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
if agent.name != "Test builder":
|
|
320
|
+
final_messages = final_state.get("messages", [])
|
|
321
|
+
assert final_messages, "The agent should have produced at least one message."
|
|
322
|
+
last_message = final_messages[-1]
|
|
325
323
|
|
|
326
|
-
|
|
324
|
+
final_response = last_message.content if hasattr(last_message, "content") else str(last_message)
|
|
327
325
|
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
assert final_response is not None, "The final response should not be None."
|
|
327
|
+
assert final_response != "", "The final response should not be an empty string."
|
|
330
328
|
|
|
331
329
|
|
|
332
330
|
class TestAgentBuilder:
|
|
@@ -346,8 +344,9 @@ class TestAgentBuilder:
|
|
|
346
344
|
async def test_create_agent(self, agent_builder: BuilderAgent):
|
|
347
345
|
"""Test case for creating an agent with the builder."""
|
|
348
346
|
task = "Send a daily email to manoj@agentr.dev with daily agenda of the day"
|
|
347
|
+
thread_id = "test-thread-create-agent"
|
|
349
348
|
|
|
350
|
-
result = await agent_builder.invoke(task)
|
|
349
|
+
result = await agent_builder.invoke(thread_id=thread_id, user_input={"userInput": task})
|
|
351
350
|
|
|
352
351
|
assert "generated_agent" in result
|
|
353
352
|
generated_agent = result["generated_agent"]
|
{universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/__init__.py
RENAMED
|
@@ -4,7 +4,7 @@ from universal_mcp.agents.base import BaseAgent
|
|
|
4
4
|
from universal_mcp.agents.bigtool import BigToolAgent
|
|
5
5
|
from universal_mcp.agents.builder.builder import BuilderAgent
|
|
6
6
|
from universal_mcp.agents.codeact import CodeActAgent as CodeActScript
|
|
7
|
-
from universal_mcp.agents.codeact0 import
|
|
7
|
+
from universal_mcp.agents.codeact0 import CodeActPlaybookAgent as CodeActRepl
|
|
8
8
|
from universal_mcp.agents.react import ReactAgent
|
|
9
9
|
from universal_mcp.agents.simple import SimpleAgent
|
|
10
10
|
|
{universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/base.py
RENAMED
|
@@ -85,6 +85,7 @@ class BaseAgent:
|
|
|
85
85
|
event = cast(AIMessageChunk, event)
|
|
86
86
|
event.usage_metadata = aggregate.usage_metadata
|
|
87
87
|
logger.debug(f"Usage metadata: {event.usage_metadata}")
|
|
88
|
+
event.content = "" # Clear the message since it would have already been streamed above
|
|
88
89
|
yield event
|
|
89
90
|
|
|
90
91
|
async def stream_interactive(self, thread_id: str, user_input: str):
|
|
@@ -116,7 +117,7 @@ class BaseAgent:
|
|
|
116
117
|
"configurable": {"thread_id": thread_id},
|
|
117
118
|
"metadata": run_metadata,
|
|
118
119
|
"run_id": thread_id,
|
|
119
|
-
"run_name"
|
|
120
|
+
"run_name": self.name,
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
result = await self._graph.ainvoke(
|
|
@@ -2,15 +2,16 @@ import asyncio
|
|
|
2
2
|
|
|
3
3
|
from loguru import logger
|
|
4
4
|
from universal_mcp.agentr.registry import AgentrRegistry
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from universal_mcp.agents.bigtool import BigToolAgent
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
async def main():
|
|
9
|
-
agent =
|
|
10
|
+
agent = BigToolAgent(
|
|
10
11
|
registry=AgentrRegistry(),
|
|
11
12
|
)
|
|
12
13
|
async for event in agent.stream(
|
|
13
|
-
user_input="
|
|
14
|
+
user_input="Load a supabase tool",
|
|
14
15
|
thread_id="test123",
|
|
15
16
|
):
|
|
16
17
|
logger.info(event.content)
|
|
@@ -11,9 +11,10 @@ from langgraph.types import Command, RetryPolicy
|
|
|
11
11
|
from universal_mcp.tools.registry import ToolRegistry
|
|
12
12
|
from universal_mcp.types import ToolFormat
|
|
13
13
|
|
|
14
|
+
from universal_mcp.agents.utils import filter_retry_on
|
|
15
|
+
|
|
14
16
|
from .state import State
|
|
15
17
|
from .tools import get_valid_tools
|
|
16
|
-
from universal_mcp.agents.utils import filter_retry_on
|
|
17
18
|
|
|
18
19
|
load_dotenv()
|
|
19
20
|
|
|
@@ -96,13 +97,16 @@ def build_graph(
|
|
|
96
97
|
for tool_call in tool_calls:
|
|
97
98
|
try:
|
|
98
99
|
if tool_call["name"] == "load_tools": # Handle load_tools separately
|
|
99
|
-
valid_tools, unconnected_links = await get_valid_tools(
|
|
100
|
+
valid_tools, unconnected_links = await get_valid_tools(
|
|
101
|
+
tool_ids=tool_call["args"]["tool_ids"], registry=registry
|
|
102
|
+
)
|
|
100
103
|
new_tool_ids.extend(valid_tools)
|
|
101
104
|
# Create tool message response
|
|
102
105
|
tool_result = f"Successfully loaded {len(valid_tools)} tools: {valid_tools}"
|
|
103
106
|
if unconnected_links:
|
|
104
107
|
ask_user = True
|
|
105
|
-
|
|
108
|
+
links = "\n".join(unconnected_links)
|
|
109
|
+
ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
|
|
106
110
|
|
|
107
111
|
elif tool_call["name"] == "search_tools":
|
|
108
112
|
tool_result = await meta_tools["search_tools"].ainvoke(tool_call["args"])
|
|
@@ -115,7 +119,6 @@ def build_graph(
|
|
|
115
119
|
except Exception as e:
|
|
116
120
|
tool_result = f"Error during {tool_call}: {e}"
|
|
117
121
|
|
|
118
|
-
|
|
119
122
|
tool_message = ToolMessage(
|
|
120
123
|
content=json.dumps(tool_result),
|
|
121
124
|
name=tool_call["name"],
|
|
@@ -34,9 +34,8 @@ def create_meta_tools(tool_registry: ToolRegistry) -> dict[str, Any]:
|
|
|
34
34
|
for tools_list in query_results:
|
|
35
35
|
for tool in tools_list:
|
|
36
36
|
app = tool["id"].split("__")[0]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
app_tools[app].append(f"{tool['id']}: {cleaned_desc}")
|
|
37
|
+
cleaned_desc = tool["description"].split("Context:")[0].strip()
|
|
38
|
+
app_tools[app].append(f"{tool['id']}: {cleaned_desc}")
|
|
40
39
|
|
|
41
40
|
# Build result string efficiently
|
|
42
41
|
result_parts = []
|
|
@@ -133,7 +132,7 @@ async def get_valid_tools(tool_ids: list[str], registry: ToolRegistry) -> tuple[
|
|
|
133
132
|
app_tool_list[app] = tools
|
|
134
133
|
|
|
135
134
|
# Validate tool_ids
|
|
136
|
-
for app, tool_entries in app_to_tools.items():
|
|
135
|
+
for app, tool_entries in app_to_tools.items():
|
|
137
136
|
available = app_tool_list.get(app)
|
|
138
137
|
if available is None:
|
|
139
138
|
incorrect.extend(tool_id for tool_id, _ in tool_entries)
|
|
@@ -142,7 +141,7 @@ async def get_valid_tools(tool_ids: list[str], registry: ToolRegistry) -> tuple[
|
|
|
142
141
|
unconnected.add(app)
|
|
143
142
|
text = registry.client.get_authorization_url(app)
|
|
144
143
|
start = text.find(":") + 1
|
|
145
|
-
end = text.find(".", start)
|
|
144
|
+
end = text.find(". R", start)
|
|
146
145
|
url = text[start:end].strip()
|
|
147
146
|
markdown_link = f"[{app}]({url})"
|
|
148
147
|
unconnected_links.append(markdown_link)
|