tooluniverse 1.0.3__py3-none-any.whl → 1.0.5__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 tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +17 -5
- tooluniverse/agentic_tool.py +268 -330
- tooluniverse/compose_scripts/output_summarizer.py +21 -15
- tooluniverse/data/agentic_tools.json +2 -2
- tooluniverse/data/odphp_tools.json +354 -0
- tooluniverse/data/output_summarization_tools.json +2 -2
- tooluniverse/default_config.py +1 -0
- tooluniverse/llm_clients.py +570 -0
- tooluniverse/mcp_tool_registry.py +3 -3
- tooluniverse/odphp_tool.py +226 -0
- tooluniverse/output_hook.py +92 -3
- tooluniverse/remote/boltz/boltz_mcp_server.py +2 -2
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +2 -2
- tooluniverse/smcp.py +204 -112
- tooluniverse/smcp_server.py +23 -20
- tooluniverse/test/list_azure_openai_models.py +210 -0
- tooluniverse/test/test_agentic_tool_azure_models.py +91 -0
- tooluniverse/test/test_api_key_validation_min.py +64 -0
- tooluniverse/test/test_claude_sdk.py +86 -0
- tooluniverse/test/test_global_fallback.py +288 -0
- tooluniverse/test/test_hooks_direct.py +219 -0
- tooluniverse/test/test_odphp_tool.py +166 -0
- tooluniverse/test/test_openrouter_client.py +288 -0
- tooluniverse/test/test_stdio_hooks.py +285 -0
- tooluniverse/test/test_tool_finder.py +1 -1
- {tooluniverse-1.0.3.dist-info → tooluniverse-1.0.5.dist-info}/METADATA +101 -74
- {tooluniverse-1.0.3.dist-info → tooluniverse-1.0.5.dist-info}/RECORD +31 -19
- tooluniverse-1.0.5.dist-info/licenses/LICENSE +201 -0
- tooluniverse-1.0.3.dist-info/licenses/LICENSE +0 -21
- {tooluniverse-1.0.3.dist-info → tooluniverse-1.0.5.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.3.dist-info → tooluniverse-1.0.5.dist-info}/entry_points.txt +0 -0
- {tooluniverse-1.0.3.dist-info → tooluniverse-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Batch test: AgenticTool with multiple Azure OpenAI deployments.
|
|
4
|
+
- Validates API key during initialization
|
|
5
|
+
- Performs a tiny run call per model (1 token)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
# Ensure src/ is importable
|
|
13
|
+
CURRENT_DIR = os.path.dirname(__file__)
|
|
14
|
+
SRC_DIR = os.path.join(CURRENT_DIR, "src")
|
|
15
|
+
if SRC_DIR not in sys.path:
|
|
16
|
+
sys.path.insert(0, SRC_DIR)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from tooluniverse.agentic_tool import AgenticTool # type: ignore
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Fallback for when running from different directory
|
|
22
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
23
|
+
from tooluniverse.agentic_tool import AgenticTool # type: ignore
|
|
24
|
+
|
|
25
|
+
# Chat-capable deployment IDs to test (skip embeddings)
|
|
26
|
+
MODELS: List[str] = [
|
|
27
|
+
"gpt-4.1",
|
|
28
|
+
"gpt-4.1-mini",
|
|
29
|
+
"gpt-4.1-nano",
|
|
30
|
+
"gpt-4o-1120",
|
|
31
|
+
"gpt-4o-0806",
|
|
32
|
+
"gpt-4o-mini-0718",
|
|
33
|
+
"o4-mini-0416",
|
|
34
|
+
"o3-mini-0131",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_model(model_id: str) -> None:
|
|
39
|
+
print(f"\n=== Testing model: {model_id} ===")
|
|
40
|
+
config = {
|
|
41
|
+
"name": f"agentic_test_{model_id}",
|
|
42
|
+
"description": "AgenticTool model validation",
|
|
43
|
+
"type": "AgenticTool",
|
|
44
|
+
"prompt": "Echo: {q}",
|
|
45
|
+
"input_arguments": ["q"],
|
|
46
|
+
"parameter": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"q": {"type": "string", "description": "input", "required": True}
|
|
50
|
+
},
|
|
51
|
+
"required": ["q"],
|
|
52
|
+
},
|
|
53
|
+
"configs": {
|
|
54
|
+
"api_type": "CHATGPT",
|
|
55
|
+
"model_id": model_id,
|
|
56
|
+
"validate_api_key": True,
|
|
57
|
+
"temperature": 0.0,
|
|
58
|
+
"max_new_tokens": 1,
|
|
59
|
+
"return_json": False,
|
|
60
|
+
"return_metadata": False,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
tool = AgenticTool(config)
|
|
66
|
+
print("- Init: OK (API key validated)")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"- Init: FAIL -> {e}")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
out = tool.run({"q": "ping"})
|
|
73
|
+
ok = isinstance(out, (str, dict))
|
|
74
|
+
print(
|
|
75
|
+
f"- Run : {'OK' if ok else 'WARN'} -> {str(out)[:120].replace('\n', ' ')}"
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"- Run : FAIL -> {e}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main() -> None:
|
|
82
|
+
print("Azure endpoint:", os.getenv("AZURE_OPENAI_ENDPOINT"))
|
|
83
|
+
print("Using per-model versions:")
|
|
84
|
+
print(os.getenv("AZURE_OPENAI_API_VERSION_BY_MODEL", "<none>"))
|
|
85
|
+
|
|
86
|
+
for m in MODELS:
|
|
87
|
+
test_model(m)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Minimal test: validate API key during AgenticTool initialization.
|
|
4
|
+
- Success: prints OK and model info
|
|
5
|
+
- Failure: prints the error message (e.g., invalid/missing key or model not deployed)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
# Ensure src/ is importable
|
|
12
|
+
CURRENT_DIR = os.path.dirname(__file__)
|
|
13
|
+
SRC_DIR = os.path.join(CURRENT_DIR, "src")
|
|
14
|
+
if SRC_DIR not in sys.path:
|
|
15
|
+
sys.path.insert(0, SRC_DIR)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from tooluniverse.agentic_tool import AgenticTool # type: ignore
|
|
19
|
+
except ImportError:
|
|
20
|
+
# Fallback for when running from different directory
|
|
21
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
22
|
+
from tooluniverse.agentic_tool import AgenticTool # type: ignore
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> None:
|
|
26
|
+
config = {
|
|
27
|
+
"name": "api_key_validation_test",
|
|
28
|
+
"description": "Minimal API key validation test",
|
|
29
|
+
"type": "AgenticTool",
|
|
30
|
+
"prompt": "Test: {q}",
|
|
31
|
+
"input_arguments": ["q"],
|
|
32
|
+
"parameter": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"q": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "placeholder",
|
|
38
|
+
"required": True,
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": ["q"],
|
|
42
|
+
},
|
|
43
|
+
"configs": {
|
|
44
|
+
"api_type": "CHATGPT", # or "GEMINI" if you prefer
|
|
45
|
+
"model_id": "gpt-4o-1120", # requested model to test
|
|
46
|
+
"validate_api_key": True, # this triggers validation during init
|
|
47
|
+
"temperature": 0.0,
|
|
48
|
+
"max_new_tokens": 1,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
print("Running minimal API key validation test...")
|
|
53
|
+
try:
|
|
54
|
+
tool = AgenticTool(config)
|
|
55
|
+
info = tool.get_model_info()
|
|
56
|
+
print("OK: initialization succeeded and API key validated.")
|
|
57
|
+
print(f"Model info: {info}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print("ERROR: initialization failed during API key validation.")
|
|
60
|
+
print(f"Reason: {e}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from claude_agent_sdk import ClaudeAgentOptions, query
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
|
|
6
|
+
load_dotenv()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def delegate_task(
|
|
10
|
+
prompt: str,
|
|
11
|
+
append_system_prompt: str,
|
|
12
|
+
):
|
|
13
|
+
"""Delegate a task to an expert
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
prompt: The prompt describing the task to delegate
|
|
17
|
+
append_system_prompt: The system prompt describing the expert
|
|
18
|
+
Returns:
|
|
19
|
+
The result of the delegation
|
|
20
|
+
"""
|
|
21
|
+
import os
|
|
22
|
+
cwd = os.getcwd()
|
|
23
|
+
|
|
24
|
+
async for message in query(
|
|
25
|
+
prompt=prompt,
|
|
26
|
+
options=ClaudeAgentOptions(
|
|
27
|
+
system_prompt={
|
|
28
|
+
"type": "preset",
|
|
29
|
+
"preset": "claude_code",
|
|
30
|
+
"append": append_system_prompt,
|
|
31
|
+
}, # Use the preset
|
|
32
|
+
cwd=cwd,
|
|
33
|
+
permission_mode="bypassPermissions",
|
|
34
|
+
mcp_servers={
|
|
35
|
+
"tooluniverse": {
|
|
36
|
+
"type": "stdio",
|
|
37
|
+
"command": "tooluniverse-smcp-stdio",
|
|
38
|
+
"args": [],
|
|
39
|
+
"env": {},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
),
|
|
43
|
+
):
|
|
44
|
+
# Print all message types to see tool usage
|
|
45
|
+
message_type = type(message).__name__
|
|
46
|
+
print(f"\n--- Message Type: {message_type} ---")
|
|
47
|
+
|
|
48
|
+
# Check for tool use messages
|
|
49
|
+
if message_type == "ToolUseMessage":
|
|
50
|
+
print(f"Tool Name: {message.name}")
|
|
51
|
+
print(f"Tool Input: {message.input}")
|
|
52
|
+
elif message_type == "ToolResultMessage":
|
|
53
|
+
print(f"Tool: {message.tool_use_id}")
|
|
54
|
+
result_preview = (
|
|
55
|
+
str(message.content)[:200]
|
|
56
|
+
if hasattr(message, "content")
|
|
57
|
+
else str(message)[:200]
|
|
58
|
+
)
|
|
59
|
+
print(f"Result: {result_preview}...")
|
|
60
|
+
elif message_type == "TextMessage":
|
|
61
|
+
text_preview = (
|
|
62
|
+
str(message.text)[:200]
|
|
63
|
+
if hasattr(message, "text")
|
|
64
|
+
else str(message)[:200]
|
|
65
|
+
)
|
|
66
|
+
print(f"Text: {text_preview}...")
|
|
67
|
+
elif message_type == "ResultMessage":
|
|
68
|
+
return {
|
|
69
|
+
"status": "success",
|
|
70
|
+
"result": message.result,
|
|
71
|
+
}
|
|
72
|
+
else:
|
|
73
|
+
# Print any other message types for debugging
|
|
74
|
+
print(f"Message: {message}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
# result = asyncio.run(conduct_research("What is the capital of France?"))
|
|
79
|
+
# print(result)
|
|
80
|
+
result = asyncio.run(
|
|
81
|
+
delegate_task(
|
|
82
|
+
"What tools do you have available? Return a list of all available tool names",
|
|
83
|
+
"You are a helpful assistant",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
print(result)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test script to verify global fallback functionality in AgenticTool.
|
|
4
|
+
Tests the system-wide default fallback chain.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
from src.tooluniverse.agentic_tool import AgenticTool
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_default_global_fallback():
|
|
13
|
+
"""Test: Tool uses default global fallback chain when no explicit fallback is configured."""
|
|
14
|
+
|
|
15
|
+
print("=== Test 1: Default Global Fallback Chain ===\n")
|
|
16
|
+
|
|
17
|
+
# Remove Gemini API key to force fallback
|
|
18
|
+
original_gemini_key = os.environ.get("GEMINI_API_KEY")
|
|
19
|
+
if "GEMINI_API_KEY" in os.environ:
|
|
20
|
+
del os.environ["GEMINI_API_KEY"]
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
# Create tool with only primary API (no explicit fallback)
|
|
24
|
+
tool = AgenticTool(
|
|
25
|
+
{
|
|
26
|
+
"name": "gemini_with_global_fallback",
|
|
27
|
+
"api_type": "GEMINI",
|
|
28
|
+
"model_id": "gemini-2.0-flash",
|
|
29
|
+
# No fallback_api_type configured - should use global fallback
|
|
30
|
+
"prompt": "You are a helpful assistant. Answer: {question}",
|
|
31
|
+
"input_arguments": ["question"],
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
print(f"Tool available: {tool.is_available()}")
|
|
36
|
+
print(f"Current API: {tool._current_api_type}")
|
|
37
|
+
print(f"Current model: {tool._current_model_id}")
|
|
38
|
+
print(f"Original config: {tool._api_type} with {tool._model_id}")
|
|
39
|
+
print(f"Global fallback enabled: {tool._use_global_fallback}")
|
|
40
|
+
print(f"Global fallback chain: {tool._global_fallback_chain}")
|
|
41
|
+
|
|
42
|
+
if tool.is_available():
|
|
43
|
+
result = tool.run({"question": "What is 2+2?"})
|
|
44
|
+
if result.get("success"):
|
|
45
|
+
print(f"✅ Tool worked with global fallback: {result['result']}")
|
|
46
|
+
else:
|
|
47
|
+
print(f"❌ Tool failed: {result['error']}")
|
|
48
|
+
else:
|
|
49
|
+
print(f"❌ Tool not available: {tool._initialization_error}")
|
|
50
|
+
|
|
51
|
+
finally:
|
|
52
|
+
# Restore Gemini key
|
|
53
|
+
if original_gemini_key:
|
|
54
|
+
os.environ["GEMINI_API_KEY"] = original_gemini_key
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_custom_global_fallback():
|
|
58
|
+
"""Test: Tool uses custom global fallback chain from environment variable."""
|
|
59
|
+
|
|
60
|
+
print("\n=== Test 2: Custom Global Fallback Chain ===\n")
|
|
61
|
+
|
|
62
|
+
# Set custom global fallback chain
|
|
63
|
+
custom_chain = [
|
|
64
|
+
{"api_type": "GEMINI", "model_id": "gemini-2.0-flash"},
|
|
65
|
+
{"api_type": "CHATGPT", "model_id": "gpt-4o-mini-0718"},
|
|
66
|
+
]
|
|
67
|
+
os.environ["AGENTIC_TOOL_FALLBACK_CHAIN"] = json.dumps(custom_chain)
|
|
68
|
+
|
|
69
|
+
# Remove Azure OpenAI API key to force fallback
|
|
70
|
+
original_azure_key = os.environ.get("AZURE_OPENAI_API_KEY")
|
|
71
|
+
if "AZURE_OPENAI_API_KEY" in os.environ:
|
|
72
|
+
del os.environ["AZURE_OPENAI_API_KEY"]
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Create tool with ChatGPT as primary
|
|
76
|
+
tool = AgenticTool(
|
|
77
|
+
{
|
|
78
|
+
"name": "chatgpt_with_custom_global_fallback",
|
|
79
|
+
"api_type": "CHATGPT",
|
|
80
|
+
"model_id": "gpt-4o-1120",
|
|
81
|
+
"prompt": "You are a helpful assistant. Answer: {question}",
|
|
82
|
+
"input_arguments": ["question"],
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
print(f"Tool available: {tool.is_available()}")
|
|
87
|
+
print(f"Current API: {tool._current_api_type}")
|
|
88
|
+
print(f"Current model: {tool._current_model_id}")
|
|
89
|
+
print(f"Original config: {tool._api_type} with {tool._model_id}")
|
|
90
|
+
print(f"Custom global fallback chain: {tool._global_fallback_chain}")
|
|
91
|
+
|
|
92
|
+
if tool.is_available():
|
|
93
|
+
result = tool.run({"question": "What is 2+2?"})
|
|
94
|
+
if result.get("success"):
|
|
95
|
+
print(f"✅ Tool worked with custom global fallback: {result['result']}")
|
|
96
|
+
else:
|
|
97
|
+
print(f"❌ Tool failed: {result['error']}")
|
|
98
|
+
else:
|
|
99
|
+
print(f"❌ Tool not available: {tool._initialization_error}")
|
|
100
|
+
|
|
101
|
+
finally:
|
|
102
|
+
# Restore Azure key and remove custom chain
|
|
103
|
+
if original_azure_key:
|
|
104
|
+
os.environ["AZURE_OPENAI_API_KEY"] = original_azure_key
|
|
105
|
+
if "AGENTIC_TOOL_FALLBACK_CHAIN" in os.environ:
|
|
106
|
+
del os.environ["AGENTIC_TOOL_FALLBACK_CHAIN"]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_disable_global_fallback():
|
|
110
|
+
"""Test: Tool can disable global fallback."""
|
|
111
|
+
|
|
112
|
+
print("\n=== Test 3: Disable Global Fallback ===\n")
|
|
113
|
+
|
|
114
|
+
# Remove Azure OpenAI API key
|
|
115
|
+
original_azure_key = os.environ.get("AZURE_OPENAI_API_KEY")
|
|
116
|
+
if "AZURE_OPENAI_API_KEY" in os.environ:
|
|
117
|
+
del os.environ["AZURE_OPENAI_API_KEY"]
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# Create tool with global fallback disabled
|
|
121
|
+
tool = AgenticTool(
|
|
122
|
+
{
|
|
123
|
+
"name": "no_global_fallback",
|
|
124
|
+
"api_type": "CHATGPT",
|
|
125
|
+
"model_id": "gpt-4o-1120",
|
|
126
|
+
"use_global_fallback": False, # Disable global fallback
|
|
127
|
+
"prompt": "You are a helpful assistant. Answer: {question}",
|
|
128
|
+
"input_arguments": ["question"],
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
print(f"Tool available: {tool.is_available()}")
|
|
133
|
+
print(f"Current API: {tool._current_api_type}")
|
|
134
|
+
print(f"Current model: {tool._current_model_id}")
|
|
135
|
+
print(f"Global fallback enabled: {tool._use_global_fallback}")
|
|
136
|
+
|
|
137
|
+
if tool.is_available():
|
|
138
|
+
result = tool.run({"question": "What is 2+2?"})
|
|
139
|
+
print(f"Unexpected success: {result}")
|
|
140
|
+
else:
|
|
141
|
+
print(
|
|
142
|
+
f"✅ Tool correctly failed without global fallback: {tool._initialization_error}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
finally:
|
|
146
|
+
# Restore Azure key
|
|
147
|
+
if original_azure_key:
|
|
148
|
+
os.environ["AZURE_OPENAI_API_KEY"] = original_azure_key
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_explicit_fallback_priority():
|
|
152
|
+
"""Test: Explicit fallback takes priority over global fallback."""
|
|
153
|
+
|
|
154
|
+
print("\n=== Test 4: Explicit Fallback Priority ===\n")
|
|
155
|
+
|
|
156
|
+
# Remove Gemini API key
|
|
157
|
+
original_gemini_key = os.environ.get("GEMINI_API_KEY")
|
|
158
|
+
if "GEMINI_API_KEY" in os.environ:
|
|
159
|
+
del os.environ["GEMINI_API_KEY"]
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# Create tool with both explicit and global fallback
|
|
163
|
+
tool = AgenticTool(
|
|
164
|
+
{
|
|
165
|
+
"name": "explicit_vs_global_fallback",
|
|
166
|
+
"api_type": "GEMINI",
|
|
167
|
+
"model_id": "gemini-2.0-flash",
|
|
168
|
+
"fallback_api_type": "CHATGPT", # Explicit fallback
|
|
169
|
+
"fallback_model_id": "gpt-4o-mini-0718", # Different from global default
|
|
170
|
+
"prompt": "You are a helpful assistant. Answer: {question}",
|
|
171
|
+
"input_arguments": ["question"],
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
print(f"Tool available: {tool.is_available()}")
|
|
176
|
+
print(f"Current API: {tool._current_api_type}")
|
|
177
|
+
print(f"Current model: {tool._current_model_id}")
|
|
178
|
+
print(
|
|
179
|
+
f"Explicit fallback: {tool._fallback_api_type} ({tool._fallback_model_id})"
|
|
180
|
+
)
|
|
181
|
+
print(f"Global fallback chain: {tool._global_fallback_chain}")
|
|
182
|
+
|
|
183
|
+
if tool.is_available():
|
|
184
|
+
result = tool.run({"question": "What is 2+2?"})
|
|
185
|
+
if result.get("success"):
|
|
186
|
+
print(f"✅ Tool worked with explicit fallback: {result['result']}")
|
|
187
|
+
# Check if it used explicit fallback (gpt-4o-mini-0718) instead of global default (gpt-4o-1120)
|
|
188
|
+
if tool._current_model_id == "gpt-4o-mini-0718":
|
|
189
|
+
print("✅ Used explicit fallback as expected")
|
|
190
|
+
else:
|
|
191
|
+
print(
|
|
192
|
+
f"⚠️ Used {tool._current_model_id} instead of explicit fallback"
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
print(f"❌ Tool failed: {result['error']}")
|
|
196
|
+
else:
|
|
197
|
+
print(f"❌ Tool not available: {tool._initialization_error}")
|
|
198
|
+
|
|
199
|
+
finally:
|
|
200
|
+
# Restore Gemini key
|
|
201
|
+
if original_gemini_key:
|
|
202
|
+
os.environ["GEMINI_API_KEY"] = original_gemini_key
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_multiple_tools_global_fallback():
|
|
206
|
+
"""Test: Multiple tools using global fallback."""
|
|
207
|
+
|
|
208
|
+
print("\n=== Test 5: Multiple Tools with Global Fallback ===\n")
|
|
209
|
+
|
|
210
|
+
# Remove Gemini API key to force fallback for Gemini tools
|
|
211
|
+
original_gemini_key = os.environ.get("GEMINI_API_KEY")
|
|
212
|
+
if "GEMINI_API_KEY" in os.environ:
|
|
213
|
+
del os.environ["GEMINI_API_KEY"]
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
tools_config = [
|
|
217
|
+
{
|
|
218
|
+
"name": "gemini_tool_1",
|
|
219
|
+
"api_type": "GEMINI",
|
|
220
|
+
"model_id": "gemini-2.0-flash",
|
|
221
|
+
"prompt": "Gemini tool 1: {question}",
|
|
222
|
+
"input_arguments": ["question"],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "gemini_tool_2",
|
|
226
|
+
"api_type": "GEMINI",
|
|
227
|
+
"model_id": "gemini-2.0-flash",
|
|
228
|
+
"prompt": "Gemini tool 2: {question}",
|
|
229
|
+
"input_arguments": ["question"],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "chatgpt_tool",
|
|
233
|
+
"api_type": "CHATGPT",
|
|
234
|
+
"model_id": "gpt-4o-1120",
|
|
235
|
+
"prompt": "ChatGPT tool: {question}",
|
|
236
|
+
"input_arguments": ["question"],
|
|
237
|
+
},
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
tools = []
|
|
241
|
+
for config in tools_config:
|
|
242
|
+
tool = AgenticTool(config)
|
|
243
|
+
tools.append(tool)
|
|
244
|
+
|
|
245
|
+
status = "✅ Available" if tool.is_available() else "❌ Unavailable"
|
|
246
|
+
current_api = (
|
|
247
|
+
f"{tool._current_api_type} ({tool._current_model_id})"
|
|
248
|
+
if tool._current_api_type
|
|
249
|
+
else "None"
|
|
250
|
+
)
|
|
251
|
+
print(f"{config['name']}: {status} - Using: {current_api}")
|
|
252
|
+
|
|
253
|
+
print()
|
|
254
|
+
|
|
255
|
+
# Test all available tools
|
|
256
|
+
available_tools = [tool for tool in tools if tool.is_available()]
|
|
257
|
+
if available_tools:
|
|
258
|
+
print("=== Testing Available Tools ===")
|
|
259
|
+
for tool in available_tools:
|
|
260
|
+
result = tool.run({"question": "Hello!"})
|
|
261
|
+
if result.get("success"):
|
|
262
|
+
print(f"✅ {tool.name}: {result['result'][:50]}...")
|
|
263
|
+
else:
|
|
264
|
+
print(f"❌ {tool.name}: {result['error']}")
|
|
265
|
+
|
|
266
|
+
finally:
|
|
267
|
+
# Restore Gemini key
|
|
268
|
+
if original_gemini_key:
|
|
269
|
+
os.environ["GEMINI_API_KEY"] = original_gemini_key
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
test_default_global_fallback()
|
|
274
|
+
test_custom_global_fallback()
|
|
275
|
+
test_disable_global_fallback()
|
|
276
|
+
test_explicit_fallback_priority()
|
|
277
|
+
test_multiple_tools_global_fallback()
|
|
278
|
+
|
|
279
|
+
print("\n=== Summary ===")
|
|
280
|
+
print("✅ Global fallback chain works as default")
|
|
281
|
+
print("✅ Custom global fallback chain from environment variable")
|
|
282
|
+
print("✅ Global fallback can be disabled per tool")
|
|
283
|
+
print("✅ Explicit fallback takes priority over global fallback")
|
|
284
|
+
print("✅ Multiple tools can use global fallback independently")
|
|
285
|
+
print("\n=== Configuration Options ===")
|
|
286
|
+
print("• use_global_fallback: true/false (default: true)")
|
|
287
|
+
print("• AGENTIC_TOOL_FALLBACK_CHAIN: JSON array of {api_type, model_id}")
|
|
288
|
+
print("• Explicit fallback_api_type/fallback_model_id takes priority")
|