aixtools 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl
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.
Potentially problematic release.
This version of aixtools might be problematic. Click here for more details.
- aixtools/_version.py +2 -2
- aixtools/compliance/__init__.py +9 -0
- aixtools/compliance/private_data.py +5 -3
- aixtools/mcp/client.py +102 -1
- aixtools/testing/aix_test_model.py +2 -0
- {aixtools-0.1.9.dist-info → aixtools-0.1.11.dist-info}/METADATA +2 -1
- {aixtools-0.1.9.dist-info → aixtools-0.1.11.dist-info}/RECORD +10 -46
- aixtools-0.1.11.dist-info/top_level.txt +1 -0
- aixtools-0.1.9.dist-info/top_level.txt +0 -5
- docker/mcp-base/Dockerfile +0 -33
- docker/mcp-base/zscaler.crt +0 -28
- notebooks/example_faulty_mcp_server.ipynb +0 -74
- notebooks/example_mcp_server_stdio.ipynb +0 -76
- notebooks/example_raw_mcp_client.ipynb +0 -84
- notebooks/example_tool_doctor.ipynb +0 -65
- scripts/config.sh +0 -28
- scripts/lint.sh +0 -32
- scripts/log_view.sh +0 -18
- scripts/run_example_mcp_server.sh +0 -14
- scripts/run_faulty_mcp_server.sh +0 -13
- scripts/run_server.sh +0 -29
- scripts/test.sh +0 -30
- tests/__init__.py +0 -0
- tests/unit/__init__.py +0 -0
- tests/unit/a2a/__init__.py +0 -0
- tests/unit/a2a/google_sdk/__init__.py +0 -0
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -188
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -156
- tests/unit/a2a/google_sdk/test_card.py +0 -114
- tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -413
- tests/unit/a2a/google_sdk/test_utils.py +0 -208
- tests/unit/agents/__init__.py +0 -0
- tests/unit/agents/test_prompt.py +0 -363
- tests/unit/compliance/test_private_data.py +0 -329
- tests/unit/google/__init__.py +0 -1
- tests/unit/google/test_client.py +0 -233
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_client.py +0 -242
- tests/unit/server/__init__.py +0 -0
- tests/unit/server/test_path.py +0 -225
- tests/unit/server/test_utils.py +0 -362
- tests/unit/utils/__init__.py +0 -0
- tests/unit/utils/test_files.py +0 -146
- tests/unit/vault/__init__.py +0 -0
- tests/unit/vault/test_vault.py +0 -246
- {aixtools-0.1.9.dist-info → aixtools-0.1.11.dist-info}/WHEEL +0 -0
- {aixtools-0.1.9.dist-info → aixtools-0.1.11.dist-info}/entry_points.txt +0 -0
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "dab47237",
|
|
6
|
-
"metadata": {},
|
|
7
|
-
"source": [
|
|
8
|
-
"# Tool doctor example"
|
|
9
|
-
]
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"cell_type": "code",
|
|
13
|
-
"execution_count": null,
|
|
14
|
-
"id": "9735e38c",
|
|
15
|
-
"metadata": {},
|
|
16
|
-
"outputs": [],
|
|
17
|
-
"source": [
|
|
18
|
-
"# Example 1: A reasonably well-defined tool\n",
|
|
19
|
-
"from aixtools.tools.doctor import tool_doctor\n",
|
|
20
|
-
"\n",
|
|
21
|
-
"\n",
|
|
22
|
-
"def add(a: int, b: int) -> int:\n",
|
|
23
|
-
" \"\"\" Add two numbers \"\"\"\n",
|
|
24
|
-
" return a + b\n",
|
|
25
|
-
"\n",
|
|
26
|
-
"# Example 2: A poorly defined tool\n",
|
|
27
|
-
"def z(a: int, b: int) -> int:\n",
|
|
28
|
-
" \"\"\" performs some arithmentic with two parameters \"\"\"\n",
|
|
29
|
-
" return a + b\n"
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"cell_type": "code",
|
|
34
|
-
"execution_count": null,
|
|
35
|
-
"id": "e50d48d6",
|
|
36
|
-
"metadata": {},
|
|
37
|
-
"outputs": [],
|
|
38
|
-
"source": [
|
|
39
|
-
"# Call tool doctor\n",
|
|
40
|
-
"ret = await tool_doctor([add, z])"
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
"metadata": {
|
|
45
|
-
"kernelspec": {
|
|
46
|
-
"display_name": ".venv",
|
|
47
|
-
"language": "python",
|
|
48
|
-
"name": "python3"
|
|
49
|
-
},
|
|
50
|
-
"language_info": {
|
|
51
|
-
"codemirror_mode": {
|
|
52
|
-
"name": "ipython",
|
|
53
|
-
"version": 3
|
|
54
|
-
},
|
|
55
|
-
"file_extension": ".py",
|
|
56
|
-
"mimetype": "text/x-python",
|
|
57
|
-
"name": "python",
|
|
58
|
-
"nbconvert_exporter": "python",
|
|
59
|
-
"pygments_lexer": "ipython3",
|
|
60
|
-
"version": "3.12.2"
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"nbformat": 4,
|
|
64
|
-
"nbformat_minor": 5
|
|
65
|
-
}
|
scripts/config.sh
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#-----------------------------------------------------------------------------
|
|
2
|
-
#
|
|
3
|
-
# This template script sets up the environment for the project by defining the
|
|
4
|
-
# project directory and activating the virtual environment.
|
|
5
|
-
#
|
|
6
|
-
#-----------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
export SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
9
|
-
|
|
10
|
-
# Project directory
|
|
11
|
-
export PROJECT_DIR="$( cd $SCRIPTS_DIR/.. && pwd -P )"
|
|
12
|
-
export PROJECT_NAME="$(basename $PROJECT_DIR)"
|
|
13
|
-
|
|
14
|
-
# Data directories
|
|
15
|
-
export DATA_DIR="$PROJECT_DIR/data"
|
|
16
|
-
export LOGS_DIR="$PROJECT_DIR/logs"
|
|
17
|
-
export PG_DATA_DIR="$DATA_DIR/data/db/postgres"
|
|
18
|
-
|
|
19
|
-
# Server configuration
|
|
20
|
-
export PORT=8081
|
|
21
|
-
|
|
22
|
-
# Activate virtual environment
|
|
23
|
-
if [ "${OS-}" == "Windows_NT" ]; then
|
|
24
|
-
source .venv/Scripts/activate
|
|
25
|
-
else
|
|
26
|
-
source .venv/bin/activate
|
|
27
|
-
fi
|
|
28
|
-
|
scripts/lint.sh
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
#-----------------------------------------------------------------------------
|
|
5
|
-
#
|
|
6
|
-
# Template script for running the linter
|
|
7
|
-
#
|
|
8
|
-
#-----------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Get script directory
|
|
12
|
-
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
13
|
-
source "$SCRIPTS_DIR/config.sh"
|
|
14
|
-
|
|
15
|
-
# Check if the fix parameter is provided
|
|
16
|
-
FIX_MODE=false
|
|
17
|
-
if [[ $# -gt 0 && "$1" == "--fix" ]]; then
|
|
18
|
-
FIX_MODE=true
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
# Run linter
|
|
22
|
-
if [ "$FIX_MODE" = true ]; then
|
|
23
|
-
echo "Running linters in fix mode..."
|
|
24
|
-
ruff format .
|
|
25
|
-
ruff check --fix .
|
|
26
|
-
else
|
|
27
|
-
echo "Running linters in check mode..."
|
|
28
|
-
ruff format --check .
|
|
29
|
-
ruff check .
|
|
30
|
-
fi
|
|
31
|
-
echo "Running pylint..."
|
|
32
|
-
pylint aixtools/
|
scripts/log_view.sh
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
#-----------------------------------------------------------------------------
|
|
5
|
-
#
|
|
6
|
-
# Template script for running the Chainlit server
|
|
7
|
-
#
|
|
8
|
-
#-----------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
# Get script directory
|
|
11
|
-
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
12
|
-
source "$SCRIPTS_DIR/config.sh"
|
|
13
|
-
|
|
14
|
-
# Activate virtual environment (with special handling for Windows)
|
|
15
|
-
cd "$PROJECT_DIR"
|
|
16
|
-
|
|
17
|
-
# Run the server
|
|
18
|
-
streamlit run aixtools/log_view/app.py $*
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
# Get script directory
|
|
5
|
-
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
6
|
-
source "$SCRIPTS_DIR/config.sh"
|
|
7
|
-
|
|
8
|
-
# Activate virtual environment (with special handling for Windows)
|
|
9
|
-
cd "$PROJECT_DIR"
|
|
10
|
-
|
|
11
|
-
# Run the example MCP server
|
|
12
|
-
fastmcp run \
|
|
13
|
-
--transport http \
|
|
14
|
-
"$PROJECT_DIR/aixtools/mcp/example_server.py"
|
scripts/run_faulty_mcp_server.sh
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
# Get script directory
|
|
5
|
-
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
6
|
-
source "$SCRIPTS_DIR/config.sh"
|
|
7
|
-
|
|
8
|
-
cd "$PROJECT_DIR"
|
|
9
|
-
|
|
10
|
-
while true; do
|
|
11
|
-
uv run aixtools/mcp/faulty_mcp.py "$@" || true
|
|
12
|
-
echo "MCP server terminated. Restarting..."
|
|
13
|
-
done
|
scripts/run_server.sh
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
#-----------------------------------------------------------------------------
|
|
5
|
-
#
|
|
6
|
-
# Template script for running the Chainlit server
|
|
7
|
-
#
|
|
8
|
-
#-----------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
# Get script directory
|
|
11
|
-
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
12
|
-
source "$SCRIPTS_DIR/config.sh"
|
|
13
|
-
|
|
14
|
-
# Activate virtual environment (with special handling for Windows)
|
|
15
|
-
cd "$PROJECT_DIR"
|
|
16
|
-
|
|
17
|
-
# Run linter before starting the server
|
|
18
|
-
"$SCRIPTS_DIR/lint.sh"
|
|
19
|
-
|
|
20
|
-
# Go into the sub-dir (most projects don't need this)
|
|
21
|
-
cd "$PROJECT_NAME"
|
|
22
|
-
|
|
23
|
-
# Run the server
|
|
24
|
-
chainlit run \
|
|
25
|
-
--host 0.0.0.0 \
|
|
26
|
-
--port $PORT \
|
|
27
|
-
--root-path /$PROJECT_NAME \
|
|
28
|
-
--watch \
|
|
29
|
-
app.py
|
scripts/test.sh
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/bin/bash -eu
|
|
2
|
-
set -o pipefail
|
|
3
|
-
|
|
4
|
-
# Get the directory of this script
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
source "${SCRIPT_DIR}/config.sh"
|
|
7
|
-
|
|
8
|
-
# Install required dependencies
|
|
9
|
-
echo "Installing test dependencies..."
|
|
10
|
-
uv pip install pytest pytest-asyncio pytest-mock pytest-cov
|
|
11
|
-
|
|
12
|
-
# Ensure .env exists
|
|
13
|
-
touch .env
|
|
14
|
-
|
|
15
|
-
# Add the project root to PYTHONPATH so imports work correctly
|
|
16
|
-
export PYTHONPATH="${PROJECT_DIR}:${PYTHONPATH:-}"
|
|
17
|
-
|
|
18
|
-
# Check if a specific test is provided as parameter
|
|
19
|
-
if [ $# -eq 1 ]; then
|
|
20
|
-
# Run specific test without coverage
|
|
21
|
-
echo "Running specific test: $1"
|
|
22
|
-
pytest "$1" -v
|
|
23
|
-
else
|
|
24
|
-
# Run all tests using pytest with coverage
|
|
25
|
-
echo "Running tests with coverage..."
|
|
26
|
-
pytest tests/ -v --cov=aixtools --cov-report=term-missing
|
|
27
|
-
|
|
28
|
-
# Generate HTML coverage report
|
|
29
|
-
# pytest tests/ --cov=aixtools --cov-report=html
|
|
30
|
-
fi
|
tests/__init__.py
DELETED
|
File without changes
|
tests/unit/__init__.py
DELETED
|
File without changes
|
tests/unit/a2a/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"""Tests for the Pydantic AI adapter agent executor module."""
|
|
2
|
-
|
|
3
|
-
import unittest
|
|
4
|
-
from unittest.mock import AsyncMock, MagicMock, patch
|
|
5
|
-
|
|
6
|
-
from a2a.server.agent_execution import RequestContext
|
|
7
|
-
from a2a.server.events import EventQueue
|
|
8
|
-
from a2a.types import Message, TaskState, TaskStatusUpdateEvent
|
|
9
|
-
from pydantic_ai import Agent
|
|
10
|
-
|
|
11
|
-
from aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor import (
|
|
12
|
-
AgentParameters,
|
|
13
|
-
RunOutput,
|
|
14
|
-
PydanticAgentExecutor,
|
|
15
|
-
_task_failed_event,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestAgentParameters(unittest.TestCase):
|
|
20
|
-
"""Tests for the AgentParameters model."""
|
|
21
|
-
|
|
22
|
-
def test_agent_parameters_creation(self):
|
|
23
|
-
"""Test creating AgentParameters with valid data."""
|
|
24
|
-
params = AgentParameters(
|
|
25
|
-
system_prompt="Test prompt",
|
|
26
|
-
mcp_servers=["server1", "server2"]
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
self.assertEqual(params.system_prompt, "Test prompt")
|
|
30
|
-
self.assertEqual(params.mcp_servers, ["server1", "server2"])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class TestRunOutput(unittest.TestCase):
|
|
34
|
-
"""Tests for the RunOutput model."""
|
|
35
|
-
|
|
36
|
-
def test_run_output_creation(self):
|
|
37
|
-
"""Test creating RunOutput with valid data."""
|
|
38
|
-
output = RunOutput(
|
|
39
|
-
is_task_failed=False,
|
|
40
|
-
is_task_in_progress=True,
|
|
41
|
-
is_input_required=False,
|
|
42
|
-
output="Test output",
|
|
43
|
-
created_artifacts_paths=["path1", "path2"]
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
self.assertFalse(output.is_task_failed)
|
|
47
|
-
self.assertTrue(output.is_task_in_progress)
|
|
48
|
-
self.assertFalse(output.is_input_required)
|
|
49
|
-
self.assertEqual(output.output, "Test output")
|
|
50
|
-
self.assertEqual(output.created_artifacts_paths, ["path1", "path2"])
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class TestTaskFailedEvent(unittest.TestCase):
|
|
54
|
-
"""Tests for the _task_failed_event function."""
|
|
55
|
-
|
|
56
|
-
def test_task_failed_event_creation(self):
|
|
57
|
-
"""Test creating a task failed event."""
|
|
58
|
-
text = "Test error message"
|
|
59
|
-
context_id = "ctx123"
|
|
60
|
-
task_id = "task456"
|
|
61
|
-
|
|
62
|
-
event = _task_failed_event(text, context_id, task_id)
|
|
63
|
-
|
|
64
|
-
self.assertIsInstance(event, TaskStatusUpdateEvent)
|
|
65
|
-
self.assertEqual(event.status.state, TaskState.failed)
|
|
66
|
-
self.assertEqual(event.context_id, context_id)
|
|
67
|
-
self.assertEqual(event.task_id, task_id)
|
|
68
|
-
self.assertTrue(event.final)
|
|
69
|
-
|
|
70
|
-
def test_task_failed_event_with_none_ids(self):
|
|
71
|
-
"""Test creating a task failed event with None IDs."""
|
|
72
|
-
text = "Test error message"
|
|
73
|
-
|
|
74
|
-
event = _task_failed_event(text, "default_ctx", "default_task")
|
|
75
|
-
|
|
76
|
-
self.assertIsInstance(event, TaskStatusUpdateEvent)
|
|
77
|
-
self.assertEqual(event.status.state, TaskState.failed)
|
|
78
|
-
self.assertEqual(event.context_id, "default_ctx")
|
|
79
|
-
self.assertEqual(event.task_id, "default_task")
|
|
80
|
-
self.assertTrue(event.final)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class TestPydanticAgentExecutor(unittest.IsolatedAsyncioTestCase):
|
|
84
|
-
"""Tests for the PydanticAgentExecutor class."""
|
|
85
|
-
|
|
86
|
-
def setUp(self):
|
|
87
|
-
self.agent_params = AgentParameters(
|
|
88
|
-
system_prompt="Test system prompt",
|
|
89
|
-
mcp_servers=["test_server"]
|
|
90
|
-
)
|
|
91
|
-
self.executor = PydanticAgentExecutor(self.agent_params)
|
|
92
|
-
|
|
93
|
-
def test_init(self):
|
|
94
|
-
"""Test PydanticAgentExecutor initialization."""
|
|
95
|
-
self.assertEqual(self.executor._agent_parameters, self.agent_params)
|
|
96
|
-
self.assertIsNotNone(self.executor.history_storage)
|
|
97
|
-
|
|
98
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_message_text")
|
|
99
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_file_parts")
|
|
100
|
-
def test_convert_message_to_pydantic_parts_text_only(self, mock_get_file_parts, mock_get_message_text):
|
|
101
|
-
"""Test converting message with text only."""
|
|
102
|
-
mock_get_message_text.return_value = "Test message"
|
|
103
|
-
mock_get_file_parts.return_value = []
|
|
104
|
-
|
|
105
|
-
mock_message = MagicMock(spec=Message)
|
|
106
|
-
mock_message.parts = []
|
|
107
|
-
session_tuple = ("user1", "session1")
|
|
108
|
-
|
|
109
|
-
result = self.executor._convert_message_to_pydantic_parts(session_tuple, mock_message)
|
|
110
|
-
|
|
111
|
-
self.assertEqual(result, "Test message")
|
|
112
|
-
mock_get_message_text.assert_called_once_with(mock_message)
|
|
113
|
-
mock_get_file_parts.assert_called_once_with([])
|
|
114
|
-
|
|
115
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.build_user_input")
|
|
116
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_message_text")
|
|
117
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_file_parts")
|
|
118
|
-
def test_convert_message_to_pydantic_parts_with_files(self, mock_get_file_parts, mock_get_message_text, mock_build_user_input):
|
|
119
|
-
"""Test converting message with files."""
|
|
120
|
-
mock_get_message_text.return_value = "Test message"
|
|
121
|
-
mock_file_part = MagicMock()
|
|
122
|
-
mock_file_part.uri = "/test/path.txt"
|
|
123
|
-
mock_get_file_parts.return_value = [mock_file_part]
|
|
124
|
-
mock_build_user_input.return_value = ["processed", "input"]
|
|
125
|
-
|
|
126
|
-
mock_message = MagicMock(spec=Message)
|
|
127
|
-
mock_message.parts = [mock_file_part]
|
|
128
|
-
session_tuple = ("user1", "session1")
|
|
129
|
-
|
|
130
|
-
result = self.executor._convert_message_to_pydantic_parts(session_tuple, mock_message)
|
|
131
|
-
|
|
132
|
-
self.assertEqual(result, ["processed", "input"])
|
|
133
|
-
# Verify that build_user_input was called (exact args may vary due to filtering)
|
|
134
|
-
mock_build_user_input.assert_called_once()
|
|
135
|
-
|
|
136
|
-
def test_execute_no_message_validation(self):
|
|
137
|
-
"""Test that execute validates message presence."""
|
|
138
|
-
# This is a simple validation test without complex integration
|
|
139
|
-
mock_context = MagicMock(spec=RequestContext)
|
|
140
|
-
mock_context.message = None
|
|
141
|
-
|
|
142
|
-
# The actual validation happens in the execute method
|
|
143
|
-
# We test the validation logic separately
|
|
144
|
-
self.assertIsNone(mock_context.message)
|
|
145
|
-
|
|
146
|
-
async def test_cancel_not_supported(self):
|
|
147
|
-
"""Test that cancel raises an exception."""
|
|
148
|
-
mock_context = MagicMock(spec=RequestContext)
|
|
149
|
-
mock_event_queue = AsyncMock(spec=EventQueue)
|
|
150
|
-
|
|
151
|
-
with self.assertRaises(Exception) as context:
|
|
152
|
-
await self.executor.cancel(mock_context, mock_event_queue)
|
|
153
|
-
|
|
154
|
-
self.assertIn("cancel not supported", str(context.exception))
|
|
155
|
-
|
|
156
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_agent")
|
|
157
|
-
@patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_configured_mcp_servers")
|
|
158
|
-
def test_build_agent(self, mock_get_mcp_servers, mock_get_agent):
|
|
159
|
-
"""Test building an agent."""
|
|
160
|
-
mock_agent = MagicMock(spec=Agent)
|
|
161
|
-
mock_get_agent.return_value = mock_agent
|
|
162
|
-
mock_get_mcp_servers.return_value = ["mcp1", "mcp2"]
|
|
163
|
-
|
|
164
|
-
session_tuple = ("user1", "session1")
|
|
165
|
-
result = self.executor._build_agent(session_tuple)
|
|
166
|
-
|
|
167
|
-
self.assertEqual(result, mock_agent)
|
|
168
|
-
mock_get_mcp_servers.assert_called_once_with(session_tuple, ["test_server"])
|
|
169
|
-
mock_get_agent.assert_called_once_with(
|
|
170
|
-
system_prompt="Test system prompt",
|
|
171
|
-
toolsets=["mcp1", "mcp2"],
|
|
172
|
-
output_type=RunOutput
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
def test_task_creation_logic(self):
|
|
176
|
-
"""Test task creation logic validation."""
|
|
177
|
-
# Test the basic logic without complex integration
|
|
178
|
-
mock_context = MagicMock(spec=RequestContext)
|
|
179
|
-
mock_context.current_task = None
|
|
180
|
-
mock_context.message = MagicMock(spec=Message)
|
|
181
|
-
|
|
182
|
-
# Verify that when current_task is None, we have a message to work with
|
|
183
|
-
self.assertIsNone(mock_context.current_task)
|
|
184
|
-
self.assertIsNotNone(mock_context.message)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if __name__ == '__main__':
|
|
188
|
-
unittest.main()
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"""Tests for the Pydantic AI adapter storage module."""
|
|
2
|
-
|
|
3
|
-
import unittest
|
|
4
|
-
from unittest.mock import MagicMock
|
|
5
|
-
|
|
6
|
-
from aixtools.a2a.google_sdk.pydantic_ai_adapter.storage import (
|
|
7
|
-
PydanticAiAgentHistoryStorage,
|
|
8
|
-
InMemoryHistoryStorage,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestInMemoryHistoryStorage(unittest.TestCase):
|
|
13
|
-
"""Tests for the InMemoryHistoryStorage class."""
|
|
14
|
-
|
|
15
|
-
def setUp(self):
|
|
16
|
-
self.storage = InMemoryHistoryStorage()
|
|
17
|
-
|
|
18
|
-
def test_init(self):
|
|
19
|
-
"""Test InMemoryHistoryStorage initialization."""
|
|
20
|
-
self.assertEqual(self.storage.storage, {})
|
|
21
|
-
|
|
22
|
-
def test_get_nonexistent_task(self):
|
|
23
|
-
"""Test getting history for a task that doesn't exist."""
|
|
24
|
-
result = self.storage.get("nonexistent_task")
|
|
25
|
-
self.assertIsNone(result)
|
|
26
|
-
|
|
27
|
-
def test_store_and_get_messages(self):
|
|
28
|
-
"""Test storing and retrieving messages."""
|
|
29
|
-
task_id = "test_task_1"
|
|
30
|
-
# Use simple mock objects that can be stored
|
|
31
|
-
messages = [MagicMock(), MagicMock()]
|
|
32
|
-
|
|
33
|
-
# Store the messages
|
|
34
|
-
self.storage.store(task_id, messages)
|
|
35
|
-
|
|
36
|
-
# Retrieve the messages
|
|
37
|
-
result = self.storage.get(task_id)
|
|
38
|
-
|
|
39
|
-
self.assertIsNotNone(result)
|
|
40
|
-
self.assertEqual(result, messages)
|
|
41
|
-
self.assertEqual(len(result), 2)
|
|
42
|
-
|
|
43
|
-
def test_store_overwrites_existing(self):
|
|
44
|
-
"""Test that storing overwrites existing messages for the same task."""
|
|
45
|
-
task_id = "test_task_3"
|
|
46
|
-
|
|
47
|
-
# Store initial messages
|
|
48
|
-
initial_messages = [MagicMock()]
|
|
49
|
-
self.storage.store(task_id, initial_messages)
|
|
50
|
-
|
|
51
|
-
# Store new messages (should overwrite)
|
|
52
|
-
new_messages = [MagicMock(), MagicMock()]
|
|
53
|
-
self.storage.store(task_id, new_messages)
|
|
54
|
-
|
|
55
|
-
# Retrieve and verify new messages are stored
|
|
56
|
-
result = self.storage.get(task_id)
|
|
57
|
-
|
|
58
|
-
self.assertIsNotNone(result)
|
|
59
|
-
self.assertEqual(result, new_messages)
|
|
60
|
-
self.assertEqual(len(result), 2)
|
|
61
|
-
self.assertNotEqual(result, initial_messages)
|
|
62
|
-
|
|
63
|
-
def test_multiple_tasks(self):
|
|
64
|
-
"""Test storing and retrieving messages for multiple tasks."""
|
|
65
|
-
task1_id = "task_1"
|
|
66
|
-
task2_id = "task_2"
|
|
67
|
-
|
|
68
|
-
task1_messages = [MagicMock()]
|
|
69
|
-
task2_messages = [MagicMock(), MagicMock()]
|
|
70
|
-
|
|
71
|
-
# Store messages for both tasks
|
|
72
|
-
self.storage.store(task1_id, task1_messages)
|
|
73
|
-
self.storage.store(task2_id, task2_messages)
|
|
74
|
-
|
|
75
|
-
# Retrieve and verify both tasks' messages
|
|
76
|
-
result1 = self.storage.get(task1_id)
|
|
77
|
-
result2 = self.storage.get(task2_id)
|
|
78
|
-
|
|
79
|
-
self.assertIsNotNone(result1)
|
|
80
|
-
self.assertIsNotNone(result2)
|
|
81
|
-
self.assertEqual(result1, task1_messages)
|
|
82
|
-
self.assertEqual(result2, task2_messages)
|
|
83
|
-
self.assertNotEqual(result1, result2)
|
|
84
|
-
|
|
85
|
-
def test_store_empty_list(self):
|
|
86
|
-
"""Test storing an empty list of messages."""
|
|
87
|
-
task_id = "empty_task"
|
|
88
|
-
empty_messages = []
|
|
89
|
-
|
|
90
|
-
self.storage.store(task_id, empty_messages)
|
|
91
|
-
result = self.storage.get(task_id)
|
|
92
|
-
|
|
93
|
-
self.assertIsNotNone(result)
|
|
94
|
-
self.assertEqual(result, empty_messages)
|
|
95
|
-
self.assertEqual(len(result), 0)
|
|
96
|
-
|
|
97
|
-
def test_get_after_multiple_stores(self):
|
|
98
|
-
"""Test that get returns the most recent store for a task."""
|
|
99
|
-
task_id = "update_task"
|
|
100
|
-
|
|
101
|
-
# Store multiple times
|
|
102
|
-
messages1 = [MagicMock()]
|
|
103
|
-
messages2 = [MagicMock()]
|
|
104
|
-
messages3 = [MagicMock(), MagicMock()]
|
|
105
|
-
|
|
106
|
-
self.storage.store(task_id, messages1)
|
|
107
|
-
self.storage.store(task_id, messages2)
|
|
108
|
-
self.storage.store(task_id, messages3)
|
|
109
|
-
|
|
110
|
-
result = self.storage.get(task_id)
|
|
111
|
-
|
|
112
|
-
self.assertIsNotNone(result)
|
|
113
|
-
self.assertEqual(result, messages3)
|
|
114
|
-
|
|
115
|
-
def test_storage_isolation(self):
|
|
116
|
-
"""Test that different storage instances are isolated."""
|
|
117
|
-
storage1 = InMemoryHistoryStorage()
|
|
118
|
-
storage2 = InMemoryHistoryStorage()
|
|
119
|
-
|
|
120
|
-
task_id = "isolation_test"
|
|
121
|
-
messages1 = [MagicMock()]
|
|
122
|
-
messages2 = [MagicMock()]
|
|
123
|
-
|
|
124
|
-
storage1.store(task_id, messages1)
|
|
125
|
-
storage2.store(task_id, messages2)
|
|
126
|
-
|
|
127
|
-
result1 = storage1.get(task_id)
|
|
128
|
-
result2 = storage2.get(task_id)
|
|
129
|
-
|
|
130
|
-
self.assertIsNotNone(result1)
|
|
131
|
-
self.assertIsNotNone(result2)
|
|
132
|
-
self.assertEqual(result1, messages1)
|
|
133
|
-
self.assertEqual(result2, messages2)
|
|
134
|
-
self.assertNotEqual(result1, result2)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
class TestPydanticAiAgentHistoryStorageInterface(unittest.TestCase):
|
|
138
|
-
"""Tests for the PydanticAiAgentHistoryStorage abstract interface."""
|
|
139
|
-
|
|
140
|
-
def test_cannot_instantiate_abstract_class(self):
|
|
141
|
-
"""Test that the abstract base class cannot be instantiated."""
|
|
142
|
-
with self.assertRaises(TypeError):
|
|
143
|
-
PydanticAiAgentHistoryStorage()
|
|
144
|
-
|
|
145
|
-
def test_inmemory_implements_interface(self):
|
|
146
|
-
"""Test that InMemoryHistoryStorage properly implements the interface."""
|
|
147
|
-
storage = InMemoryHistoryStorage()
|
|
148
|
-
|
|
149
|
-
# Verify it's an instance of the abstract base class
|
|
150
|
-
self.assertIsInstance(storage, PydanticAiAgentHistoryStorage)
|
|
151
|
-
|
|
152
|
-
# Verify it has the required methods
|
|
153
|
-
self.assertTrue(hasattr(storage, 'get'))
|
|
154
|
-
self.assertTrue(hasattr(storage, 'store'))
|
|
155
|
-
self.assertTrue(callable(getattr(storage, 'get')))
|
|
156
|
-
self.assertTrue(callable(getattr(storage, 'store')))
|