feat: allow users to specify via query to stip messages [LET-7392] (#9411)
* feat: allow users to specify via query to stip messages * chore: regenerate API SDK and OpenAPI spec [LET-7392] 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Ari Webb <AriWebb@users.noreply.github.com> Co-Authored-By: Letta <noreply@letta.com> --------- Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: Ari Webb <AriWebb@users.noreply.github.com> Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
committed by
Caren Thomas
parent
6f51fa74be
commit
ca32311b9a
@@ -4694,6 +4694,18 @@
|
||||
"title": "Conversation Id"
|
||||
},
|
||||
"description": "Conversation ID to export. If provided, uses messages from this conversation instead of the agent's global message history."
|
||||
},
|
||||
{
|
||||
"name": "scrub_messages",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"description": "If True, excludes all messages from the export. Useful for sharing agent configs without conversation history.",
|
||||
"default": false,
|
||||
"title": "Scrub Messages"
|
||||
},
|
||||
"description": "If True, excludes all messages from the export. Useful for sharing agent configs without conversation history."
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -32743,6 +32755,12 @@
|
||||
],
|
||||
"title": "Conversation Id",
|
||||
"description": "Conversation ID to export. If provided, uses messages from this conversation instead of the agent's global message history."
|
||||
},
|
||||
"scrub_messages": {
|
||||
"type": "boolean",
|
||||
"title": "Scrub Messages",
|
||||
"description": "If True, excludes all messages from the export. Useful for sharing agent configs without conversation history.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@@ -297,6 +297,10 @@ async def export_agent(
|
||||
None,
|
||||
description="Conversation ID to export. If provided, uses messages from this conversation instead of the agent's global message history.",
|
||||
),
|
||||
scrub_messages: bool = Query(
|
||||
False,
|
||||
description="If True, excludes all messages from the export. Useful for sharing agent configs without conversation history.",
|
||||
),
|
||||
# do not remove, used to autogeneration of spec
|
||||
# TODO: Think of a better way to export AgentFileSchema
|
||||
spec: AgentFileSchema | None = None,
|
||||
@@ -308,7 +312,12 @@ async def export_agent(
|
||||
if use_legacy_format:
|
||||
raise HTTPException(status_code=400, detail="Legacy format is not supported")
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
agent_file_schema = await server.agent_serialization_manager.export(agent_ids=[agent_id], actor=actor, conversation_id=conversation_id)
|
||||
agent_file_schema = await server.agent_serialization_manager.export(
|
||||
agent_ids=[agent_id],
|
||||
actor=actor,
|
||||
conversation_id=conversation_id,
|
||||
scrub_messages=scrub_messages,
|
||||
)
|
||||
return agent_file_schema.model_dump()
|
||||
|
||||
|
||||
@@ -323,6 +332,10 @@ class ExportAgentRequest(BaseModel):
|
||||
None,
|
||||
description="Conversation ID to export. If provided, uses messages from this conversation instead of the agent's global message history.",
|
||||
)
|
||||
scrub_messages: bool = Field(
|
||||
default=False,
|
||||
description="If True, excludes all messages from the export. Useful for sharing agent configs without conversation history.",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{agent_id}/export", response_class=IndentedORJSONResponse, operation_id="export_agent_with_skills")
|
||||
@@ -343,12 +356,14 @@ async def export_agent_with_skills(
|
||||
# Use defaults if no request body provided
|
||||
skills = request.skills if request else []
|
||||
conversation_id = request.conversation_id if request else None
|
||||
scrub_messages = request.scrub_messages if request else False
|
||||
|
||||
agent_file_schema = await server.agent_serialization_manager.export(
|
||||
agent_ids=[agent_id],
|
||||
actor=actor,
|
||||
conversation_id=conversation_id,
|
||||
skills=skills,
|
||||
scrub_messages=scrub_messages,
|
||||
)
|
||||
return agent_file_schema.model_dump()
|
||||
|
||||
|
||||
@@ -189,7 +189,13 @@ class AgentSerializationManager:
|
||||
|
||||
return sources, files
|
||||
|
||||
async def _convert_agent_state_to_schema(self, agent_state: AgentState, actor: User, files_agents_cache: dict = None) -> AgentSchema:
|
||||
async def _convert_agent_state_to_schema(
|
||||
self,
|
||||
agent_state: AgentState,
|
||||
actor: User,
|
||||
files_agents_cache: dict = None,
|
||||
scrub_messages: bool = False,
|
||||
) -> AgentSchema:
|
||||
"""Convert AgentState to AgentSchema with ID remapping"""
|
||||
|
||||
agent_file_id = self._map_db_to_file_id(agent_state.id, AgentSchema.__id_prefix__)
|
||||
@@ -210,21 +216,27 @@ class AgentSerializationManager:
|
||||
)
|
||||
agent_schema.id = agent_file_id
|
||||
|
||||
# Ensure all in-context messages are present before ID remapping.
|
||||
# AgentSchema.from_agent_state fetches a limited slice (~50) and may exclude messages still
|
||||
# referenced by in_context_message_ids. Fetch any missing in-context messages by ID so remapping succeeds.
|
||||
existing_msg_ids = {m.id for m in (agent_schema.messages or [])}
|
||||
in_context_ids = agent_schema.in_context_message_ids or []
|
||||
missing_in_context_ids = [mid for mid in in_context_ids if mid not in existing_msg_ids]
|
||||
if missing_in_context_ids:
|
||||
missing_msgs = await self.message_manager.get_messages_by_ids_async(message_ids=missing_in_context_ids, actor=actor)
|
||||
fetched_ids = {m.id for m in missing_msgs}
|
||||
not_found = [mid for mid in missing_in_context_ids if mid not in fetched_ids]
|
||||
if not_found:
|
||||
# Surface a clear mapping error; handled upstream by the route/export wrapper.
|
||||
raise AgentExportIdMappingError(db_id=not_found[0], entity_type=MessageSchema.__id_prefix__)
|
||||
for msg in missing_msgs:
|
||||
agent_schema.messages.append(MessageSchema.from_message(msg))
|
||||
# Handle message scrubbing
|
||||
if not scrub_messages:
|
||||
# Ensure all in-context messages are present before ID remapping.
|
||||
# AgentSchema.from_agent_state fetches a limited slice (~50) and may exclude messages still
|
||||
# referenced by in_context_message_ids. Fetch any missing in-context messages by ID so remapping succeeds.
|
||||
existing_msg_ids = {m.id for m in (agent_schema.messages or [])}
|
||||
in_context_ids = agent_schema.in_context_message_ids or []
|
||||
missing_in_context_ids = [mid for mid in in_context_ids if mid not in existing_msg_ids]
|
||||
if missing_in_context_ids:
|
||||
missing_msgs = await self.message_manager.get_messages_by_ids_async(message_ids=missing_in_context_ids, actor=actor)
|
||||
fetched_ids = {m.id for m in missing_msgs}
|
||||
not_found = [mid for mid in missing_in_context_ids if mid not in fetched_ids]
|
||||
if not_found:
|
||||
# Surface a clear mapping error; handled upstream by the route/export wrapper.
|
||||
raise AgentExportIdMappingError(db_id=not_found[0], entity_type=MessageSchema.__id_prefix__)
|
||||
for msg in missing_msgs:
|
||||
agent_schema.messages.append(MessageSchema.from_message(msg))
|
||||
else:
|
||||
# Scrub all messages from export
|
||||
agent_schema.messages = []
|
||||
agent_schema.in_context_message_ids = []
|
||||
|
||||
# wipe the values of tool_exec_environment_variables (they contain secrets)
|
||||
agent_secrets = agent_schema.secrets or agent_schema.tool_exec_environment_variables
|
||||
@@ -232,17 +244,18 @@ class AgentSerializationManager:
|
||||
agent_schema.tool_exec_environment_variables = {key: "" for key in agent_secrets}
|
||||
agent_schema.secrets = {key: "" for key in agent_secrets}
|
||||
|
||||
if agent_schema.messages:
|
||||
for message in agent_schema.messages:
|
||||
message_file_id = self._map_db_to_file_id(message.id, MessageSchema.__id_prefix__)
|
||||
message.id = message_file_id
|
||||
message.agent_id = agent_file_id
|
||||
if not scrub_messages:
|
||||
if agent_schema.messages:
|
||||
for message in agent_schema.messages:
|
||||
message_file_id = self._map_db_to_file_id(message.id, MessageSchema.__id_prefix__)
|
||||
message.id = message_file_id
|
||||
message.agent_id = agent_file_id
|
||||
|
||||
if agent_schema.in_context_message_ids:
|
||||
agent_schema.in_context_message_ids = [
|
||||
self._map_db_to_file_id(message_id, MessageSchema.__id_prefix__, allow_new=False)
|
||||
for message_id in agent_schema.in_context_message_ids
|
||||
]
|
||||
if agent_schema.in_context_message_ids:
|
||||
agent_schema.in_context_message_ids = [
|
||||
self._map_db_to_file_id(message_id, MessageSchema.__id_prefix__, allow_new=False)
|
||||
for message_id in agent_schema.in_context_message_ids
|
||||
]
|
||||
|
||||
if agent_schema.tool_ids:
|
||||
agent_schema.tool_ids = [self._map_db_to_file_id(tool_id, ToolSchema.__id_prefix__) for tool_id in agent_schema.tool_ids]
|
||||
@@ -366,6 +379,7 @@ class AgentSerializationManager:
|
||||
actor: User,
|
||||
conversation_id: Optional[str] = None,
|
||||
skills: Optional[List[SkillSchema]] = None,
|
||||
scrub_messages: bool = False,
|
||||
) -> AgentFileSchema:
|
||||
"""
|
||||
Export agents and their related entities to AgentFileSchema format.
|
||||
@@ -376,6 +390,8 @@ class AgentSerializationManager:
|
||||
in-context message_ids instead of the agent's global message_ids.
|
||||
skills: Optional list of skills to include in the export. Skills are resolved
|
||||
client-side and passed as SkillSchema objects.
|
||||
scrub_messages: If True, excludes all messages from the export. Useful for
|
||||
sharing agent configs without conversation history.
|
||||
|
||||
Returns:
|
||||
AgentFileSchema with all related entities
|
||||
@@ -443,7 +459,12 @@ class AgentSerializationManager:
|
||||
|
||||
# Convert to schemas with ID remapping (reusing cached file-agent data)
|
||||
agent_schemas = [
|
||||
await self._convert_agent_state_to_schema(agent_state, actor=actor, files_agents_cache=files_agents_cache)
|
||||
await self._convert_agent_state_to_schema(
|
||||
agent_state,
|
||||
actor=actor,
|
||||
files_agents_cache=files_agents_cache,
|
||||
scrub_messages=scrub_messages,
|
||||
)
|
||||
for agent_state in agent_states
|
||||
]
|
||||
tool_schemas = [self._convert_tool_to_schema(tool) for tool in tool_set]
|
||||
|
||||
Reference in New Issue
Block a user