letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.12.0.dev20251009104148__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.
- letta/__init__.py +1 -1
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/letta_llm_request_adapter.py +0 -1
- letta/adapters/letta_llm_stream_adapter.py +7 -2
- letta/adapters/simple_llm_request_adapter.py +88 -0
- letta/adapters/simple_llm_stream_adapter.py +192 -0
- letta/agents/agent_loop.py +6 -0
- letta/agents/ephemeral_summary_agent.py +2 -1
- letta/agents/helpers.py +142 -6
- letta/agents/letta_agent.py +13 -33
- letta/agents/letta_agent_batch.py +2 -4
- letta/agents/letta_agent_v2.py +87 -77
- letta/agents/letta_agent_v3.py +927 -0
- letta/agents/voice_agent.py +2 -6
- letta/constants.py +8 -4
- letta/database_utils.py +161 -0
- letta/errors.py +40 -0
- letta/functions/function_sets/base.py +84 -4
- letta/functions/function_sets/multi_agent.py +0 -3
- letta/functions/schema_generator.py +113 -71
- letta/groups/dynamic_multi_agent.py +3 -2
- letta/groups/helpers.py +1 -2
- letta/groups/round_robin_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/groups/sleeptime_multi_agent_v3.py +17 -17
- letta/groups/supervisor_multi_agent.py +84 -80
- letta/helpers/converters.py +3 -0
- letta/helpers/message_helper.py +4 -0
- letta/helpers/tool_rule_solver.py +92 -5
- letta/interfaces/anthropic_streaming_interface.py +409 -0
- letta/interfaces/gemini_streaming_interface.py +296 -0
- letta/interfaces/openai_streaming_interface.py +752 -1
- letta/llm_api/anthropic_client.py +127 -16
- letta/llm_api/bedrock_client.py +4 -2
- letta/llm_api/deepseek_client.py +4 -1
- letta/llm_api/google_vertex_client.py +124 -42
- letta/llm_api/groq_client.py +4 -1
- letta/llm_api/llm_api_tools.py +11 -4
- letta/llm_api/llm_client_base.py +6 -2
- letta/llm_api/openai.py +32 -2
- letta/llm_api/openai_client.py +423 -18
- letta/llm_api/xai_client.py +4 -1
- letta/main.py +9 -5
- letta/memory.py +1 -0
- letta/orm/__init__.py +2 -1
- letta/orm/agent.py +10 -0
- letta/orm/block.py +7 -16
- letta/orm/blocks_agents.py +8 -2
- letta/orm/files_agents.py +2 -0
- letta/orm/job.py +7 -5
- letta/orm/mcp_oauth.py +1 -0
- letta/orm/message.py +21 -6
- letta/orm/organization.py +2 -0
- letta/orm/provider.py +6 -2
- letta/orm/run.py +71 -0
- letta/orm/run_metrics.py +82 -0
- letta/orm/sandbox_config.py +7 -1
- letta/orm/sqlalchemy_base.py +0 -306
- letta/orm/step.py +6 -5
- letta/orm/step_metrics.py +5 -5
- letta/otel/tracing.py +28 -3
- letta/plugins/defaults.py +4 -4
- letta/prompts/system_prompts/__init__.py +2 -0
- letta/prompts/system_prompts/letta_v1.py +25 -0
- letta/schemas/agent.py +3 -2
- letta/schemas/agent_file.py +9 -3
- letta/schemas/block.py +23 -10
- letta/schemas/enums.py +21 -2
- letta/schemas/job.py +17 -4
- letta/schemas/letta_message_content.py +71 -2
- letta/schemas/letta_stop_reason.py +5 -5
- letta/schemas/llm_config.py +53 -3
- letta/schemas/memory.py +1 -1
- letta/schemas/message.py +564 -117
- letta/schemas/openai/responses_request.py +64 -0
- letta/schemas/providers/__init__.py +2 -0
- letta/schemas/providers/anthropic.py +16 -0
- letta/schemas/providers/ollama.py +115 -33
- letta/schemas/providers/openrouter.py +52 -0
- letta/schemas/providers/vllm.py +2 -1
- letta/schemas/run.py +48 -42
- letta/schemas/run_metrics.py +21 -0
- letta/schemas/step.py +2 -2
- letta/schemas/step_metrics.py +1 -1
- letta/schemas/tool.py +15 -107
- letta/schemas/tool_rule.py +88 -5
- letta/serialize_schemas/marshmallow_agent.py +1 -0
- letta/server/db.py +79 -408
- letta/server/rest_api/app.py +61 -10
- letta/server/rest_api/dependencies.py +14 -0
- letta/server/rest_api/redis_stream_manager.py +19 -8
- letta/server/rest_api/routers/v1/agents.py +364 -292
- letta/server/rest_api/routers/v1/blocks.py +14 -20
- letta/server/rest_api/routers/v1/identities.py +45 -110
- letta/server/rest_api/routers/v1/internal_templates.py +21 -0
- letta/server/rest_api/routers/v1/jobs.py +23 -6
- letta/server/rest_api/routers/v1/messages.py +1 -1
- letta/server/rest_api/routers/v1/runs.py +149 -99
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
- letta/server/rest_api/routers/v1/tools.py +281 -594
- letta/server/rest_api/routers/v1/voice.py +1 -1
- letta/server/rest_api/streaming_response.py +29 -29
- letta/server/rest_api/utils.py +122 -64
- letta/server/server.py +160 -887
- letta/services/agent_manager.py +236 -919
- letta/services/agent_serialization_manager.py +16 -0
- letta/services/archive_manager.py +0 -100
- letta/services/block_manager.py +211 -168
- letta/services/context_window_calculator/token_counter.py +1 -1
- letta/services/file_manager.py +1 -1
- letta/services/files_agents_manager.py +24 -33
- letta/services/group_manager.py +0 -142
- letta/services/helpers/agent_manager_helper.py +7 -2
- letta/services/helpers/run_manager_helper.py +69 -0
- letta/services/job_manager.py +96 -411
- letta/services/lettuce/__init__.py +6 -0
- letta/services/lettuce/lettuce_client_base.py +86 -0
- letta/services/mcp_manager.py +38 -6
- letta/services/message_manager.py +165 -362
- letta/services/organization_manager.py +0 -36
- letta/services/passage_manager.py +0 -345
- letta/services/provider_manager.py +0 -80
- letta/services/run_manager.py +364 -0
- letta/services/sandbox_config_manager.py +0 -234
- letta/services/step_manager.py +62 -39
- letta/services/summarizer/summarizer.py +9 -7
- letta/services/telemetry_manager.py +0 -16
- letta/services/tool_executor/builtin_tool_executor.py +35 -0
- letta/services/tool_executor/core_tool_executor.py +397 -2
- letta/services/tool_executor/files_tool_executor.py +3 -3
- letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
- letta/services/tool_executor/tool_execution_manager.py +6 -8
- letta/services/tool_executor/tool_executor_base.py +3 -3
- letta/services/tool_manager.py +85 -339
- letta/services/tool_sandbox/base.py +24 -13
- letta/services/tool_sandbox/e2b_sandbox.py +16 -1
- letta/services/tool_schema_generator.py +123 -0
- letta/services/user_manager.py +0 -99
- letta/settings.py +20 -4
- letta/system.py +5 -1
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/METADATA +3 -5
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/RECORD +146 -135
- letta/agents/temporal/activities/__init__.py +0 -4
- letta/agents/temporal/activities/example_activity.py +0 -7
- letta/agents/temporal/activities/prepare_messages.py +0 -10
- letta/agents/temporal/temporal_agent_workflow.py +0 -56
- letta/agents/temporal/types.py +0 -25
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,16 @@
|
|
1
1
|
from typing import List, Optional
|
2
2
|
|
3
|
-
from letta.
|
3
|
+
from letta.agents.base_agent import BaseAgent
|
4
4
|
from letta.interface import AgentInterface
|
5
5
|
from letta.orm import User
|
6
|
+
from letta.schemas.agent import AgentState
|
6
7
|
from letta.schemas.letta_message_content import TextContent
|
7
8
|
from letta.schemas.message import Message, MessageCreate
|
8
9
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
9
10
|
from letta.schemas.usage import LettaUsageStatistics
|
10
11
|
|
11
12
|
|
12
|
-
class RoundRobinMultiAgent(
|
13
|
+
class RoundRobinMultiAgent(BaseAgent):
|
13
14
|
def __init__(
|
14
15
|
self,
|
15
16
|
interface: AgentInterface,
|
@@ -3,10 +3,11 @@ import threading
|
|
3
3
|
from datetime import datetime, timezone
|
4
4
|
from typing import List, Optional
|
5
5
|
|
6
|
-
from letta.
|
6
|
+
from letta.agents.base_agent import BaseAgent
|
7
7
|
from letta.groups.helpers import stringify_message
|
8
8
|
from letta.interface import AgentInterface
|
9
9
|
from letta.orm import User
|
10
|
+
from letta.schemas.agent import AgentState
|
10
11
|
from letta.schemas.enums import JobStatus
|
11
12
|
from letta.schemas.job import JobUpdate
|
12
13
|
from letta.schemas.letta_message_content import TextContent
|
@@ -19,7 +20,7 @@ from letta.services.job_manager import JobManager
|
|
19
20
|
from letta.services.message_manager import MessageManager
|
20
21
|
|
21
22
|
|
22
|
-
class SleeptimeMultiAgent(
|
23
|
+
class SleeptimeMultiAgent(BaseAgent):
|
23
24
|
def __init__(
|
24
25
|
self,
|
25
26
|
interface: AgentInterface,
|
@@ -268,7 +268,7 @@ class SleeptimeMultiAgentV2(BaseAgent):
|
|
268
268
|
prior_messages = []
|
269
269
|
if self.group.sleeptime_agent_frequency:
|
270
270
|
try:
|
271
|
-
prior_messages = await self.message_manager.
|
271
|
+
prior_messages = await self.message_manager.list_messages(
|
272
272
|
agent_id=foreground_agent_id,
|
273
273
|
actor=self.actor,
|
274
274
|
after=last_processed_message_id,
|
@@ -7,14 +7,14 @@ from letta.constants import DEFAULT_MAX_STEPS
|
|
7
7
|
from letta.groups.helpers import stringify_message
|
8
8
|
from letta.otel.tracing import trace_method
|
9
9
|
from letta.schemas.agent import AgentState
|
10
|
-
from letta.schemas.enums import JobStatus
|
10
|
+
from letta.schemas.enums import JobStatus, RunStatus
|
11
11
|
from letta.schemas.group import Group, ManagerType
|
12
12
|
from letta.schemas.job import JobUpdate
|
13
13
|
from letta.schemas.letta_message import MessageType
|
14
14
|
from letta.schemas.letta_message_content import TextContent
|
15
15
|
from letta.schemas.letta_response import LettaResponse
|
16
16
|
from letta.schemas.message import Message, MessageCreate
|
17
|
-
from letta.schemas.run import Run
|
17
|
+
from letta.schemas.run import Run, RunUpdate
|
18
18
|
from letta.schemas.user import User
|
19
19
|
from letta.services.group_manager import GroupManager
|
20
20
|
from letta.utils import safe_create_task
|
@@ -134,14 +134,14 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
134
134
|
use_assistant_message: bool = True,
|
135
135
|
) -> str:
|
136
136
|
run = Run(
|
137
|
-
|
138
|
-
status=
|
137
|
+
agent_id=sleeptime_agent_id,
|
138
|
+
status=RunStatus.created,
|
139
139
|
metadata={
|
140
|
-
"
|
140
|
+
"run_type": "sleeptime_agent_send_message_async", # is this right?
|
141
141
|
"agent_id": sleeptime_agent_id,
|
142
142
|
},
|
143
143
|
)
|
144
|
-
run = await self.
|
144
|
+
run = await self.run_manager.create_run(pydantic_run=run, actor=self.actor)
|
145
145
|
|
146
146
|
safe_create_task(
|
147
147
|
self._participant_agent_step(
|
@@ -167,15 +167,15 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
167
167
|
use_assistant_message: bool = True,
|
168
168
|
) -> LettaResponse:
|
169
169
|
try:
|
170
|
-
# Update
|
171
|
-
|
172
|
-
await self.
|
170
|
+
# Update run status
|
171
|
+
run_update = RunUpdate(status=RunStatus.running)
|
172
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
173
173
|
|
174
174
|
# Create conversation transcript
|
175
175
|
prior_messages = []
|
176
176
|
if self.group.sleeptime_agent_frequency:
|
177
177
|
try:
|
178
|
-
prior_messages = await self.message_manager.
|
178
|
+
prior_messages = await self.message_manager.list_messages(
|
179
179
|
agent_id=foreground_agent_id,
|
180
180
|
actor=self.actor,
|
181
181
|
after=last_processed_message_id,
|
@@ -212,22 +212,22 @@ class SleeptimeMultiAgentV3(LettaAgentV2):
|
|
212
212
|
use_assistant_message=use_assistant_message,
|
213
213
|
)
|
214
214
|
|
215
|
-
# Update
|
216
|
-
|
217
|
-
status=
|
215
|
+
# Update run status
|
216
|
+
run_update = RunUpdate(
|
217
|
+
status=RunStatus.completed,
|
218
218
|
completed_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
219
219
|
metadata={
|
220
220
|
"result": result.model_dump(mode="json"),
|
221
221
|
"agent_id": sleeptime_agent_state.id,
|
222
222
|
},
|
223
223
|
)
|
224
|
-
await self.
|
224
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
225
225
|
return result
|
226
226
|
except Exception as e:
|
227
|
-
|
228
|
-
status=
|
227
|
+
run_update = RunUpdate(
|
228
|
+
status=RunStatus.failed,
|
229
229
|
completed_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
230
230
|
metadata={"error": str(e)},
|
231
231
|
)
|
232
|
-
await self.
|
232
|
+
await self.run_manager.update_run_by_id_async(run_id=run_id, update=run_update, actor=self.actor)
|
233
233
|
raise
|
@@ -1,12 +1,13 @@
|
|
1
1
|
from typing import List, Optional
|
2
2
|
|
3
|
-
from letta.
|
3
|
+
from letta.agents.base_agent import BaseAgent
|
4
4
|
from letta.constants import DEFAULT_MESSAGE_TOOL
|
5
5
|
from letta.functions.function_sets.multi_agent import send_message_to_all_agents_in_group
|
6
6
|
from letta.functions.functions import parse_source_code
|
7
7
|
from letta.functions.schema_generator import generate_schema
|
8
8
|
from letta.interface import AgentInterface
|
9
9
|
from letta.orm import User
|
10
|
+
from letta.schemas.agent import AgentState
|
10
11
|
from letta.schemas.enums import ToolType
|
11
12
|
from letta.schemas.letta_message_content import TextContent
|
12
13
|
from letta.schemas.message import MessageCreate
|
@@ -17,7 +18,7 @@ from letta.services.agent_manager import AgentManager
|
|
17
18
|
from letta.services.tool_manager import ToolManager
|
18
19
|
|
19
20
|
|
20
|
-
class SupervisorMultiAgent(
|
21
|
+
class SupervisorMultiAgent(BaseAgent):
|
21
22
|
def __init__(
|
22
23
|
self,
|
23
24
|
interface: AgentInterface,
|
@@ -35,82 +36,85 @@ class SupervisorMultiAgent(Agent):
|
|
35
36
|
self.agent_manager = AgentManager()
|
36
37
|
self.tool_manager = ToolManager()
|
37
38
|
|
38
|
-
def step(
|
39
|
-
self,
|
40
|
-
input_messages: List[MessageCreate],
|
41
|
-
chaining: bool = True,
|
42
|
-
max_chaining_steps: Optional[int] = None,
|
43
|
-
put_inner_thoughts_first: bool = True,
|
44
|
-
assistant_message_tool_name: str = DEFAULT_MESSAGE_TOOL,
|
45
|
-
**kwargs,
|
46
|
-
) -> LettaUsageStatistics:
|
47
|
-
# Load settings
|
48
|
-
token_streaming = self.interface.streaming_mode if hasattr(self.interface, "streaming_mode") else False
|
49
|
-
metadata = self.interface.metadata if hasattr(self.interface, "metadata") else None
|
50
|
-
|
51
|
-
# Prepare supervisor agent
|
52
|
-
if self.tool_manager.get_tool_by_name(tool_name="send_message_to_all_agents_in_group", actor=self.user) is None:
|
53
|
-
multi_agent_tool = Tool(
|
54
|
-
name=send_message_to_all_agents_in_group.__name__,
|
55
|
-
description="",
|
56
|
-
source_type="python",
|
57
|
-
tags=[],
|
58
|
-
source_code=parse_source_code(send_message_to_all_agents_in_group),
|
59
|
-
json_schema=generate_schema(send_message_to_all_agents_in_group, None),
|
60
|
-
)
|
61
|
-
multi_agent_tool.tool_type = ToolType.LETTA_MULTI_AGENT_CORE
|
62
|
-
multi_agent_tool = self.tool_manager.create_or_update_tool(
|
63
|
-
pydantic_tool=multi_agent_tool,
|
64
|
-
actor=self.user,
|
65
|
-
)
|
66
|
-
self.agent_state = self.agent_manager.attach_tool(agent_id=self.agent_state.id, tool_id=multi_agent_tool.id, actor=self.user)
|
67
|
-
|
68
|
-
old_tool_rules = self.agent_state.tool_rules
|
69
|
-
self.agent_state.tool_rules = [
|
70
|
-
InitToolRule(
|
71
|
-
tool_name="send_message_to_all_agents_in_group",
|
72
|
-
),
|
73
|
-
TerminalToolRule(
|
74
|
-
tool_name=assistant_message_tool_name,
|
75
|
-
),
|
76
|
-
ChildToolRule(
|
77
|
-
tool_name="send_message_to_all_agents_in_group",
|
78
|
-
children=[assistant_message_tool_name],
|
79
|
-
),
|
80
|
-
]
|
81
|
-
|
82
|
-
# Prepare new messages
|
83
|
-
new_messages = []
|
84
|
-
for message in input_messages:
|
85
|
-
if isinstance(message.content, str):
|
86
|
-
message.content = [TextContent(text=message.content)]
|
87
|
-
message.group_id = self.group_id
|
88
|
-
new_messages.append(message)
|
89
|
-
|
90
|
-
try:
|
91
|
-
# Load supervisor agent
|
92
|
-
supervisor_agent = Agent(
|
93
|
-
agent_state=self.agent_state,
|
94
|
-
interface=self.interface,
|
95
|
-
user=self.user,
|
96
|
-
)
|
97
|
-
|
98
|
-
# Perform supervisor step
|
99
|
-
usage_stats = supervisor_agent.step(
|
100
|
-
input_messages=new_messages,
|
101
|
-
chaining=chaining,
|
102
|
-
max_chaining_steps=max_chaining_steps,
|
103
|
-
stream=token_streaming,
|
104
|
-
skip_verify=True,
|
105
|
-
metadata=metadata,
|
106
|
-
put_inner_thoughts_first=put_inner_thoughts_first,
|
107
|
-
)
|
108
|
-
except Exception as e:
|
109
|
-
raise e
|
110
|
-
finally:
|
111
|
-
self.interface.step_yield()
|
112
|
-
self.agent_state.tool_rules = old_tool_rules
|
113
|
-
|
114
|
-
self.interface.step_complete()
|
115
39
|
|
116
|
-
|
40
|
+
#
|
41
|
+
# def step(
|
42
|
+
# self,
|
43
|
+
# input_messages: List[MessageCreate],
|
44
|
+
# chaining: bool = True,
|
45
|
+
# max_chaining_steps: Optional[int] = None,
|
46
|
+
# put_inner_thoughts_first: bool = True,
|
47
|
+
# assistant_message_tool_name: str = DEFAULT_MESSAGE_TOOL,
|
48
|
+
# **kwargs,
|
49
|
+
# ) -> LettaUsageStatistics:
|
50
|
+
# # Load settings
|
51
|
+
# token_streaming = self.interface.streaming_mode if hasattr(self.interface, "streaming_mode") else False
|
52
|
+
# metadata = self.interface.metadata if hasattr(self.interface, "metadata") else None
|
53
|
+
#
|
54
|
+
# # Prepare supervisor agent
|
55
|
+
# if self.tool_manager.get_tool_by_name(tool_name="send_message_to_all_agents_in_group", actor=self.user) is None:
|
56
|
+
# multi_agent_tool = Tool(
|
57
|
+
# name=send_message_to_all_agents_in_group.__name__,
|
58
|
+
# description="",
|
59
|
+
# source_type="python",
|
60
|
+
# tags=[],
|
61
|
+
# source_code=parse_source_code(send_message_to_all_agents_in_group),
|
62
|
+
# json_schema=generate_schema(send_message_to_all_agents_in_group, None),
|
63
|
+
# )
|
64
|
+
# multi_agent_tool.tool_type = ToolType.LETTA_MULTI_AGENT_CORE
|
65
|
+
# multi_agent_tool = self.tool_manager.create_or_update_tool(
|
66
|
+
# pydantic_tool=multi_agent_tool,
|
67
|
+
# actor=self.user,
|
68
|
+
# )
|
69
|
+
# self.agent_state = self.agent_manager.attach_tool(agent_id=self.agent_state.id, tool_id=multi_agent_tool.id, actor=self.user)
|
70
|
+
#
|
71
|
+
# old_tool_rules = self.agent_state.tool_rules
|
72
|
+
# self.agent_state.tool_rules = [
|
73
|
+
# InitToolRule(
|
74
|
+
# tool_name="send_message_to_all_agents_in_group",
|
75
|
+
# ),
|
76
|
+
# TerminalToolRule(
|
77
|
+
# tool_name=assistant_message_tool_name,
|
78
|
+
# ),
|
79
|
+
# ChildToolRule(
|
80
|
+
# tool_name="send_message_to_all_agents_in_group",
|
81
|
+
# children=[assistant_message_tool_name],
|
82
|
+
# ),
|
83
|
+
# ]
|
84
|
+
#
|
85
|
+
# # Prepare new messages
|
86
|
+
# new_messages = []
|
87
|
+
# for message in input_messages:
|
88
|
+
# if isinstance(message.content, str):
|
89
|
+
# message.content = [TextContent(text=message.content)]
|
90
|
+
# message.group_id = self.group_id
|
91
|
+
# new_messages.append(message)
|
92
|
+
#
|
93
|
+
# try:
|
94
|
+
# # Load supervisor agent
|
95
|
+
# supervisor_agent = Agent(
|
96
|
+
# agent_state=self.agent_state,
|
97
|
+
# interface=self.interface,
|
98
|
+
# user=self.user,
|
99
|
+
# )
|
100
|
+
#
|
101
|
+
# # Perform supervisor step
|
102
|
+
# usage_stats = supervisor_agent.step(
|
103
|
+
# input_messages=new_messages,
|
104
|
+
# chaining=chaining,
|
105
|
+
# max_chaining_steps=max_chaining_steps,
|
106
|
+
# stream=token_streaming,
|
107
|
+
# skip_verify=True,
|
108
|
+
# metadata=metadata,
|
109
|
+
# put_inner_thoughts_first=put_inner_thoughts_first,
|
110
|
+
# )
|
111
|
+
# except Exception as e:
|
112
|
+
# raise e
|
113
|
+
# finally:
|
114
|
+
# self.interface.step_yield()
|
115
|
+
# self.agent_state.tool_rules = old_tool_rules
|
116
|
+
#
|
117
|
+
# self.interface.step_complete()
|
118
|
+
#
|
119
|
+
# return usage_stats
|
120
|
+
#
|
letta/helpers/converters.py
CHANGED
@@ -16,6 +16,7 @@ from letta.schemas.letta_message_content import (
|
|
16
16
|
OmittedReasoningContent,
|
17
17
|
ReasoningContent,
|
18
18
|
RedactedReasoningContent,
|
19
|
+
SummarizedReasoningContent,
|
19
20
|
TextContent,
|
20
21
|
ToolCallContent,
|
21
22
|
ToolReturnContent,
|
@@ -270,6 +271,8 @@ def deserialize_message_content(data: Optional[List[Dict]]) -> List[MessageConte
|
|
270
271
|
content = RedactedReasoningContent(**item)
|
271
272
|
elif content_type == MessageContentType.omitted_reasoning:
|
272
273
|
content = OmittedReasoningContent(**item)
|
274
|
+
elif content_type == MessageContentType.summarized_reasoning:
|
275
|
+
content = SummarizedReasoningContent(**item)
|
273
276
|
else:
|
274
277
|
# Skip invalid content
|
275
278
|
continue
|
letta/helpers/message_helper.py
CHANGED
@@ -13,6 +13,7 @@ def convert_message_creates_to_messages(
|
|
13
13
|
message_creates: list[MessageCreate],
|
14
14
|
agent_id: str,
|
15
15
|
timezone: str,
|
16
|
+
run_id: str,
|
16
17
|
wrap_user_message: bool = True,
|
17
18
|
wrap_system_message: bool = True,
|
18
19
|
) -> list[Message]:
|
@@ -21,6 +22,7 @@ def convert_message_creates_to_messages(
|
|
21
22
|
message_create=create,
|
22
23
|
agent_id=agent_id,
|
23
24
|
timezone=timezone,
|
25
|
+
run_id=run_id,
|
24
26
|
wrap_user_message=wrap_user_message,
|
25
27
|
wrap_system_message=wrap_system_message,
|
26
28
|
)
|
@@ -32,6 +34,7 @@ def _convert_message_create_to_message(
|
|
32
34
|
message_create: MessageCreate,
|
33
35
|
agent_id: str,
|
34
36
|
timezone: str,
|
37
|
+
run_id: str,
|
35
38
|
wrap_user_message: bool = True,
|
36
39
|
wrap_system_message: bool = True,
|
37
40
|
) -> Message:
|
@@ -81,4 +84,5 @@ def _convert_message_create_to_message(
|
|
81
84
|
sender_id=message_create.sender_id,
|
82
85
|
group_id=message_create.group_id,
|
83
86
|
batch_item_id=message_create.batch_item_id,
|
87
|
+
run_id=run_id,
|
84
88
|
)
|
@@ -50,6 +50,16 @@ class ToolRulesSolver(BaseModel):
|
|
50
50
|
)
|
51
51
|
tool_call_history: list[str] = Field(default_factory=list, description="History of tool calls, updated with each tool call.")
|
52
52
|
|
53
|
+
# Last-evaluated prefilled args cache (per step)
|
54
|
+
last_prefilled_args_by_tool: dict[str, dict] = Field(
|
55
|
+
default_factory=dict, description="Cached mapping of tool name to prefilled args from the last allowlist evaluation.", exclude=True
|
56
|
+
)
|
57
|
+
last_prefilled_args_provenance: dict[str, str] = Field(
|
58
|
+
default_factory=dict,
|
59
|
+
description="Cached mapping of tool name to a short description of which rule provided the prefilled args.",
|
60
|
+
exclude=True,
|
61
|
+
)
|
62
|
+
|
53
63
|
def __init__(self, tool_rules: list[ToolRule] | None = None, **kwargs):
|
54
64
|
super().__init__(tool_rules=tool_rules, **kwargs)
|
55
65
|
|
@@ -88,28 +98,78 @@ class ToolRulesSolver(BaseModel):
|
|
88
98
|
) -> list[ToolName]:
|
89
99
|
"""Get a list of tool names allowed based on the last tool called.
|
90
100
|
|
101
|
+
Side-effect: also caches any prefilled args provided by active rules into
|
102
|
+
`last_prefilled_args_by_tool` and `last_prefilled_args_provenance`.
|
103
|
+
|
91
104
|
The logic is as follows:
|
92
105
|
1. if there are no previous tool calls, and we have InitToolRules, those are the only options for the first tool call
|
93
106
|
2. else we take the intersection of the Parent/Child/Conditional/MaxSteps as the options
|
94
107
|
3. Continue/Terminal/RequiredBeforeExit rules are applied in the agent loop flow, not to restrict tools
|
95
108
|
"""
|
96
|
-
#
|
97
|
-
# TODO: -> Tool rules should probably be refactored to take in a set of tool names?
|
109
|
+
# Compute allowed tools first
|
98
110
|
if not self.tool_call_history and self.init_tool_rules:
|
99
|
-
|
111
|
+
allowed = [rule.tool_name for rule in self.init_tool_rules]
|
100
112
|
else:
|
101
113
|
valid_tool_sets = []
|
102
114
|
for rule in self.child_based_tool_rules + self.parent_tool_rules:
|
103
115
|
tools = rule.get_valid_tools(self.tool_call_history, available_tools, last_function_response)
|
104
116
|
valid_tool_sets.append(tools)
|
105
117
|
|
106
|
-
# Compute intersection of all valid tool sets
|
118
|
+
# Compute intersection of all valid tool sets and restrict to available_tools
|
107
119
|
final_allowed_tools = set.intersection(*valid_tool_sets) if valid_tool_sets else available_tools
|
120
|
+
final_allowed_tools = final_allowed_tools & available_tools
|
108
121
|
|
109
122
|
if error_on_empty and not final_allowed_tools:
|
110
123
|
raise ValueError("No valid tools found based on tool rules.")
|
111
124
|
|
112
|
-
|
125
|
+
allowed = list(final_allowed_tools)
|
126
|
+
|
127
|
+
# Build prefilled args cache for current allowed set
|
128
|
+
args_by_tool: dict[str, dict] = {}
|
129
|
+
provenance_by_tool: dict[str, str] = {}
|
130
|
+
|
131
|
+
def _store_args(tool_name: str, args: dict, provenance: str):
|
132
|
+
if not isinstance(args, dict) or len(args) == 0:
|
133
|
+
return
|
134
|
+
if tool_name not in args_by_tool:
|
135
|
+
args_by_tool[tool_name] = {}
|
136
|
+
args_by_tool[tool_name].update(args) # last-write-wins
|
137
|
+
provenance_by_tool[tool_name] = provenance
|
138
|
+
|
139
|
+
# For caching, restrict to actually available tools
|
140
|
+
allowed_set = set(allowed) & available_tools
|
141
|
+
|
142
|
+
last_tool = self.tool_call_history[-1] if self.tool_call_history else None
|
143
|
+
|
144
|
+
# Init rule args apply only at the beginning
|
145
|
+
if not self.tool_call_history and self.init_tool_rules:
|
146
|
+
for rule in self.init_tool_rules:
|
147
|
+
if hasattr(rule, "args") and getattr(rule, "args") and rule.tool_name in allowed_set:
|
148
|
+
_store_args(rule.tool_name, getattr(rule, "args"), f"InitToolRule({rule.tool_name})")
|
149
|
+
|
150
|
+
# ChildToolRule per-child args apply only when parent is the last tool
|
151
|
+
for rule in self.child_based_tool_rules:
|
152
|
+
if isinstance(rule, ChildToolRule) and last_tool == rule.tool_name:
|
153
|
+
child_map = rule.get_child_args_map()
|
154
|
+
for child_name, child_args in child_map.items():
|
155
|
+
if child_name in allowed_set:
|
156
|
+
_store_args(child_name, child_args, f"ChildToolRule({rule.tool_name}->{child_name})")
|
157
|
+
|
158
|
+
# Rule-level args for other rule types (future-proofing)
|
159
|
+
for rule in (
|
160
|
+
self.parent_tool_rules
|
161
|
+
+ self.continue_tool_rules
|
162
|
+
+ self.terminal_tool_rules
|
163
|
+
+ self.required_before_exit_tool_rules
|
164
|
+
+ self.requires_approval_tool_rules
|
165
|
+
):
|
166
|
+
if hasattr(rule, "args") and getattr(rule, "args") and getattr(rule, "tool_name", None) in allowed_set:
|
167
|
+
_store_args(rule.tool_name, getattr(rule, "args"), f"{rule.__class__.__name__}({rule.tool_name})")
|
168
|
+
|
169
|
+
self.last_prefilled_args_by_tool = args_by_tool
|
170
|
+
self.last_prefilled_args_provenance = provenance_by_tool
|
171
|
+
|
172
|
+
return allowed
|
113
173
|
|
114
174
|
def is_terminal_tool(self, tool_name: ToolName) -> bool:
|
115
175
|
"""Check if the tool is defined as a terminal tool in the terminal tool rules or required-before-exit tool rules."""
|
@@ -209,3 +269,30 @@ class ToolRulesSolver(BaseModel):
|
|
209
269
|
violated_rules.append(rendered_prompt)
|
210
270
|
|
211
271
|
return violated_rules
|
272
|
+
|
273
|
+
def should_force_tool_call(self) -> bool:
|
274
|
+
"""
|
275
|
+
Determine if a tool call should be forced (using 'required' instead of 'auto') based on active constrained tool rules.
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
bool: True if a constrained tool rule is currently active, False otherwise
|
279
|
+
"""
|
280
|
+
# check if we're at the start with init rules
|
281
|
+
if not self.tool_call_history and self.init_tool_rules:
|
282
|
+
return True
|
283
|
+
|
284
|
+
# check if any constrained rule is currently active
|
285
|
+
if self.tool_call_history:
|
286
|
+
last_tool = self.tool_call_history[-1]
|
287
|
+
|
288
|
+
# check child-based rules (ChildToolRule, ConditionalToolRule)
|
289
|
+
for rule in self.child_based_tool_rules:
|
290
|
+
if rule.requires_force_tool_call and rule.tool_name == last_tool:
|
291
|
+
return True
|
292
|
+
|
293
|
+
# check parent rules, `requires_force_tool_call` for safety in case this gets expanded
|
294
|
+
for rule in self.parent_tool_rules:
|
295
|
+
if rule.requires_force_tool_call and rule.tool_name == last_tool:
|
296
|
+
return True
|
297
|
+
|
298
|
+
return False
|