* adding log messages to track memory consumption * add logging to sources, folders, agentfiles, and mcp clients * memory logging for tool creation paths
142 lines
5.6 KiB
Python
142 lines
5.6 KiB
Python
"""Schema generation utilities for tool creation and updates."""
|
|
|
|
from typing import Optional
|
|
|
|
from letta.functions.ast_parsers import get_function_name_and_docstring
|
|
from letta.functions.functions import derive_openai_json_schema
|
|
from letta.functions.helpers import generate_model_from_args_json_schema
|
|
from letta.functions.schema_generator import generate_schema_from_args_schema_v2
|
|
from letta.log import get_logger
|
|
from letta.schemas.enums import ToolSourceType, ToolType
|
|
from letta.schemas.tool import Tool as PydanticTool
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def generate_schema_for_tool_creation(
|
|
tool: PydanticTool,
|
|
) -> Optional[dict]:
|
|
"""
|
|
Generate JSON schema for tool creation based on the provided parameters.
|
|
|
|
Args:
|
|
tool: The tool being created
|
|
|
|
Returns:
|
|
Generated JSON schema or None if not applicable
|
|
"""
|
|
# Only generate schema for custom tools
|
|
if tool.tool_type != ToolType.CUSTOM:
|
|
return None
|
|
|
|
# If json_schema is already provided, use it
|
|
if tool.json_schema:
|
|
return tool.json_schema
|
|
|
|
# Must have source code for custom tools
|
|
if not tool.source_code:
|
|
logger.error("Custom tool is missing source_code field")
|
|
raise ValueError("Custom tool is missing source_code field.")
|
|
|
|
source_code_size_kb = len(tool.source_code) / 1024
|
|
logger.info(f"Generating schema for tool '{tool.name}': source code {source_code_size_kb:.2f} KB")
|
|
|
|
# TypeScript tools
|
|
if tool.source_type == ToolSourceType.typescript:
|
|
try:
|
|
from letta.functions.typescript_parser import derive_typescript_json_schema
|
|
|
|
schema = derive_typescript_json_schema(source_code=tool.source_code)
|
|
import json
|
|
|
|
schema_size_kb = len(json.dumps(schema)) / 1024
|
|
logger.info(f"Generated TypeScript schema for '{tool.name}': {schema_size_kb:.2f} KB")
|
|
return schema
|
|
except Exception as e:
|
|
logger.warning(f"Failed to derive TypeScript json schema: {e}")
|
|
raise ValueError(f"Failed to derive TypeScript json schema: {e}")
|
|
|
|
# Python tools (default if not specified for backwards compatibility)
|
|
elif tool.source_type == ToolSourceType.python or tool.source_type is None:
|
|
# If args_json_schema is provided, use it to generate full schema
|
|
if tool.args_json_schema:
|
|
name, description = get_function_name_and_docstring(tool.source_code, tool.name)
|
|
args_schema = generate_model_from_args_json_schema(tool.args_json_schema)
|
|
schema = generate_schema_from_args_schema_v2(
|
|
args_schema=args_schema,
|
|
name=name,
|
|
description=description,
|
|
append_heartbeat=False,
|
|
)
|
|
import json
|
|
|
|
schema_size_kb = len(json.dumps(schema)) / 1024
|
|
logger.info(f"Generated Python schema from args_json for '{tool.name}': {schema_size_kb:.2f} KB")
|
|
return schema
|
|
# Otherwise, attempt to parse from docstring with best effort
|
|
else:
|
|
try:
|
|
schema = derive_openai_json_schema(source_code=tool.source_code)
|
|
import json
|
|
|
|
schema_size_kb = len(json.dumps(schema)) / 1024
|
|
logger.info(f"Generated Python schema from docstring for '{tool.name}': {schema_size_kb:.2f} KB")
|
|
return schema
|
|
except Exception as e:
|
|
logger.warning(f"Failed to derive json schema: {e}")
|
|
raise ValueError(f"Failed to derive json schema: {e}")
|
|
else:
|
|
# TODO: convert to explicit error
|
|
raise ValueError(f"Unknown tool source type: {tool.source_type}")
|
|
|
|
|
|
def generate_schema_for_tool_update(
|
|
current_tool: PydanticTool,
|
|
json_schema: Optional[dict] = None,
|
|
args_json_schema: Optional[dict] = None,
|
|
source_code: Optional[str] = None,
|
|
source_type: Optional[ToolSourceType] = None,
|
|
) -> Optional[dict]:
|
|
"""
|
|
Generate JSON schema for tool update based on the provided parameters.
|
|
|
|
Args:
|
|
current_tool: The current tool being updated
|
|
json_schema: Directly provided JSON schema (takes precedence)
|
|
args_json_schema: Schema for just the arguments
|
|
source_code: Updated source code (only used if explicitly updating source)
|
|
source_type: Source type for the tool
|
|
|
|
Returns:
|
|
Updated JSON schema or None if no update needed
|
|
"""
|
|
# Only handle custom tools
|
|
if current_tool.tool_type != ToolType.CUSTOM:
|
|
return None
|
|
|
|
# If json_schema is directly provided, use it
|
|
if json_schema is not None:
|
|
# If args_json_schema is also provided, that's an error
|
|
if args_json_schema is not None:
|
|
raise ValueError("Cannot provide both json_schema and args_json_schema in update")
|
|
return json_schema
|
|
|
|
# If args_json_schema is provided, generate full schema from it
|
|
if args_json_schema is not None:
|
|
# Use updated source_code if provided, otherwise use current
|
|
code_to_parse = source_code if source_code is not None else current_tool.source_code
|
|
if not code_to_parse:
|
|
raise ValueError("Source code required when updating with args_json_schema")
|
|
|
|
name, description = get_function_name_and_docstring(code_to_parse, current_tool.name)
|
|
args_schema = generate_model_from_args_json_schema(args_json_schema)
|
|
return generate_schema_from_args_schema_v2(
|
|
args_schema=args_schema,
|
|
name=name,
|
|
description=description,
|
|
append_heartbeat=False,
|
|
)
|
|
|
|
# Otherwise, no schema updates (don't parse docstring)
|
|
return None
|