letta-nightly 0.11.7.dev20251006104136__py3-none-any.whl → 0.11.7.dev20251008104128__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.
Files changed (145) hide show
  1. letta/adapters/letta_llm_adapter.py +1 -0
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +7 -2
  4. letta/adapters/simple_llm_request_adapter.py +88 -0
  5. letta/adapters/simple_llm_stream_adapter.py +192 -0
  6. letta/agents/agent_loop.py +6 -0
  7. letta/agents/ephemeral_summary_agent.py +2 -1
  8. letta/agents/helpers.py +142 -6
  9. letta/agents/letta_agent.py +13 -33
  10. letta/agents/letta_agent_batch.py +2 -4
  11. letta/agents/letta_agent_v2.py +87 -77
  12. letta/agents/letta_agent_v3.py +899 -0
  13. letta/agents/voice_agent.py +2 -6
  14. letta/constants.py +8 -4
  15. letta/errors.py +40 -0
  16. letta/functions/function_sets/base.py +84 -4
  17. letta/functions/function_sets/multi_agent.py +0 -3
  18. letta/functions/schema_generator.py +113 -71
  19. letta/groups/dynamic_multi_agent.py +3 -2
  20. letta/groups/helpers.py +1 -2
  21. letta/groups/round_robin_multi_agent.py +3 -2
  22. letta/groups/sleeptime_multi_agent.py +3 -2
  23. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  24. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  25. letta/groups/supervisor_multi_agent.py +84 -80
  26. letta/helpers/converters.py +3 -0
  27. letta/helpers/message_helper.py +4 -0
  28. letta/helpers/tool_rule_solver.py +92 -5
  29. letta/interfaces/anthropic_streaming_interface.py +409 -0
  30. letta/interfaces/gemini_streaming_interface.py +296 -0
  31. letta/interfaces/openai_streaming_interface.py +752 -1
  32. letta/llm_api/anthropic_client.py +126 -16
  33. letta/llm_api/bedrock_client.py +4 -2
  34. letta/llm_api/deepseek_client.py +4 -1
  35. letta/llm_api/google_vertex_client.py +123 -42
  36. letta/llm_api/groq_client.py +4 -1
  37. letta/llm_api/llm_api_tools.py +11 -4
  38. letta/llm_api/llm_client_base.py +6 -2
  39. letta/llm_api/openai.py +32 -2
  40. letta/llm_api/openai_client.py +423 -18
  41. letta/llm_api/xai_client.py +4 -1
  42. letta/main.py +9 -5
  43. letta/memory.py +1 -0
  44. letta/orm/__init__.py +1 -1
  45. letta/orm/agent.py +10 -0
  46. letta/orm/block.py +7 -16
  47. letta/orm/blocks_agents.py +8 -2
  48. letta/orm/files_agents.py +2 -0
  49. letta/orm/job.py +7 -5
  50. letta/orm/mcp_oauth.py +1 -0
  51. letta/orm/message.py +21 -6
  52. letta/orm/organization.py +2 -0
  53. letta/orm/provider.py +6 -2
  54. letta/orm/run.py +71 -0
  55. letta/orm/sandbox_config.py +7 -1
  56. letta/orm/sqlalchemy_base.py +0 -306
  57. letta/orm/step.py +6 -5
  58. letta/orm/step_metrics.py +5 -5
  59. letta/otel/tracing.py +28 -3
  60. letta/plugins/defaults.py +4 -4
  61. letta/prompts/system_prompts/__init__.py +2 -0
  62. letta/prompts/system_prompts/letta_v1.py +25 -0
  63. letta/schemas/agent.py +3 -2
  64. letta/schemas/agent_file.py +9 -3
  65. letta/schemas/block.py +23 -10
  66. letta/schemas/enums.py +21 -2
  67. letta/schemas/job.py +17 -4
  68. letta/schemas/letta_message_content.py +71 -2
  69. letta/schemas/letta_stop_reason.py +5 -5
  70. letta/schemas/llm_config.py +53 -3
  71. letta/schemas/memory.py +1 -1
  72. letta/schemas/message.py +504 -117
  73. letta/schemas/openai/responses_request.py +64 -0
  74. letta/schemas/providers/__init__.py +2 -0
  75. letta/schemas/providers/anthropic.py +16 -0
  76. letta/schemas/providers/ollama.py +115 -33
  77. letta/schemas/providers/openrouter.py +52 -0
  78. letta/schemas/providers/vllm.py +2 -1
  79. letta/schemas/run.py +48 -42
  80. letta/schemas/step.py +2 -2
  81. letta/schemas/step_metrics.py +1 -1
  82. letta/schemas/tool.py +15 -107
  83. letta/schemas/tool_rule.py +88 -5
  84. letta/serialize_schemas/marshmallow_agent.py +1 -0
  85. letta/server/db.py +86 -408
  86. letta/server/rest_api/app.py +61 -10
  87. letta/server/rest_api/dependencies.py +14 -0
  88. letta/server/rest_api/redis_stream_manager.py +19 -8
  89. letta/server/rest_api/routers/v1/agents.py +364 -292
  90. letta/server/rest_api/routers/v1/blocks.py +14 -20
  91. letta/server/rest_api/routers/v1/identities.py +45 -110
  92. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  93. letta/server/rest_api/routers/v1/jobs.py +23 -6
  94. letta/server/rest_api/routers/v1/messages.py +1 -1
  95. letta/server/rest_api/routers/v1/runs.py +126 -85
  96. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  97. letta/server/rest_api/routers/v1/tools.py +281 -594
  98. letta/server/rest_api/routers/v1/voice.py +1 -1
  99. letta/server/rest_api/streaming_response.py +29 -29
  100. letta/server/rest_api/utils.py +122 -64
  101. letta/server/server.py +160 -887
  102. letta/services/agent_manager.py +236 -919
  103. letta/services/agent_serialization_manager.py +16 -0
  104. letta/services/archive_manager.py +0 -100
  105. letta/services/block_manager.py +211 -168
  106. letta/services/file_manager.py +1 -1
  107. letta/services/files_agents_manager.py +24 -33
  108. letta/services/group_manager.py +0 -142
  109. letta/services/helpers/agent_manager_helper.py +7 -2
  110. letta/services/helpers/run_manager_helper.py +85 -0
  111. letta/services/job_manager.py +96 -411
  112. letta/services/lettuce/__init__.py +6 -0
  113. letta/services/lettuce/lettuce_client_base.py +86 -0
  114. letta/services/mcp_manager.py +38 -6
  115. letta/services/message_manager.py +165 -362
  116. letta/services/organization_manager.py +0 -36
  117. letta/services/passage_manager.py +0 -345
  118. letta/services/provider_manager.py +0 -80
  119. letta/services/run_manager.py +301 -0
  120. letta/services/sandbox_config_manager.py +0 -234
  121. letta/services/step_manager.py +62 -39
  122. letta/services/summarizer/summarizer.py +9 -7
  123. letta/services/telemetry_manager.py +0 -16
  124. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  125. letta/services/tool_executor/core_tool_executor.py +397 -2
  126. letta/services/tool_executor/files_tool_executor.py +3 -3
  127. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  128. letta/services/tool_executor/tool_execution_manager.py +6 -8
  129. letta/services/tool_executor/tool_executor_base.py +3 -3
  130. letta/services/tool_manager.py +85 -339
  131. letta/services/tool_sandbox/base.py +24 -13
  132. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  133. letta/services/tool_schema_generator.py +123 -0
  134. letta/services/user_manager.py +0 -99
  135. letta/settings.py +20 -4
  136. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
  137. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
  138. letta/agents/temporal/activities/__init__.py +0 -4
  139. letta/agents/temporal/activities/example_activity.py +0 -7
  140. letta/agents/temporal/activities/prepare_messages.py +0 -10
  141. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  142. letta/agents/temporal/types.py +0 -25
  143. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
  144. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
  145. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
letta/schemas/tool.py CHANGED
@@ -3,7 +3,6 @@ from typing import Any, Dict, List, Optional
3
3
  from pydantic import ConfigDict, Field, model_validator
4
4
 
5
5
  from letta.constants import (
6
- COMPOSIO_TOOL_TAG_NAME,
7
6
  FUNCTION_RETURN_CHAR_LIMIT,
8
7
  LETTA_BUILTIN_TOOL_MODULE_NAME,
9
8
  LETTA_CORE_TOOL_MODULE_NAME,
@@ -16,15 +15,9 @@ from letta.constants import (
16
15
  # MCP Tool metadata constants for schema health status
17
16
  MCP_TOOL_METADATA_SCHEMA_STATUS = f"{MCP_TOOL_TAG_NAME_PREFIX}:SCHEMA_STATUS"
18
17
  MCP_TOOL_METADATA_SCHEMA_WARNINGS = f"{MCP_TOOL_TAG_NAME_PREFIX}:SCHEMA_WARNINGS"
19
- from letta.functions.ast_parsers import get_function_name_and_docstring
20
- from letta.functions.composio_helpers import generate_composio_tool_wrapper
21
- from letta.functions.functions import derive_openai_json_schema, get_json_schema_from_module
18
+ from letta.functions.functions import get_json_schema_from_module
22
19
  from letta.functions.mcp_client.types import MCPTool
23
- from letta.functions.schema_generator import (
24
- generate_schema_from_args_schema_v2,
25
- generate_tool_schema_for_composio,
26
- generate_tool_schema_for_mcp,
27
- )
20
+ from letta.functions.schema_generator import generate_tool_schema_for_mcp
28
21
  from letta.log import get_logger
29
22
  from letta.schemas.enums import ToolSourceType, ToolType
30
23
  from letta.schemas.letta_base import LettaBase
@@ -80,46 +73,19 @@ class Tool(BaseTool):
80
73
  def refresh_source_code_and_json_schema(self):
81
74
  """
82
75
  Refresh name, description, source_code, and json_schema.
83
- """
84
- from letta.functions.helpers import generate_model_from_args_json_schema
85
-
86
- if self.tool_type == ToolType.CUSTOM and not self.json_schema:
87
- # attempt various fallbacks to get the JSON schema
88
- if not self.source_code:
89
- logger.error("Custom tool with id=%s is missing source_code field", self.id)
90
- raise ValueError(f"Custom tool with id={self.id} is missing source_code field.")
91
-
92
- if self.source_type == ToolSourceType.typescript:
93
- # TypeScript tools don't support args_json_schema, only direct schema generation
94
- if not self.json_schema:
95
- try:
96
- from letta.functions.typescript_parser import derive_typescript_json_schema
97
76
 
98
- self.json_schema = derive_typescript_json_schema(source_code=self.source_code)
99
- except Exception as e:
100
- logger.error("Failed to derive TypeScript json schema for tool with id=%s name=%s: %s", self.id, self.name, e)
101
- elif (
102
- self.source_type == ToolSourceType.python or self.source_type is None
103
- ): # default to python if not provided for backwards compatability
104
- # Python tool handling
105
- # Always derive json_schema for freshest possible json_schema
106
- if self.args_json_schema is not None:
107
- name, description = get_function_name_and_docstring(self.source_code, self.name)
108
- args_schema = generate_model_from_args_json_schema(self.args_json_schema)
109
- self.json_schema = generate_schema_from_args_schema_v2(
110
- args_schema=args_schema,
111
- name=name,
112
- description=description,
113
- append_heartbeat=False,
114
- )
115
- else: # elif not self.json_schema: # TODO: JSON schema is not being derived correctly the first time?
116
- # If there's not a json_schema provided, then we need to re-derive
117
- try:
118
- self.json_schema = derive_openai_json_schema(source_code=self.source_code)
119
- except Exception as e:
120
- logger.error("Failed to derive json schema for tool with id=%s name=%s: %s", self.id, self.name, e)
121
- else:
122
- raise ValueError(f"Unknown tool source type: {self.source_type}")
77
+ Note: Schema generation for custom tools is now handled at creation/update time in ToolManager.
78
+ This method only handles built-in Letta tools.
79
+ """
80
+ if self.tool_type == ToolType.CUSTOM:
81
+ # Custom tools should already have their schema set during creation/update
82
+ # No schema generation happens here anymore
83
+ if not self.json_schema:
84
+ logger.warning(
85
+ "Custom tool with id=%s name=%s is missing json_schema. Schema should be set during creation/update.",
86
+ self.id,
87
+ self.name,
88
+ )
123
89
  elif self.tool_type in {ToolType.LETTA_CORE, ToolType.LETTA_MEMORY_CORE, ToolType.LETTA_SLEEPTIME_CORE}:
124
90
  # If it's letta core tool, we generate the json_schema on the fly here
125
91
  self.json_schema = get_json_schema_from_module(module_name=LETTA_CORE_TOOL_MODULE_NAME, function_name=self.name)
@@ -135,26 +101,6 @@ class Tool(BaseTool):
135
101
  elif self.tool_type in {ToolType.LETTA_FILES_CORE}:
136
102
  # If it's letta files tool, we generate the json_schema on the fly here
137
103
  self.json_schema = get_json_schema_from_module(module_name=LETTA_FILES_TOOL_MODULE_NAME, function_name=self.name)
138
- elif self.tool_type in {ToolType.EXTERNAL_COMPOSIO}:
139
- # Composio schemas handled separately
140
- pass
141
-
142
- # At this point, we need to validate that at least json_schema is populated
143
- if not self.json_schema:
144
- logger.error("Tool with id=%s name=%s tool_type=%s is missing a json_schema", self.id, self.name, self.tool_type)
145
- raise ValueError(f"Tool with id={self.id} name={self.name} tool_type={self.tool_type} is missing a json_schema.")
146
-
147
- # Derive name from the JSON schema if not provided
148
- if not self.name:
149
- # TODO: This in theory could error, but name should always be on json_schema
150
- # TODO: Make JSON schema a typed pydantic object
151
- self.name = self.json_schema.get("name")
152
-
153
- # Derive description from the JSON schema if not provided
154
- if not self.description:
155
- # TODO: This in theory could error, but description should always be on json_schema
156
- # TODO: Make JSON schema a typed pydantic object
157
- self.description = self.json_schema.get("description")
158
104
 
159
105
  return self
160
106
 
@@ -199,45 +145,6 @@ class ToolCreate(LettaBase):
199
145
  json_schema=json_schema,
200
146
  )
201
147
 
202
- @classmethod
203
- def from_composio(cls, action_name: str) -> "ToolCreate":
204
- """
205
- Class method to create an instance of Letta-compatible Composio Tool.
206
- Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio
207
-
208
- This function will error if we find more than one tool, or 0 tools.
209
-
210
- Args:
211
- action_name str: A action name to filter tools by.
212
- Returns:
213
- Tool: A Letta Tool initialized with attributes derived from the Composio tool.
214
- """
215
- from composio import ComposioToolSet, LogLevel
216
-
217
- composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR, lock=False)
218
- composio_action_schemas = composio_toolset.get_action_schemas(actions=[action_name], check_connected_accounts=False)
219
-
220
- assert len(composio_action_schemas) > 0, "User supplied parameters do not match any Composio tools"
221
- assert len(composio_action_schemas) == 1, (
222
- f"User supplied parameters match too many Composio tools; {len(composio_action_schemas)} > 1"
223
- )
224
-
225
- composio_action_schema = composio_action_schemas[0]
226
-
227
- description = composio_action_schema.description
228
- source_type = "python"
229
- tags = [COMPOSIO_TOOL_TAG_NAME]
230
- wrapper_func_name, wrapper_function_str = generate_composio_tool_wrapper(action_name)
231
- json_schema = generate_tool_schema_for_composio(composio_action_schema.parameters, name=wrapper_func_name, description=description)
232
-
233
- return cls(
234
- description=description,
235
- source_type=source_type,
236
- tags=tags,
237
- source_code=wrapper_function_str,
238
- json_schema=json_schema,
239
- )
240
-
241
148
 
242
149
  class ToolUpdate(LettaBase):
243
150
  description: Optional[str] = Field(None, description="The description of the tool.")
@@ -253,6 +160,7 @@ class ToolUpdate(LettaBase):
253
160
  npm_requirements: list[NpmRequirement] | None = Field(None, description="Optional list of npm packages required by this tool.")
254
161
  metadata_: Optional[Dict[str, Any]] = Field(None, description="A dictionary of additional metadata for the tool.")
255
162
  default_requires_approval: Optional[bool] = Field(None, description="Whether or not to require approval before executing this tool.")
163
+ # name: Optional[str] = Field(None, description="The name of the tool (must match the JSON schema name and source code function name).")
256
164
 
257
165
  model_config = ConfigDict(extra="ignore") # Allows extra fields without validation errors
258
166
  # TODO: Remove this, and clean usage of ToolUpdate everywhere else
@@ -2,7 +2,7 @@ import json
2
2
  import logging
3
3
  from typing import Annotated, Any, Dict, List, Literal, Optional, Set, Union
4
4
 
5
- from pydantic import Field, field_validator
5
+ from pydantic import BaseModel, Field, field_validator, model_validator
6
6
 
7
7
  from letta.schemas.enums import ToolRuleType
8
8
  from letta.schemas.letta_base import LettaBase
@@ -36,6 +36,30 @@ class BaseToolRule(LettaBase):
36
36
  """Default implementation returns None. Subclasses provide optimized strings."""
37
37
  return None
38
38
 
39
+ @property
40
+ def requires_force_tool_call(self) -> bool:
41
+ """Whether this tool rule requires forcing a tool call in the LLM request when active.
42
+ When True, the LLM must use a tool; when False, tool use is optional.
43
+ Default is False for most rules."""
44
+ return False
45
+
46
+
47
+ class ToolCallNode(BaseModel):
48
+ """Typed child override for prefilled arguments.
49
+
50
+ When used in a ChildToolRule, if this child is selected next, its `args` will be
51
+ applied as prefilled arguments (overriding overlapping LLM-provided values).
52
+ """
53
+
54
+ name: str = Field(..., description="The name of the child tool to invoke next.")
55
+ args: Optional[Dict[str, Any]] = Field(
56
+ default=None,
57
+ description=(
58
+ "Optional prefilled arguments for this child tool. Keys must match the tool's parameter names and values "
59
+ "must satisfy the tool's JSON schema. Supports partial prefill; non-overlapping parameters are left to the model."
60
+ ),
61
+ )
62
+
39
63
 
40
64
  class ChildToolRule(BaseToolRule):
41
65
  """
@@ -43,30 +67,66 @@ class ChildToolRule(BaseToolRule):
43
67
  """
44
68
 
45
69
  type: Literal[ToolRuleType.constrain_child_tools] = ToolRuleType.constrain_child_tools
70
+
46
71
  children: List[str] = Field(..., description="The children tools that can be invoked.")
72
+ child_arg_nodes: Optional[List[ToolCallNode]] = Field(
73
+ default=None,
74
+ description=("Optional list of typed child argument overrides. Each node must reference a child in 'children'."),
75
+ )
47
76
  prompt_template: Optional[str] = Field(
48
77
  default=None,
49
78
  description="Optional template string (ignored).",
50
79
  )
51
80
 
81
+ @property
82
+ def requires_force_tool_call(self) -> bool:
83
+ """Child tool rules require forcing tool calls."""
84
+ return True
85
+
52
86
  def __hash__(self):
53
87
  """Hash including children list (sorted for consistency)."""
54
- return hash((self.tool_name, self.type, tuple(sorted(self.children))))
88
+ # Hash on child names only for stability
89
+ child_names = tuple(sorted(self.children))
90
+ return hash((self.tool_name, self.type, child_names))
55
91
 
56
92
  def __eq__(self, other):
57
93
  """Equality including children list."""
58
94
  if not isinstance(other, ChildToolRule):
59
95
  return False
60
- return self.tool_name == other.tool_name and self.type == other.type and sorted(self.children) == sorted(other.children)
96
+ self_names = sorted(self.children)
97
+ other_names = sorted(other.children)
98
+ return self.tool_name == other.tool_name and self.type == other.type and self_names == other_names
99
+
100
+ def get_child_names(self) -> List[str]:
101
+ return list(self.children)
102
+
103
+ def get_child_args_map(self) -> Dict[str, Dict[str, Any]]:
104
+ mapping: Dict[str, Dict[str, Any]] = {}
105
+ if self.child_arg_nodes:
106
+ for node in self.child_arg_nodes:
107
+ if node.args:
108
+ mapping[node.name] = dict(node.args)
109
+ return mapping
61
110
 
62
111
  def get_valid_tools(self, tool_call_history: List[str], available_tools: Set[str], last_function_response: Optional[str]) -> Set[str]:
63
112
  last_tool = tool_call_history[-1] if tool_call_history else None
64
- return set(self.children) if last_tool == self.tool_name else available_tools
113
+ return set(self.get_child_names()) if last_tool == self.tool_name else available_tools
65
114
 
66
115
  def render_prompt(self) -> str | None:
67
- children_str = ", ".join(self.children)
116
+ children_str = ", ".join(self.get_child_names())
68
117
  return f"<tool_rule>\nAfter using {self.tool_name}, you must use one of these tools: {children_str}\n</tool_rule>"
69
118
 
119
+ @model_validator(mode="after")
120
+ def validate_child_arg_nodes(self):
121
+ if self.child_arg_nodes:
122
+ child_set = set(self.children)
123
+ for node in self.child_arg_nodes:
124
+ if node.name not in child_set:
125
+ raise ValueError(
126
+ f"ChildToolRule child_arg_nodes contains a node for '{node.name}' which is not in children {self.children}."
127
+ )
128
+ return self
129
+
70
130
 
71
131
  class ParentToolRule(BaseToolRule):
72
132
  """
@@ -77,6 +137,11 @@ class ParentToolRule(BaseToolRule):
77
137
  children: List[str] = Field(..., description="The children tools that can be invoked.")
78
138
  prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
79
139
 
140
+ @property
141
+ def requires_force_tool_call(self) -> bool:
142
+ """Parent tool rules require forcing tool calls."""
143
+ return True
144
+
80
145
  def __hash__(self):
81
146
  """Hash including children list (sorted for consistency)."""
82
147
  return hash((self.tool_name, self.type, tuple(sorted(self.children))))
@@ -107,6 +172,11 @@ class ConditionalToolRule(BaseToolRule):
107
172
  require_output_mapping: bool = Field(default=False, description="Whether to throw an error when output doesn't match any case")
108
173
  prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
109
174
 
175
+ @property
176
+ def requires_force_tool_call(self) -> bool:
177
+ """Conditional tool rules require forcing tool calls."""
178
+ return True
179
+
110
180
  def __hash__(self):
111
181
  """Hash including all configuration fields."""
112
182
  # convert dict to sorted tuple of items for consistent hashing
@@ -187,6 +257,19 @@ class InitToolRule(BaseToolRule):
187
257
  """
188
258
 
189
259
  type: Literal[ToolRuleType.run_first] = ToolRuleType.run_first
260
+ args: Optional[Dict[str, Any]] = Field(
261
+ default=None,
262
+ description=(
263
+ "Optional prefilled arguments for this tool. When present, these values will override any LLM-provided "
264
+ "arguments with the same keys during invocation. Keys must match the tool's parameter names and values "
265
+ "must satisfy the tool's JSON schema. Supports partial prefill; non-overlapping parameters are left to the model."
266
+ ),
267
+ )
268
+
269
+ @property
270
+ def requires_force_tool_call(self) -> bool:
271
+ """Initial tool rules require forcing tool calls."""
272
+ return True
190
273
 
191
274
 
192
275
  class TerminalToolRule(BaseToolRule):
@@ -241,4 +241,5 @@ class MarshmallowAgentSchema(BaseSchema):
241
241
  "groups",
242
242
  "batch_items",
243
243
  "organization",
244
+ "runs", # Exclude the runs relationship (agents_runs association table)
244
245
  )