mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-05-03 12:22:37 +02:00
Release/v1.2 (#457)
* Bump setup.py versions for 1.1 * PoC MCP server (#419) * Very initial MCP server PoC for TrustGraph * Put service on port 8000 * Add MCP container and packages to buildout * Update docs for API/CLI changes in 1.0 (#421) * Update some API basics for the 0.23/1.0 API change * Add MCP container push (#425) * Add command args to the MCP server (#426) * Host and port parameters * Added websocket arg * More docs * MCP client support (#427) - MCP client service - Tool request/response schema - API gateway support for mcp-tool - Message translation for tool request & response - Make mcp-tool using configuration service for information about where the MCP services are. * Feature/react call mcp (#428) Key Features - MCP Tool Integration: Added core MCP tool support with ToolClientSpec and ToolClient classes - API Enhancement: New mcp_tool method for flow-specific tool invocation - CLI Tooling: New tg-invoke-mcp-tool command for testing MCP integration - React Agent Enhancement: Fixed and improved multi-tool invocation capabilities - Tool Management: Enhanced CLI for tool configuration and management Changes - Added MCP tool invocation to API with flow-specific integration - Implemented ToolClientSpec and ToolClient for tool call handling - Updated agent-manager-react to invoke MCP tools with configurable types - Enhanced CLI with new commands and improved help text - Added comprehensive documentation for new CLI commands - Improved tool configuration management Testing - Added tg-invoke-mcp-tool CLI command for isolated MCP integration testing - Enhanced agent capability to invoke multiple tools simultaneously * Test suite executed from CI pipeline (#433) * Test strategy & test cases * Unit tests * Integration tests * Extending test coverage (#434) * Contract tests * Testing embeedings * Agent unit tests * Knowledge pipeline tests * Turn on contract tests * Increase storage test coverage (#435) * Fixing storage and adding tests * PR pipeline only runs quick tests * Empty configuration is returned as empty list, previously was not in response (#436) * Update config util to take files as well as command-line text (#437) * Updated CLI invocation and config model for tools and mcp (#438) * Updated CLI invocation and config model for tools and mcp * CLI anomalies * Tweaked the MCP tool implementation for new model * Update agent implementation to match the new model * Fix agent tools, now all tested * Fixed integration tests * Fix MCP delete tool params * Update Python deps to 1.2 * Update to enable knowledge extraction using the agent framework (#439) * Implement KG extraction agent (kg-extract-agent) * Using ReAct framework (agent-manager-react) * ReAct manager had an issue when emitting JSON, which conflicts which ReAct manager's own JSON messages, so refactored ReAct manager to use traditional ReAct messages, non-JSON structure. * Minor refactor to take the prompt template client out of prompt-template so it can be more readily used by other modules. kg-extract-agent uses this framework. * Migrate from setup.py to pyproject.toml (#440) * Converted setup.py to pyproject.toml * Modern package infrastructure as recommended by py docs * Install missing build deps (#441) * Install missing build deps (#442) * Implement logging strategy (#444) * Logging strategy and convert all prints() to logging invocations * Fix/startup failure (#445) * Fix loggin startup problems * Fix logging startup problems (#446) * Fix logging startup problems (#447) * Fixed Mistral OCR to use current API (#448) * Fixed Mistral OCR to use current API * Added PDF decoder tests * Fix Mistral OCR ident to be standard pdf-decoder (#450) * Fix Mistral OCR ident to be standard pdf-decoder * Correct test * Schema structure refactor (#451) * Write schema refactor spec * Implemented schema refactor spec * Structure data mvp (#452) * Structured data tech spec * Architecture principles * New schemas * Updated schemas and specs * Object extractor * Add .coveragerc * New tests * Cassandra object storage * Trying to object extraction working, issues exist * Validate librarian collection (#453) * Fix token chunker, broken API invocation (#454) * Fix token chunker, broken API invocation (#455) * Knowledge load utility CLI (#456) * Knowledge loader * More tests
This commit is contained in:
parent
c85ba197be
commit
89be656990
509 changed files with 49632 additions and 5159 deletions
716
tests/integration/test_agent_manager_integration.py
Normal file
716
tests/integration/test_agent_manager_integration.py
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
"""
|
||||
Integration tests for Agent Manager (ReAct Pattern) Service
|
||||
|
||||
These tests verify the end-to-end functionality of the Agent Manager service,
|
||||
testing the ReAct pattern (Think-Act-Observe), tool coordination, multi-step reasoning,
|
||||
and conversation state management.
|
||||
Following the TEST_STRATEGY.md approach for integration testing.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from trustgraph.agent.react.agent_manager import AgentManager
|
||||
from trustgraph.agent.react.tools import KnowledgeQueryImpl, TextCompletionImpl, McpToolImpl
|
||||
from trustgraph.agent.react.types import Action, Final, Tool, Argument
|
||||
from trustgraph.schema import AgentRequest, AgentResponse, AgentStep, Error
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestAgentManagerIntegration:
|
||||
"""Integration tests for Agent Manager ReAct pattern coordination"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_flow_context(self):
|
||||
"""Mock flow context for service coordination"""
|
||||
context = MagicMock()
|
||||
|
||||
# Mock prompt client
|
||||
prompt_client = AsyncMock()
|
||||
prompt_client.agent_react.return_value = """Thought: I need to search for information about machine learning
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "What is machine learning?"
|
||||
}"""
|
||||
|
||||
# Mock graph RAG client
|
||||
graph_rag_client = AsyncMock()
|
||||
graph_rag_client.rag.return_value = "Machine learning is a subset of AI that enables computers to learn from data."
|
||||
|
||||
# Mock text completion client
|
||||
text_completion_client = AsyncMock()
|
||||
text_completion_client.question.return_value = "Machine learning involves algorithms that improve through experience."
|
||||
|
||||
# Mock MCP tool client
|
||||
mcp_tool_client = AsyncMock()
|
||||
mcp_tool_client.invoke.return_value = "Tool execution successful"
|
||||
|
||||
# Configure context to return appropriate clients
|
||||
def context_router(service_name):
|
||||
if service_name == "prompt-request":
|
||||
return prompt_client
|
||||
elif service_name == "graph-rag-request":
|
||||
return graph_rag_client
|
||||
elif service_name == "prompt-request":
|
||||
return text_completion_client
|
||||
elif service_name == "mcp-tool-request":
|
||||
return mcp_tool_client
|
||||
else:
|
||||
return AsyncMock()
|
||||
|
||||
context.side_effect = context_router
|
||||
return context
|
||||
|
||||
@pytest.fixture
|
||||
def sample_tools(self):
|
||||
"""Sample tool configuration for testing"""
|
||||
return {
|
||||
"knowledge_query": Tool(
|
||||
name="knowledge_query",
|
||||
description="Query the knowledge graph for information",
|
||||
arguments=[
|
||||
Argument(
|
||||
name="question",
|
||||
type="string",
|
||||
description="The question to ask the knowledge graph"
|
||||
)
|
||||
],
|
||||
implementation=KnowledgeQueryImpl,
|
||||
config={}
|
||||
),
|
||||
"text_completion": Tool(
|
||||
name="text_completion",
|
||||
description="Generate text completion using LLM",
|
||||
arguments=[
|
||||
Argument(
|
||||
name="question",
|
||||
type="string",
|
||||
description="The question to ask the LLM"
|
||||
)
|
||||
],
|
||||
implementation=TextCompletionImpl,
|
||||
config={}
|
||||
),
|
||||
"web_search": Tool(
|
||||
name="web_search",
|
||||
description="Search the web for information",
|
||||
arguments=[
|
||||
Argument(
|
||||
name="query",
|
||||
type="string",
|
||||
description="The search query"
|
||||
)
|
||||
],
|
||||
implementation=lambda context: AsyncMock(invoke=AsyncMock(return_value="Web search results")),
|
||||
config={}
|
||||
)
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def agent_manager(self, sample_tools):
|
||||
"""Create agent manager with sample tools"""
|
||||
return AgentManager(
|
||||
tools=sample_tools,
|
||||
additional_context="You are a helpful AI assistant with access to knowledge and tools."
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_reasoning_cycle(self, agent_manager, mock_flow_context):
|
||||
"""Test basic reasoning cycle with tool selection"""
|
||||
# Arrange
|
||||
question = "What is machine learning?"
|
||||
history = []
|
||||
|
||||
# Act
|
||||
action = await agent_manager.reason(question, history, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.thought == "I need to search for information about machine learning"
|
||||
assert action.name == "knowledge_query"
|
||||
assert action.arguments == {"question": "What is machine learning?"}
|
||||
assert action.observation == ""
|
||||
|
||||
# Verify prompt client was called correctly
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
prompt_client.agent_react.assert_called_once()
|
||||
|
||||
# Verify the prompt variables passed to agent_react
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
assert variables["question"] == question
|
||||
assert len(variables["tools"]) == 3 # knowledge_query, text_completion, web_search
|
||||
assert variables["context"] == "You are a helpful AI assistant with access to knowledge and tools."
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_final_answer(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager returning final answer"""
|
||||
# Arrange
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I have enough information to answer the question
|
||||
Final Answer: Machine learning is a field of AI that enables computers to learn from data."""
|
||||
|
||||
question = "What is machine learning?"
|
||||
history = []
|
||||
|
||||
# Act
|
||||
action = await agent_manager.reason(question, history, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Final)
|
||||
assert action.thought == "I have enough information to answer the question"
|
||||
assert action.final == "Machine learning is a field of AI that enables computers to learn from data."
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_react_with_tool_execution(self, agent_manager, mock_flow_context):
|
||||
"""Test full ReAct cycle with tool execution"""
|
||||
# Arrange
|
||||
question = "What is machine learning?"
|
||||
history = []
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act
|
||||
action = await agent_manager.react(question, history, think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.thought == "I need to search for information about machine learning"
|
||||
assert action.name == "knowledge_query"
|
||||
assert action.arguments == {"question": "What is machine learning?"}
|
||||
assert action.observation == "Machine learning is a subset of AI that enables computers to learn from data."
|
||||
|
||||
# Verify callbacks were called
|
||||
think_callback.assert_called_once_with("I need to search for information about machine learning")
|
||||
observe_callback.assert_called_once_with("Machine learning is a subset of AI that enables computers to learn from data.")
|
||||
|
||||
# Verify tool was executed
|
||||
graph_rag_client = mock_flow_context("graph-rag-request")
|
||||
graph_rag_client.rag.assert_called_once_with("What is machine learning?")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_react_with_final_answer(self, agent_manager, mock_flow_context):
|
||||
"""Test ReAct cycle ending with final answer"""
|
||||
# Arrange
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I can provide a direct answer
|
||||
Final Answer: Machine learning is a branch of artificial intelligence."""
|
||||
|
||||
question = "What is machine learning?"
|
||||
history = []
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act
|
||||
action = await agent_manager.react(question, history, think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Final)
|
||||
assert action.thought == "I can provide a direct answer"
|
||||
assert action.final == "Machine learning is a branch of artificial intelligence."
|
||||
|
||||
# Verify only think callback was called (no observation for final answer)
|
||||
think_callback.assert_called_once_with("I can provide a direct answer")
|
||||
observe_callback.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_with_conversation_history(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager with conversation history"""
|
||||
# Arrange
|
||||
question = "Can you tell me more about neural networks?"
|
||||
history = [
|
||||
Action(
|
||||
thought="I need to search for information about machine learning",
|
||||
name="knowledge_query",
|
||||
arguments={"question": "What is machine learning?"},
|
||||
observation="Machine learning is a subset of AI that enables computers to learn from data."
|
||||
)
|
||||
]
|
||||
|
||||
# Act
|
||||
action = await agent_manager.reason(question, history, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
|
||||
# Verify history was included in prompt variables
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
assert len(variables["history"]) == 1
|
||||
assert variables["history"][0]["thought"] == "I need to search for information about machine learning"
|
||||
assert variables["history"][0]["action"] == "knowledge_query"
|
||||
assert variables["history"][0]["observation"] == "Machine learning is a subset of AI that enables computers to learn from data."
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_tool_selection(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager selecting different tools"""
|
||||
# Test different tool selections
|
||||
tool_scenarios = [
|
||||
("knowledge_query", "graph-rag-request"),
|
||||
("text_completion", "prompt-request"),
|
||||
]
|
||||
|
||||
for tool_name, expected_service in tool_scenarios:
|
||||
# Arrange
|
||||
mock_flow_context("prompt-request").agent_react.return_value = f"""Thought: I need to use {tool_name}
|
||||
Action: {tool_name}
|
||||
Args: {{
|
||||
"question": "test question"
|
||||
}}"""
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act
|
||||
action = await agent_manager.react("test question", [], think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.name == tool_name
|
||||
|
||||
# Verify correct service was called
|
||||
if tool_name == "knowledge_query":
|
||||
mock_flow_context("graph-rag-request").rag.assert_called()
|
||||
elif tool_name == "text_completion":
|
||||
mock_flow_context("prompt-request").question.assert_called()
|
||||
|
||||
# Reset mocks for next iteration
|
||||
for service in ["prompt-request", "graph-rag-request", "prompt-request"]:
|
||||
mock_flow_context(service).reset_mock()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_unknown_tool_error(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager error handling for unknown tool"""
|
||||
# Arrange
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I need to use an unknown tool
|
||||
Action: unknown_tool
|
||||
Args: {
|
||||
"param": "value"
|
||||
}"""
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
await agent_manager.react("test question", [], think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
assert "No action for unknown_tool!" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_tool_execution_error(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager handling tool execution errors"""
|
||||
# Arrange
|
||||
mock_flow_context("graph-rag-request").rag.side_effect = Exception("Tool execution failed")
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await agent_manager.react("test question", [], think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
assert "Tool execution failed" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_multiple_tools_coordination(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager coordination with multiple available tools"""
|
||||
# Arrange
|
||||
question = "Find information about AI and summarize it"
|
||||
|
||||
# Mock multi-step reasoning
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I need to search for AI information first
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "What is artificial intelligence?"
|
||||
}"""
|
||||
|
||||
# Act
|
||||
action = await agent_manager.reason(question, [], mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.name == "knowledge_query"
|
||||
|
||||
# Verify tool information was passed to prompt
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
|
||||
# Should have all 3 tools available
|
||||
tool_names = [tool["name"] for tool in variables["tools"]]
|
||||
assert "knowledge_query" in tool_names
|
||||
assert "text_completion" in tool_names
|
||||
assert "web_search" in tool_names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_tool_argument_validation(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager with various tool argument patterns"""
|
||||
# Arrange
|
||||
test_cases = [
|
||||
{
|
||||
"action": "knowledge_query",
|
||||
"arguments": {"question": "What is deep learning?"},
|
||||
"expected_service": "graph-rag-request"
|
||||
},
|
||||
{
|
||||
"action": "text_completion",
|
||||
"arguments": {"question": "Explain neural networks"},
|
||||
"expected_service": "prompt-request"
|
||||
},
|
||||
{
|
||||
"action": "web_search",
|
||||
"arguments": {"query": "latest AI research"},
|
||||
"expected_service": None # Custom mock
|
||||
}
|
||||
]
|
||||
|
||||
for test_case in test_cases:
|
||||
# Arrange
|
||||
# Format arguments as JSON
|
||||
import json
|
||||
args_json = json.dumps(test_case['arguments'], indent=4)
|
||||
mock_flow_context("prompt-request").agent_react.return_value = f"""Thought: Using {test_case['action']}
|
||||
Action: {test_case['action']}
|
||||
Args: {args_json}"""
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act
|
||||
action = await agent_manager.react("test", [], think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.name == test_case['action']
|
||||
assert action.arguments == test_case['arguments']
|
||||
|
||||
# Reset mocks
|
||||
for service in ["prompt-request", "graph-rag-request", "prompt-request"]:
|
||||
mock_flow_context(service).reset_mock()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_context_integration(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager integration with additional context"""
|
||||
# Arrange
|
||||
agent_with_context = AgentManager(
|
||||
tools={"knowledge_query": agent_manager.tools["knowledge_query"]},
|
||||
additional_context="You are an expert in machine learning research."
|
||||
)
|
||||
|
||||
question = "What are the latest developments in AI?"
|
||||
|
||||
# Act
|
||||
action = await agent_with_context.reason(question, [], mock_flow_context)
|
||||
|
||||
# Assert
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
|
||||
assert variables["context"] == "You are an expert in machine learning research."
|
||||
assert variables["question"] == question
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_empty_tools(self, mock_flow_context):
|
||||
"""Test agent manager with no tools available"""
|
||||
# Arrange
|
||||
agent_no_tools = AgentManager(tools={}, additional_context="")
|
||||
|
||||
question = "What is machine learning?"
|
||||
|
||||
# Act
|
||||
action = await agent_no_tools.reason(question, [], mock_flow_context)
|
||||
|
||||
# Assert
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
|
||||
assert len(variables["tools"]) == 0
|
||||
assert variables["tool_names"] == ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_tool_response_processing(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager processing different tool response types"""
|
||||
# Arrange
|
||||
response_scenarios = [
|
||||
"Simple text response",
|
||||
"Multi-line response\nwith several lines\nof information",
|
||||
"Response with special characters: @#$%^&*()_+-=[]{}|;':\",./<>?",
|
||||
" Response with whitespace ",
|
||||
"" # Empty response
|
||||
]
|
||||
|
||||
for expected_response in response_scenarios:
|
||||
# Set up mock response
|
||||
mock_flow_context("graph-rag-request").rag.return_value = expected_response
|
||||
|
||||
think_callback = AsyncMock()
|
||||
observe_callback = AsyncMock()
|
||||
|
||||
# Act
|
||||
action = await agent_manager.react("test question", [], think_callback, observe_callback, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert action.observation == expected_response.strip()
|
||||
observe_callback.assert_called_with(expected_response.strip())
|
||||
|
||||
# Reset mocks
|
||||
mock_flow_context("graph-rag-request").reset_mock()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_malformed_response_handling(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager handling of malformed text responses"""
|
||||
# Test cases with expected error messages
|
||||
test_cases = [
|
||||
# Missing action/final answer
|
||||
{
|
||||
"response": "Thought: I need to do something",
|
||||
"error_contains": "Response has thought but no action or final answer"
|
||||
},
|
||||
# Invalid JSON in Args
|
||||
{
|
||||
"response": """Thought: I need to search
|
||||
Action: knowledge_query
|
||||
Args: {invalid json}""",
|
||||
"error_contains": "Invalid JSON in Args"
|
||||
},
|
||||
# Empty response
|
||||
{
|
||||
"response": "",
|
||||
"error_contains": "Could not parse response"
|
||||
},
|
||||
# Only whitespace
|
||||
{
|
||||
"response": " \n\t ",
|
||||
"error_contains": "Could not parse response"
|
||||
},
|
||||
# Missing Args for action (should create empty args dict)
|
||||
{
|
||||
"response": """Thought: I need to search
|
||||
Action: knowledge_query""",
|
||||
"error_contains": None # This should actually succeed with empty args
|
||||
},
|
||||
# Incomplete JSON
|
||||
{
|
||||
"response": """Thought: I need to search
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "test"
|
||||
""",
|
||||
"error_contains": "Invalid JSON in Args"
|
||||
},
|
||||
]
|
||||
|
||||
for test_case in test_cases:
|
||||
mock_flow_context("prompt-request").agent_react.return_value = test_case["response"]
|
||||
|
||||
if test_case["error_contains"]:
|
||||
# Should raise an error
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
await agent_manager.reason("test question", [], mock_flow_context)
|
||||
|
||||
assert "Failed to parse agent response" in str(exc_info.value)
|
||||
assert test_case["error_contains"] in str(exc_info.value)
|
||||
else:
|
||||
# Should succeed
|
||||
action = await agent_manager.reason("test question", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert action.name == "knowledge_query"
|
||||
assert action.arguments == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_text_parsing_edge_cases(self, agent_manager, mock_flow_context):
|
||||
"""Test edge cases in text parsing"""
|
||||
# Test response with markdown code blocks
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """```
|
||||
Thought: I need to search for information
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "What is AI?"
|
||||
}
|
||||
```"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert action.thought == "I need to search for information"
|
||||
assert action.name == "knowledge_query"
|
||||
|
||||
# Test response with extra whitespace
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """
|
||||
|
||||
Thought: I need to think about this
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "test"
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert action.thought == "I need to think about this"
|
||||
assert action.name == "knowledge_query"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_multiline_content(self, agent_manager, mock_flow_context):
|
||||
"""Test handling of multi-line thoughts and final answers"""
|
||||
# Multi-line thought
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I need to consider multiple factors:
|
||||
1. The user's question is complex
|
||||
2. I should search for comprehensive information
|
||||
3. This requires using the knowledge query tool
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "complex query"
|
||||
}"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert "multiple factors" in action.thought
|
||||
assert "knowledge query tool" in action.thought
|
||||
|
||||
# Multi-line final answer
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I have gathered enough information
|
||||
Final Answer: Here is a comprehensive answer:
|
||||
1. First point about the topic
|
||||
2. Second point with details
|
||||
3. Final conclusion
|
||||
|
||||
This covers all aspects of the question."""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Final)
|
||||
assert "First point" in action.final
|
||||
assert "Final conclusion" in action.final
|
||||
assert "all aspects" in action.final
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_json_args_special_characters(self, agent_manager, mock_flow_context):
|
||||
"""Test JSON arguments with special characters and edge cases"""
|
||||
# Test with special characters in JSON (properly escaped)
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: Processing special characters
|
||||
Action: knowledge_query
|
||||
Args: {
|
||||
"question": "What about \\"quotes\\" and 'apostrophes'?",
|
||||
"context": "Line 1\\nLine 2\\tTabbed",
|
||||
"special": "Symbols: @#$%^&*()_+-=[]{}|;':,.<>?"
|
||||
}"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert action.arguments["question"] == 'What about "quotes" and \'apostrophes\'?'
|
||||
assert action.arguments["context"] == "Line 1\nLine 2\tTabbed"
|
||||
assert "@#$%^&*" in action.arguments["special"]
|
||||
|
||||
# Test with nested JSON
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: Complex arguments
|
||||
Action: web_search
|
||||
Args: {
|
||||
"query": "test",
|
||||
"options": {
|
||||
"limit": 10,
|
||||
"filters": ["recent", "relevant"],
|
||||
"metadata": {
|
||||
"source": "user",
|
||||
"timestamp": "2024-01-01"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Action)
|
||||
assert action.arguments["options"]["limit"] == 10
|
||||
assert "recent" in action.arguments["options"]["filters"]
|
||||
assert action.arguments["options"]["metadata"]["source"] == "user"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_final_answer_json_format(self, agent_manager, mock_flow_context):
|
||||
"""Test final answers that contain JSON-like content"""
|
||||
# Final answer with JSON content
|
||||
mock_flow_context("prompt-request").agent_react.return_value = """Thought: I can provide the data in JSON format
|
||||
Final Answer: {
|
||||
"result": "success",
|
||||
"data": {
|
||||
"name": "Machine Learning",
|
||||
"type": "AI Technology",
|
||||
"applications": ["NLP", "Computer Vision", "Robotics"]
|
||||
},
|
||||
"confidence": 0.95
|
||||
}"""
|
||||
|
||||
action = await agent_manager.reason("test", [], mock_flow_context)
|
||||
assert isinstance(action, Final)
|
||||
# The final answer should preserve the JSON structure as a string
|
||||
assert '"result": "success"' in action.final
|
||||
assert '"applications":' in action.final
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.slow
|
||||
async def test_agent_manager_performance_with_large_history(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager performance with large conversation history"""
|
||||
# Arrange
|
||||
large_history = [
|
||||
Action(
|
||||
thought=f"Step {i} thinking",
|
||||
name="knowledge_query",
|
||||
arguments={"question": f"Question {i}"},
|
||||
observation=f"Observation {i}"
|
||||
)
|
||||
for i in range(50) # Large history
|
||||
]
|
||||
|
||||
question = "Final question"
|
||||
|
||||
# Act
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
action = await agent_manager.reason(question, large_history, mock_flow_context)
|
||||
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
assert execution_time < 5.0 # Should complete within reasonable time
|
||||
|
||||
# Verify history was processed correctly
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
assert len(variables["history"]) == 50
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_manager_json_serialization(self, agent_manager, mock_flow_context):
|
||||
"""Test agent manager handling of JSON serialization in prompts"""
|
||||
# Arrange
|
||||
complex_history = [
|
||||
Action(
|
||||
thought="Complex thinking with special characters: \"quotes\", 'apostrophes', and symbols",
|
||||
name="knowledge_query",
|
||||
arguments={"question": "What about JSON serialization?", "complex": {"nested": "value"}},
|
||||
observation="Response with JSON: {\"key\": \"value\"}"
|
||||
)
|
||||
]
|
||||
|
||||
question = "Handle JSON properly"
|
||||
|
||||
# Act
|
||||
action = await agent_manager.reason(question, complex_history, mock_flow_context)
|
||||
|
||||
# Assert
|
||||
assert isinstance(action, Action)
|
||||
|
||||
# Verify JSON was properly serialized in prompt
|
||||
prompt_client = mock_flow_context("prompt-request")
|
||||
call_args = prompt_client.agent_react.call_args
|
||||
variables = call_args[0][0]
|
||||
|
||||
# Should not raise JSON serialization errors
|
||||
json_str = json.dumps(variables, indent=4)
|
||||
assert len(json_str) > 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue