fix: remove json schema generation from tool validation [LET-4509] (#4964)

* patch integration test

* create default user and org

* rm

* patch

* add testing

* remove validation for schemas from pydantic object

* add file

* patch tests

* fix more tests

* fix managers

* fix sdk test

* patch schema tests

* Comment out name in update

* patch test

* patch

* add another test
This commit is contained in:
Sarah Wooders
2025-09-28 18:22:21 -07:00
committed by Caren Thomas
parent 4bdf85b883
commit d0d36a4b07
9 changed files with 654 additions and 225 deletions

View File

@@ -96,6 +96,7 @@ from letta.server.server import SyncServer
from letta.services.block_manager import BlockManager
from letta.services.helpers.agent_manager_helper import calculate_base_tools, calculate_multi_agent_tools, validate_agent_exists_async
from letta.services.step_manager import FeedbackType
from letta.services.tool_schema_generator import generate_schema_for_tool_creation
from letta.settings import settings, tool_settings
from letta.utils import calculate_file_defaults_based_on_context_window
from tests.helpers.utils import comprehensive_agent_checks, validate_context_window_overview
@@ -689,23 +690,25 @@ async def test_list_tools_with_tool_types(server: SyncServer, default_user):
# create custom tools
custom_tool1 = PydanticTool(
name="calculator",
name="calculator_tool",
description="Math tool",
source_code=parse_source_code(calculator_tool),
source_type="python",
tool_type=ToolType.CUSTOM,
)
custom_tool1.json_schema = derive_openai_json_schema(source_code=custom_tool1.source_code, name=custom_tool1.name)
# Use generate_schema_for_tool_creation to generate schema
custom_tool1.json_schema = generate_schema_for_tool_creation(custom_tool1)
custom_tool1 = await server.tool_manager.create_or_update_tool_async(custom_tool1, actor=default_user)
custom_tool2 = PydanticTool(
name="weather",
# name="weather_tool",
description="Weather tool",
source_code=parse_source_code(weather_tool),
source_type="python",
tool_type=ToolType.CUSTOM,
)
custom_tool2.json_schema = derive_openai_json_schema(source_code=custom_tool2.source_code, name=custom_tool2.name)
# Use generate_schema_for_tool_creation to generate schema
custom_tool2.json_schema = generate_schema_for_tool_creation(custom_tool2)
custom_tool2 = await server.tool_manager.create_or_update_tool_async(custom_tool2, actor=default_user)
# test filtering by single tool type
@@ -744,13 +747,13 @@ async def test_list_tools_with_exclude_tool_types(server: SyncServer, default_us
return msg
special = PydanticTool(
name="special",
name="special_tool",
description="Special tool",
source_code=parse_source_code(special_tool),
source_type="python",
tool_type=ToolType.CUSTOM,
)
special.json_schema = derive_openai_json_schema(source_code=special.source_code, name=special.name)
special.json_schema = generate_schema_for_tool_creation(special)
special = await server.tool_manager.create_or_update_tool_async(special, actor=default_user)
# test excluding EXTERNAL_MCP (should get all tools since none are MCP)
@@ -796,15 +799,15 @@ async def test_list_tools_with_names(server: SyncServer, default_user):
return "gamma"
alpha = PydanticTool(name="alpha_tool", description="Alpha", source_code=parse_source_code(alpha_tool), source_type="python")
alpha.json_schema = derive_openai_json_schema(source_code=alpha.source_code, name=alpha.name)
alpha.json_schema = generate_schema_for_tool_creation(alpha)
alpha = await server.tool_manager.create_or_update_tool_async(alpha, actor=default_user)
beta = PydanticTool(name="beta_tool", description="Beta", source_code=parse_source_code(beta_tool), source_type="python")
beta.json_schema = derive_openai_json_schema(source_code=beta.source_code, name=beta.name)
beta.json_schema = generate_schema_for_tool_creation(beta)
beta = await server.tool_manager.create_or_update_tool_async(beta, actor=default_user)
gamma = PydanticTool(name="gamma_tool", description="Gamma", source_code=parse_source_code(gamma_tool), source_type="python")
gamma.json_schema = derive_openai_json_schema(source_code=gamma.source_code, name=gamma.name)
gamma.json_schema = generate_schema_for_tool_creation(gamma)
gamma = await server.tool_manager.create_or_update_tool_async(gamma, actor=default_user)
# test filtering by single name
@@ -852,15 +855,15 @@ async def test_list_tools_with_tool_ids(server: SyncServer, default_user):
return "3"
t1 = PydanticTool(name="tool1", description="First", source_code=parse_source_code(tool1), source_type="python")
t1.json_schema = derive_openai_json_schema(source_code=t1.source_code, name=t1.name)
t1.json_schema = generate_schema_for_tool_creation(t1)
t1 = await server.tool_manager.create_or_update_tool_async(t1, actor=default_user)
t2 = PydanticTool(name="tool2", description="Second", source_code=parse_source_code(tool2), source_type="python")
t2.json_schema = derive_openai_json_schema(source_code=t2.source_code, name=t2.name)
t2.json_schema = generate_schema_for_tool_creation(t2)
t2 = await server.tool_manager.create_or_update_tool_async(t2, actor=default_user)
t3 = PydanticTool(name="tool3", description="Third", source_code=parse_source_code(tool3), source_type="python")
t3.json_schema = derive_openai_json_schema(source_code=t3.source_code, name=t3.name)
t3.json_schema = generate_schema_for_tool_creation(t3)
t3 = await server.tool_manager.create_or_update_tool_async(t3, actor=default_user)
# test filtering by single id
@@ -910,19 +913,19 @@ async def test_list_tools_with_search(server: SyncServer, default_user):
calc_add = PydanticTool(
name="calculator_add", description="Add numbers", source_code=parse_source_code(calculator_add), source_type="python"
)
calc_add.json_schema = derive_openai_json_schema(source_code=calc_add.source_code, name=calc_add.name)
calc_add.json_schema = generate_schema_for_tool_creation(calc_add)
calc_add = await server.tool_manager.create_or_update_tool_async(calc_add, actor=default_user)
calc_sub = PydanticTool(
name="calculator_subtract", description="Subtract numbers", source_code=parse_source_code(calculator_subtract), source_type="python"
)
calc_sub.json_schema = derive_openai_json_schema(source_code=calc_sub.source_code, name=calc_sub.name)
calc_sub.json_schema = generate_schema_for_tool_creation(calc_sub)
calc_sub = await server.tool_manager.create_or_update_tool_async(calc_sub, actor=default_user)
weather = PydanticTool(
name="weather_forecast", description="Weather", source_code=parse_source_code(weather_forecast), source_type="python"
)
weather.json_schema = derive_openai_json_schema(source_code=weather.source_code, name=weather.name)
weather.json_schema = generate_schema_for_tool_creation(weather)
weather = await server.tool_manager.create_or_update_tool_async(weather, actor=default_user)
# test searching for "calculator" (should find both calculator tools)
@@ -965,7 +968,7 @@ async def test_list_tools_return_only_letta_tools(server: SyncServer, default_us
source_type="python",
tool_type=ToolType.CUSTOM,
)
custom.json_schema = derive_openai_json_schema(source_code=custom.source_code, name=custom.name)
custom.json_schema = generate_schema_for_tool_creation(custom)
custom = await server.tool_manager.create_or_update_tool_async(custom, actor=default_user)
# test without filter (should get custom tool + all letta tools)
@@ -1013,48 +1016,45 @@ async def test_list_tools_combined_filters(server: SyncServer, default_user):
return "weather"
calc1 = PydanticTool(
name="calculator_add", description="Add", source_code=parse_source_code(calc_add), source_type="python", tool_type=ToolType.CUSTOM
name="calc_add", description="Add", source_code=parse_source_code(calc_add), source_type="python", tool_type=ToolType.CUSTOM
)
calc1.json_schema = derive_openai_json_schema(source_code=calc1.source_code, name=calc1.name)
calc1.json_schema = generate_schema_for_tool_creation(calc1)
calc1 = await server.tool_manager.create_or_update_tool_async(calc1, actor=default_user)
calc2 = PydanticTool(
name="calculator_multiply",
description="Multiply",
source_code=parse_source_code(calc_multiply),
source_type="python",
tool_type=ToolType.CUSTOM,
)
calc2.json_schema = derive_openai_json_schema(source_code=calc2.source_code, name=calc2.name)
calc2.json_schema = generate_schema_for_tool_creation(calc2)
calc2 = await server.tool_manager.create_or_update_tool_async(calc2, actor=default_user)
weather = PydanticTool(
name="weather_current",
name="weather_tool",
description="Weather",
source_code=parse_source_code(weather_tool),
source_type="python",
tool_type=ToolType.CUSTOM,
)
weather.json_schema = derive_openai_json_schema(source_code=weather.source_code, name=weather.name)
weather.json_schema = generate_schema_for_tool_creation(weather)
weather = await server.tool_manager.create_or_update_tool_async(weather, actor=default_user)
# combine search with tool_types
tools = await server.tool_manager.list_tools_async(
actor=default_user, search="calculator", tool_types=[ToolType.CUSTOM.value], upsert_base_tools=False
actor=default_user, search="calc", tool_types=[ToolType.CUSTOM.value], upsert_base_tools=False
)
assert len(tools) == 2
assert all("calculator" in t.name and t.tool_type == ToolType.CUSTOM for t in tools)
assert all("calc" in t.name and t.tool_type == ToolType.CUSTOM for t in tools)
# combine names with tool_ids
tools = await server.tool_manager.list_tools_async(
actor=default_user, names=["calculator_add"], tool_ids=[calc1.id], upsert_base_tools=False
)
tools = await server.tool_manager.list_tools_async(actor=default_user, names=["calc_add"], tool_ids=[calc1.id], upsert_base_tools=False)
assert len(tools) == 1
assert tools[0].id == calc1.id
# combine search with exclude_tool_types
tools = await server.tool_manager.list_tools_async(
actor=default_user, search="calculator", exclude_tool_types=[ToolType.EXTERNAL_MCP.value], upsert_base_tools=False
actor=default_user, search="cal", exclude_tool_types=[ToolType.EXTERNAL_MCP.value], upsert_base_tools=False
)
assert len(tools) == 2
@@ -1091,13 +1091,13 @@ async def test_count_tools_async(server: SyncServer, default_user):
ta = PydanticTool(
name="tool_a", description="A", source_code=parse_source_code(tool_a), source_type="python", tool_type=ToolType.CUSTOM
)
ta.json_schema = derive_openai_json_schema(source_code=ta.source_code, name=ta.name)
ta.json_schema = generate_schema_for_tool_creation(ta)
ta = await server.tool_manager.create_or_update_tool_async(ta, actor=default_user)
tb = PydanticTool(
name="tool_b", description="B", source_code=parse_source_code(tool_b), source_type="python", tool_type=ToolType.CUSTOM
)
tb.json_schema = derive_openai_json_schema(source_code=tb.source_code, name=tb.name)
tb.json_schema = generate_schema_for_tool_creation(tb)
tb = await server.tool_manager.create_or_update_tool_async(tb, actor=default_user)
# upsert base tools to ensure we have Letta tools for counting
@@ -1167,8 +1167,8 @@ async def test_update_tool_by_id(server: SyncServer, print_tool, default_user):
assert updated_tool.tool_type == ToolType.EXTERNAL_MCP
#@pytest.mark.asyncio
#async def test_update_tool_source_code_refreshes_schema_and_name(server: SyncServer, print_tool, default_user):
# @pytest.mark.asyncio
# async def test_update_tool_source_code_refreshes_schema_and_name(server: SyncServer, print_tool, default_user):
# def counter_tool(counter: int):
# """
# Args:
@@ -1205,8 +1205,8 @@ async def test_update_tool_by_id(server: SyncServer, print_tool, default_user):
# assert updated_tool.tool_type == ToolType.CUSTOM
#@pytest.mark.asyncio
#async def test_update_tool_source_code_refreshes_schema_only(server: SyncServer, print_tool, default_user):
# @pytest.mark.asyncio
# async def test_update_tool_source_code_refreshes_schema_only(server: SyncServer, print_tool, default_user):
# def counter_tool(counter: int):
# """
# Args:
@@ -1701,7 +1701,7 @@ async def test_create_tool_with_pip_requirements(server: SyncServer, default_use
tool = PydanticTool(
description=description, tags=tags, source_code=source_code, source_type=source_type, metadata_=metadata, pip_requirements=pip_reqs
)
derived_json_schema = derive_openai_json_schema(source_code=tool.source_code, name=tool.name)
derived_json_schema = generate_schema_for_tool_creation(tool)
derived_name = derived_json_schema["name"]
tool.json_schema = derived_json_schema
tool.name = derived_name
@@ -1767,7 +1767,7 @@ async def test_update_tool_clear_pip_requirements(server: SyncServer, default_us
tool = PydanticTool(
description=description, tags=tags, source_code=source_code, source_type=source_type, metadata_=metadata, pip_requirements=pip_reqs
)
derived_json_schema = derive_openai_json_schema(source_code=tool.source_code, name=tool.name)
derived_json_schema = generate_schema_for_tool_creation(tool)
derived_name = derived_json_schema["name"]
tool.json_schema = derived_json_schema
tool.name = derived_name
@@ -1817,7 +1817,7 @@ async def test_pip_requirements_roundtrip(server: SyncServer, default_user, defa
tool = PydanticTool(
description=description, tags=tags, source_code=source_code, source_type=source_type, metadata_=metadata, pip_requirements=pip_reqs
)
derived_json_schema = derive_openai_json_schema(source_code=tool.source_code, name=tool.name)
derived_json_schema = generate_schema_for_tool_creation(tool)
derived_name = derived_json_schema["name"]
tool.json_schema = derived_json_schema
tool.name = derived_name
@@ -1859,3 +1859,360 @@ async def test_update_default_requires_approval(server: SyncServer, bash_tool, d
# Assertions
assert updated_tool.default_requires_approval == True
# ======================================================================================================================
# ToolManager Schema tests
# ======================================================================================================================
async def test_create_tool_with_json_schema(server: SyncServer, default_user, default_organization):
"""Test that json_schema is used when provided at creation."""
tool_manager = server.tool_manager
source_code = """
def test_function(arg1: str) -> str:
return arg1
"""
json_schema = {
"name": "test_function",
"description": "A test function",
"parameters": {"type": "object", "properties": {"arg1": {"type": "string"}}, "required": ["arg1"]},
}
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
json_schema=json_schema,
)
created_tool = await tool_manager.create_tool_async(tool, default_user)
assert created_tool.json_schema == json_schema
assert created_tool.name == "test_function"
assert created_tool.description == "A test function"
async def test_create_tool_with_args_json_schema(server: SyncServer, default_user, default_organization):
"""Test that schema is generated from args_json_schema at creation."""
tool_manager = server.tool_manager
source_code = """
def test_function(arg1: str, arg2: int) -> str:
'''This is a test function'''
return f"{arg1} {arg2}"
"""
args_json_schema = {
"type": "object",
"properties": {
"arg1": {"type": "string", "description": "First argument"},
"arg2": {"type": "integer", "description": "Second argument"},
},
"required": ["arg1", "arg2"],
}
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
args_json_schema=args_json_schema,
)
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
assert created_tool.json_schema is not None
assert created_tool.json_schema["name"] == "test_function"
assert created_tool.json_schema["description"] == "This is a test function"
assert "parameters" in created_tool.json_schema
assert created_tool.json_schema["parameters"]["properties"]["arg1"]["type"] == "string"
assert created_tool.json_schema["parameters"]["properties"]["arg2"]["type"] == "integer"
async def test_create_tool_with_docstring_no_schema(server: SyncServer, default_user, default_organization):
"""Test that schema is generated from docstring when no schema provided."""
tool_manager = server.tool_manager
source_code = """
def test_function(arg1: str, arg2: int = 5) -> str:
'''
This is a test function
Args:
arg1: First argument
arg2: Second argument
Returns:
A string result
'''
return f"{arg1} {arg2}"
"""
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
)
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
assert created_tool.json_schema is not None
assert created_tool.json_schema["name"] == "test_function"
assert "This is a test function" in created_tool.json_schema["description"]
assert "parameters" in created_tool.json_schema
async def test_create_tool_with_docstring_and_args_schema(server: SyncServer, default_user, default_organization):
"""Test that args_json_schema takes precedence over docstring."""
tool_manager = server.tool_manager
source_code = """
def test_function(old_arg: str) -> str:
'''Old docstring that should be overridden'''
return old_arg
"""
args_json_schema = {
"type": "object",
"properties": {"new_arg": {"type": "string", "description": "New argument from schema"}},
"required": ["new_arg"],
}
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
args_json_schema=args_json_schema,
)
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
assert created_tool.json_schema is not None
assert created_tool.json_schema["name"] == "test_function"
# The description should come from the docstring
assert created_tool.json_schema["description"] == "Old docstring that should be overridden"
# But parameters should come from args_json_schema
assert "new_arg" in created_tool.json_schema["parameters"]["properties"]
assert "old_arg" not in created_tool.json_schema["parameters"]["properties"]
async def test_error_no_docstring_or_schema(server: SyncServer, default_user, default_organization):
"""Test error when no docstring or schema provided (minimal function)."""
tool_manager = server.tool_manager
# Minimal function with no docstring - should still derive basic schema
source_code = """
def test_function():
pass
"""
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
)
with pytest.raises(ValueError) as exc_info:
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
async def test_error_on_create_tool_with_name_conflict(server: SyncServer, default_user, default_organization):
"""Test error when json_schema name conflicts with function name."""
tool_manager = server.tool_manager
source_code = """
def test_function(arg1: str) -> str:
return arg1
"""
# JSON schema with conflicting name
json_schema = {
"name": "different_name",
"description": "A test function",
"parameters": {"type": "object", "properties": {"arg1": {"type": "string"}}, "required": ["arg1"]},
}
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
json_schema=json_schema,
)
# This should succeed at creation - the tool name takes precedence
created_tool = await tool_manager.create_tool_async(tool, default_user)
assert created_tool.name == "test_function"
async def test_update_tool_with_json_schema(server: SyncServer, default_user, default_organization):
"""Test update with a new json_schema."""
tool_manager = server.tool_manager
# Create initial tool
source_code = """
def test_function() -> str:
return "hello"
"""
tool = PydanticTool(
name="test_update_json_schema",
tool_type=ToolType.CUSTOM,
source_code=source_code,
json_schema={"name": "test_update_json_schema", "description": "Original"},
)
created_tool = await tool_manager.create_tool_async(tool, default_user)
# Update with new json_schema
new_schema = {
"name": "test_update_json_schema",
"description": "Updated description",
"parameters": {"type": "object", "properties": {"new_arg": {"type": "string"}}, "required": ["new_arg"]},
}
update = ToolUpdate(json_schema=new_schema)
updated_tool = await tool_manager.update_tool_by_id_async(created_tool.id, update, default_user)
assert updated_tool.json_schema == new_schema
assert updated_tool.json_schema["description"] == "Updated description"
async def test_update_tool_with_args_json_schema(server: SyncServer, default_user, default_organization):
"""Test update with args_json_schema."""
tool_manager = server.tool_manager
# Create initial tool
source_code = """
def test_function() -> str:
'''Original function'''
return "hello"
"""
tool = PydanticTool(
name="test_function",
tool_type=ToolType.CUSTOM,
source_code=source_code,
)
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
# Update with args_json_schema
new_source_code = """
def test_function(new_arg: str) -> str:
'''Updated function'''
return new_arg
"""
args_json_schema = {
"type": "object",
"properties": {"new_arg": {"type": "string", "description": "New argument"}},
"required": ["new_arg"],
}
update = ToolUpdate(source_code=new_source_code, args_json_schema=args_json_schema)
updated_tool = await tool_manager.update_tool_by_id_async(created_tool.id, update, default_user)
assert updated_tool.json_schema is not None
assert updated_tool.json_schema["description"] == "Updated function"
assert "new_arg" in updated_tool.json_schema["parameters"]["properties"]
async def test_update_tool_with_no_schema(server: SyncServer, default_user, default_organization):
"""Test update with no schema changes."""
tool_manager = server.tool_manager
# Create initial tool
original_schema = {
"name": "test_no_schema_update",
"description": "Original description",
"parameters": {"type": "object", "properties": {}},
}
tool = PydanticTool(
name="test_no_schema_update",
tool_type=ToolType.CUSTOM,
source_code="def test_function(): pass",
json_schema=original_schema,
)
created_tool = await tool_manager.create_tool_async(tool, default_user)
# Update with only description (no schema change)
update = ToolUpdate(description="New description")
updated_tool = await tool_manager.update_tool_by_id_async(created_tool.id, update, default_user)
# Schema should remain unchanged
assert updated_tool.json_schema == original_schema
assert updated_tool.description == "New description"
async def test_update_tool_name(server: SyncServer, default_user, default_organization):
"""Test various name update scenarios."""
tool_manager = server.tool_manager
# Create initial tool
original_schema = {"name": "original_name", "description": "Test", "parameters": {"type": "object", "properties": {}}}
tool = PydanticTool(
name="original_name",
tool_type=ToolType.CUSTOM,
source_code="def original_name(): pass",
json_schema=original_schema,
)
created_tool = await tool_manager.create_or_update_tool_async(tool, default_user)
assert created_tool.name == "original_name"
assert created_tool.json_schema["name"] == "original_name"
matching_schema = {"name": "matched_name", "description": "Test", "parameters": {"type": "object", "properties": {}}}
update = ToolUpdate(json_schema=matching_schema)
updated_tool3 = await tool_manager.update_tool_by_id_async(created_tool.id, update, default_user)
assert updated_tool3.name == "matched_name"
assert updated_tool3.json_schema["name"] == "matched_name"
@pytest.mark.asyncio
async def test_list_tools_with_corrupted_tool(server: SyncServer, default_user, print_tool):
"""Test that list_tools still works even if there's a corrupted tool (missing json_schema) in the database."""
# First, verify we have a normal tool
tools = await server.tool_manager.list_tools_async(actor=default_user, upsert_base_tools=False)
initial_tool_count = len(tools)
assert any(t.id == print_tool.id for t in tools)
# Now insert a corrupted tool directly into the database (bypassing normal validation)
# This simulates a tool that somehow got corrupted in the database
from letta.orm.tool import Tool as ToolModel
async with db_registry.async_session() as session:
# Create a tool with no json_schema (corrupted state)
corrupted_tool = ToolModel(
id=f"tool-corrupted-{uuid.uuid4()}",
name="corrupted_tool",
description="This tool has no json_schema",
tool_type=ToolType.CUSTOM,
source_code="def corrupted_tool(): pass",
json_schema=None, # Explicitly set to None to simulate corruption
organization_id=default_user.organization_id,
created_by_id=default_user.id,
last_updated_by_id=default_user.id,
tags=["corrupted"],
)
session.add(corrupted_tool)
await session.commit()
corrupted_tool_id = corrupted_tool.id
# Now try to list tools - it should still work and not include the corrupted tool
# The corrupted tool should be automatically excluded from results
tools = await server.tool_manager.list_tools_async(actor=default_user, upsert_base_tools=False)
# Verify listing still works
assert len(tools) == initial_tool_count # Corrupted tool should not be in the results
assert any(t.id == print_tool.id for t in tools) # Normal tool should still be there
assert not any(t.id == corrupted_tool_id for t in tools) # Corrupted tool should not be there
# Verify the corrupted tool's name is not in the results
assert not any(t.name == "corrupted_tool" for t in tools)

View File

@@ -50,7 +50,8 @@ def run_server():
start_server(debug=True)
@pytest.fixture(scope="module")
# @pytest.fixture(scope="module")
@pytest.fixture(scope="function")
def client() -> LettaSDKClient:
# Get URL from environment or start server
server_url = os.getenv("LETTA_SERVER_URL", f"http://localhost:{SERVER_PORT}")
@@ -65,8 +66,8 @@ def client() -> LettaSDKClient:
yield client
@pytest.fixture(scope="module")
def server():
@pytest.fixture(scope="function")
async def server():
"""
Creates a SyncServer instance for testing.
@@ -74,7 +75,9 @@ def server():
"""
config = LettaConfig.load()
config.save()
return SyncServer()
server = SyncServer()
await server.init_async()
return server
@pytest.fixture(scope="function")

View File

@@ -277,42 +277,6 @@ def _run_composio_test(action_name, openai_model, structured_output):
return (action_name, False, str(e)) # Failure with error message
@pytest.mark.parametrize("openai_model", ["gpt-4o-mini"])
@pytest.mark.parametrize("structured_output", [True])
def test_composio_tool_schema_generation(openai_model: str, structured_output: bool):
"""Test that we can generate the schemas for some Composio tools."""
if not os.getenv("COMPOSIO_API_KEY"):
pytest.skip("COMPOSIO_API_KEY not set")
start_time = time.time()
action_names = [
"GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER", # Simple
"CAL_GET_AVAILABLE_SLOTS_INFO", # has an array arg, needs to be converted properly
"SALESFORCE_RETRIEVE_LEAD_BY_ID", # has an array arg, needs to be converted properly
"FIRECRAWL_SEARCH", # has an optional array arg, needs to be converted properly
]
# Create a pool of processes
pool = mp.Pool(processes=min(mp.cpu_count(), len(action_names)))
# Map the work to the pool
func = partial(_run_composio_test, openai_model=openai_model, structured_output=structured_output)
results = pool.map(func, action_names)
# Check results
for action_name, success, error_message in results:
print(f"Test for {action_name}: {'SUCCESS' if success else 'FAILED - ' + error_message}")
assert success, f"Test for {action_name} failed: {error_message}"
pool.close()
pool.join()
end_time = time.time()
print(f"Total execution time: {end_time - start_time:.2f} seconds")
# Helper function for pydantic args schema test
def _run_pydantic_args_test(filename, openai_model, structured_output):
"""Run a single pydantic args schema test case"""
@@ -342,6 +306,9 @@ def _run_pydantic_args_test(filename, openai_model, structured_output):
source_code=last_function_source,
args_json_schema=args_schema,
)
from letta.services.tool_schema_generator import generate_schema_for_tool_creation
tool.json_schema = generate_schema_for_tool_creation(tool)
schema = tool.json_schema
# We expect this to fail for all_python_complex with structured_output=True