567 lines
12 KiB
Markdown
567 lines
12 KiB
Markdown
---
|
|
title: "ToolUniverse Tool Fix Examples"
|
|
task: ""
|
|
lineage_type: import
|
|
upstream_source: https://github.com/mims-harvard/ToolUniverse/blob/e2520a96/skills/devtu-fix-tool/EXAMPLES.md
|
|
upstream_sha: e2520a96
|
|
imported_at: 2026-06-26
|
|
prompt_class: prompt
|
|
upstream_changes: accepted
|
|
author: upstream
|
|
validated: false
|
|
---
|
|
|
|
# ToolUniverse Tool Fix Examples
|
|
|
|
Real-world examples of fixing ToolUniverse tools, including the complete ChEMBL image endpoint fix.
|
|
|
|
## Example 1: Binary Response Handling (ChEMBL Image)
|
|
|
|
### Initial Error
|
|
```bash
|
|
$ python scripts/test_new_tools.py chembl -v
|
|
|
|
Testing ChEMBL_get_molecule_image (1 examples)...
|
|
❌ ChEMBL_get_molecule_image Ex 1: Failed -
|
|
ChEMBL API request failed: Expecting value: line 1 column 1 (char 0)
|
|
|
|
Tests Run: 64
|
|
Passed: 63 (98.4%)
|
|
Failed: 1
|
|
```
|
|
|
|
### Diagnosis
|
|
|
|
**Step 1: Run direct test**
|
|
```python
|
|
from tooluniverse import ToolUniverse
|
|
import json
|
|
|
|
tu = ToolUniverse()
|
|
tu.load_tools()
|
|
|
|
result = tu.run_one_function({
|
|
'name': 'ChEMBL_get_molecule_image',
|
|
'arguments': {
|
|
'chembl_id': 'CHEMBL25',
|
|
'format': 'svg'
|
|
}
|
|
})
|
|
|
|
print(json.dumps(result, indent=2))
|
|
```
|
|
|
|
**Output**: Same JSON parsing error
|
|
|
|
**Step 2: Check API directly**
|
|
```python
|
|
import requests
|
|
|
|
url = "https://www.ebi.ac.uk/chembl/api/data/image/CHEMBL25?format=svg"
|
|
response = requests.get(url)
|
|
|
|
print("Status:", response.status_code) # 200
|
|
print("Content-Type:", response.headers.get("Content-Type")) # image/svg+xml
|
|
print("Is JSON?:", response.text[:10]) # <?xml... (SVG content)
|
|
```
|
|
|
|
**Root cause**: API returns SVG image (binary), but tool tries to parse as JSON.
|
|
|
|
### Solution
|
|
|
|
**File 1: src/tooluniverse/chem_tool.py**
|
|
|
|
Located the issue in `ChEMBLRESTTool.run()` method:
|
|
|
|
```python
|
|
# BEFORE (line 146)
|
|
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
url = self._build_url(arguments)
|
|
params = self._build_params(arguments)
|
|
response = request_with_retry(...)
|
|
response.raise_for_status()
|
|
|
|
data = response.json() # ❌ Fails for binary data
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": data,
|
|
"url": response.url,
|
|
}
|
|
```
|
|
|
|
Added binary detection:
|
|
|
|
```python
|
|
# AFTER
|
|
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
url = self._build_url(arguments)
|
|
params = self._build_params(arguments)
|
|
tool_name = self.tool_config.get("name", "")
|
|
|
|
# Check if this is an image endpoint
|
|
is_image_endpoint = "get_molecule_image" in tool_name.lower() or "/image/" in url
|
|
|
|
response = request_with_retry(...)
|
|
response.raise_for_status()
|
|
|
|
# Handle image endpoints differently
|
|
if is_image_endpoint:
|
|
content_type = response.headers.get("Content-Type", "")
|
|
if "image" in content_type or "svg" in content_type:
|
|
return {
|
|
"status": "success",
|
|
"data": f"Image data available at URL (Content-Type: {content_type})",
|
|
"url": response.url,
|
|
"content_type": content_type,
|
|
"image_size_bytes": len(response.content)
|
|
}
|
|
|
|
data = response.json() # ✅ Only called for JSON endpoints
|
|
|
|
return {
|
|
"status": "success",
|
|
"data": data,
|
|
"url": response.url,
|
|
}
|
|
```
|
|
|
|
**File 2: src/tooluniverse/data/chembl_tools.json**
|
|
|
|
Initially tried this schema (wrong):
|
|
```json
|
|
{
|
|
"name": "ChEMBL_get_molecule_image",
|
|
"return_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"status": {"type": "string"},
|
|
"data": {"type": "string"},
|
|
"url": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Result: Still failed schema validation because test validates `data` field (a string) against entire schema (which says root type is "object").
|
|
|
|
**Correct schema**:
|
|
```json
|
|
{
|
|
"name": "ChEMBL_get_molecule_image",
|
|
"return_schema": {
|
|
"type": "string",
|
|
"description": "Description of image data availability and access information"
|
|
}
|
|
}
|
|
```
|
|
|
|
**File 3: Regenerate tools**
|
|
```bash
|
|
python -m tooluniverse.generate_tools
|
|
```
|
|
|
|
### Verification
|
|
|
|
```bash
|
|
$ python scripts/test_new_tools.py chembl -v
|
|
|
|
Testing ChEMBL_get_molecule_image (1 examples)...
|
|
✅ Ex 1: Passed
|
|
|
|
Tests Run: 64
|
|
Passed: 64 (100.0%)
|
|
Failed: 0
|
|
Schema Valid: 64
|
|
Schema Invalid: 0
|
|
```
|
|
|
|
**Direct test**:
|
|
```python
|
|
result = tu.run_one_function({
|
|
'name': 'ChEMBL_get_molecule_image',
|
|
'arguments': {'chembl_id': 'CHEMBL25', 'format': 'svg'}
|
|
})
|
|
|
|
print(json.dumps(result, indent=2))
|
|
```
|
|
|
|
**Output**:
|
|
```json
|
|
{
|
|
"status": "success",
|
|
"data": "Image data available at URL (Content-Type: image/svg+xml)",
|
|
"url": "https://www.ebi.ac.uk/chembl/api/data/image/CHEMBL25?format=svg",
|
|
"content_type": "image/svg+xml",
|
|
"image_size_bytes": 8476
|
|
}
|
|
```
|
|
|
|
### Key Learnings
|
|
|
|
1. **Content-Type detection is critical** - Check headers before parsing
|
|
2. **Schema describes data field only** - Not the full response structure
|
|
3. **jsonschema is permissive** - `{"type": "object"}` validates any dict
|
|
4. **Test both ways** - Use test script AND direct execution
|
|
5. **Binary data needs special handling** - Return metadata instead of content
|
|
|
|
---
|
|
|
|
## Example 2: Schema Validation Fix Pattern
|
|
|
|
### Problem
|
|
```
|
|
⚠️ Tool_search Ex 1: Schema Mismatch:
|
|
At root: 'result_string' is not of type 'object'
|
|
```
|
|
|
|
### Diagnosis
|
|
|
|
**Check what data field contains**:
|
|
```python
|
|
result = tu.run_one_function({'name': 'Tool_search', 'arguments': {...}})
|
|
print("Data type:", type(result.get('data'))) # <class 'str'>
|
|
print("Data value:", result.get('data')) # "Found 5 results"
|
|
```
|
|
|
|
**Check schema**:
|
|
```json
|
|
{
|
|
"return_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"results": {"type": "array"}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Problem**: Data is string, but schema expects object.
|
|
|
|
### Solution
|
|
|
|
**Option A: Change tool to return object**
|
|
```python
|
|
# In tool class
|
|
return {
|
|
"status": "success",
|
|
"data": {
|
|
"message": "Found 5 results",
|
|
"count": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
**Option B: Change schema to match string** (if tool design is correct)
|
|
```json
|
|
{
|
|
"return_schema": {
|
|
"type": "string",
|
|
"description": "Search result message"
|
|
}
|
|
}
|
|
```
|
|
|
|
Choose based on:
|
|
- Tool's intended design
|
|
- Consistency with similar tools
|
|
- User expectations
|
|
|
|
---
|
|
|
|
## Example 3: Endpoint URL Fix
|
|
|
|
### Problem
|
|
```
|
|
❌ Tool_get_data Ex 1: Failed - ChEMBL API returned HTTP 404
|
|
```
|
|
|
|
### Diagnosis
|
|
|
|
**Check endpoint configuration**:
|
|
```json
|
|
{
|
|
"name": "Tool_get_data",
|
|
"fields": {
|
|
"endpoint": "/data/{id}.json"
|
|
},
|
|
"test_examples": [
|
|
{"id": "12345"}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Test endpoint manually**:
|
|
```bash
|
|
curl https://api.example.com/data/12345.json
|
|
# Returns 404
|
|
```
|
|
|
|
**Find correct endpoint**:
|
|
```bash
|
|
curl https://api.example.com/records/12345.json
|
|
# Returns 200 ✓
|
|
```
|
|
|
|
### Solution
|
|
|
|
```json
|
|
{
|
|
"name": "Tool_get_data",
|
|
"fields": {
|
|
"endpoint": "/records/{id}.json" // ✅ Fixed
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Example 4: Parameter Schema Fix
|
|
|
|
### Problem
|
|
```
|
|
❌ Tool_search Ex 1: Failed - 400 Bad Request:
|
|
Parameter 'limit' must be integer
|
|
```
|
|
|
|
### Diagnosis
|
|
|
|
**Check parameter schema**:
|
|
```json
|
|
{
|
|
"parameter": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {"type": "string"},
|
|
"limit": {"type": "string"} // ❌ Wrong type
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Check test example**:
|
|
```json
|
|
{
|
|
"test_examples": [
|
|
{"query": "test", "limit": "10"} // String passed
|
|
]
|
|
}
|
|
```
|
|
|
|
### Solution
|
|
|
|
```json
|
|
{
|
|
"parameter": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {"type": "string"},
|
|
"limit": {
|
|
"type": "integer", // ✅ Fixed type
|
|
"default": 20,
|
|
"minimum": 1,
|
|
"maximum": 1000
|
|
}
|
|
}
|
|
},
|
|
"test_examples": [
|
|
{"query": "test", "limit": 10} // ✅ Integer
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Example 5: Complex Response Handling
|
|
|
|
### Problem
|
|
Tool returns nested structure but schema is too simple.
|
|
|
|
### Original Schema
|
|
```json
|
|
{
|
|
"return_schema": {
|
|
"type": "object"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Better Schema
|
|
```json
|
|
{
|
|
"return_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"results": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"score": {"type": "number"}
|
|
}
|
|
}
|
|
},
|
|
"total": {"type": "integer"},
|
|
"page": {"type": "integer"}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Trade-off**: More specific schema provides better validation but requires maintenance if API changes.
|
|
|
|
**Recommendation**: Use detailed schemas for critical tools, simple schemas for exploratory tools.
|
|
|
|
---
|
|
|
|
## Debugging Workflow Example
|
|
|
|
Complete debugging session for a failing tool:
|
|
|
|
```bash
|
|
# 1. Identify failure
|
|
$ python scripts/test_new_tools.py category -v
|
|
# Note: Tool_name failed with error X
|
|
|
|
# 2. Test directly with Python
|
|
$ python -c "
|
|
from tooluniverse import ToolUniverse
|
|
import json
|
|
|
|
tu = ToolUniverse()
|
|
tu.load_tools()
|
|
|
|
result = tu.run_one_function({
|
|
'name': 'Tool_name',
|
|
'arguments': {'param': 'value'}
|
|
})
|
|
|
|
print(json.dumps(result, indent=2))
|
|
"
|
|
|
|
# 3. Check API directly
|
|
$ curl -v https://api.example.com/endpoint
|
|
|
|
# 4. Compare with working tool
|
|
$ grep -A 20 "Working_tool" src/tooluniverse/data/category_tools.json
|
|
|
|
# 5. Implement fix
|
|
$ vim src/tooluniverse/category_tool.py
|
|
$ vim src/tooluniverse/data/category_tools.json
|
|
|
|
# 6. Regenerate
|
|
$ python -m tooluniverse.generate_tools
|
|
|
|
# 7. Verify
|
|
$ python scripts/test_new_tools.py category -v
|
|
|
|
# 8. Document
|
|
$ vim TOOL_FIX_SUMMARY.md
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Patterns
|
|
|
|
### Test Individual Tool
|
|
```python
|
|
from tooluniverse import ToolUniverse
|
|
import json
|
|
|
|
def test_tool(tool_name, arguments):
|
|
tu = ToolUniverse()
|
|
tu.load_tools()
|
|
|
|
result = tu.run_one_function({
|
|
'name': tool_name,
|
|
'arguments': arguments
|
|
})
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Testing: {tool_name}")
|
|
print('='*50)
|
|
print(json.dumps(result, indent=2))
|
|
|
|
# Check status
|
|
status = result.get('status')
|
|
print(f"\nStatus: {status}")
|
|
|
|
if status == 'error':
|
|
print(f"Error: {result.get('error')}")
|
|
return False
|
|
|
|
return True
|
|
|
|
# Usage
|
|
test_tool('ChEMBL_get_molecule_image', {
|
|
'chembl_id': 'CHEMBL25',
|
|
'format': 'svg'
|
|
})
|
|
```
|
|
|
|
### Test Schema Validation
|
|
```python
|
|
from jsonschema import validate, ValidationError
|
|
import json
|
|
|
|
def test_schema(tool_name):
|
|
# Load config
|
|
with open('src/tooluniverse/data/chembl_tools.json') as f:
|
|
tools = json.load(f)
|
|
tool = next(t for t in tools if t['name'] == tool_name)
|
|
schema = tool['return_schema']
|
|
|
|
# Run tool
|
|
tu = ToolUniverse()
|
|
tu.load_tools()
|
|
result = tu.run_one_function({
|
|
'name': tool_name,
|
|
'arguments': tool['test_examples'][0]
|
|
})
|
|
|
|
# Validate
|
|
data = result.get('data')
|
|
try:
|
|
validate(instance=data, schema=schema)
|
|
print(f"✅ Schema validation passed for {tool_name}")
|
|
return True
|
|
except ValidationError as e:
|
|
print(f"❌ Schema validation failed: {e.message}")
|
|
return False
|
|
```
|
|
|
|
### Test Multiple Examples
|
|
```python
|
|
def test_all_examples(tool_name):
|
|
# Load config
|
|
with open('src/tooluniverse/data/chembl_tools.json') as f:
|
|
tools = json.load(f)
|
|
tool = next(t for t in tools if t['name'] == tool_name)
|
|
|
|
tu = ToolUniverse()
|
|
tu.load_tools()
|
|
|
|
examples = tool.get('test_examples', [])
|
|
passed = 0
|
|
|
|
for i, example in enumerate(examples, 1):
|
|
try:
|
|
result = tu.run_one_function({
|
|
'name': tool_name,
|
|
'arguments': example
|
|
})
|
|
|
|
if result.get('status') == 'success':
|
|
print(f"✅ Example {i} passed")
|
|
passed += 1
|
|
else:
|
|
print(f"❌ Example {i} failed: {result.get('error')}")
|
|
except Exception as e:
|
|
print(f"🔥 Example {i} exception: {e}")
|
|
|
|
print(f"\nResults: {passed}/{len(examples)} passed")
|
|
return passed == len(examples)
|
|
```
|