letta-nightly 0.7.8.dev20250502104219__py3-none-any.whl → 0.7.9.dev20250503104103__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 +2 -2
- letta/agents/helpers.py +58 -1
- letta/agents/letta_agent.py +13 -3
- letta/agents/letta_agent_batch.py +33 -17
- letta/agents/voice_agent.py +1 -2
- letta/agents/voice_sleeptime_agent.py +75 -320
- letta/functions/function_sets/multi_agent.py +1 -1
- letta/functions/function_sets/voice.py +20 -32
- letta/functions/helpers.py +7 -7
- letta/helpers/datetime_helpers.py +6 -0
- letta/helpers/message_helper.py +19 -18
- letta/jobs/scheduler.py +233 -49
- letta/llm_api/google_ai_client.py +13 -4
- letta/llm_api/google_vertex_client.py +5 -1
- letta/llm_api/openai.py +10 -2
- letta/llm_api/openai_client.py +14 -2
- letta/orm/message.py +4 -0
- letta/prompts/system/voice_sleeptime.txt +2 -3
- letta/schemas/letta_message.py +1 -0
- letta/schemas/letta_request.py +8 -1
- letta/schemas/letta_response.py +5 -0
- letta/schemas/llm_batch_job.py +6 -4
- letta/schemas/llm_config.py +9 -0
- letta/schemas/message.py +23 -2
- letta/schemas/providers.py +3 -1
- letta/server/rest_api/app.py +15 -7
- letta/server/rest_api/routers/v1/agents.py +3 -0
- letta/server/rest_api/routers/v1/messages.py +46 -1
- letta/server/rest_api/routers/v1/steps.py +1 -1
- letta/server/rest_api/utils.py +25 -6
- letta/server/server.py +11 -3
- letta/services/llm_batch_manager.py +60 -1
- letta/services/message_manager.py +1 -0
- letta/services/summarizer/summarizer.py +42 -36
- letta/settings.py +1 -0
- letta/tracing.py +5 -0
- {letta_nightly-0.7.8.dev20250502104219.dist-info → letta_nightly-0.7.9.dev20250503104103.dist-info}/METADATA +2 -2
- {letta_nightly-0.7.8.dev20250502104219.dist-info → letta_nightly-0.7.9.dev20250503104103.dist-info}/RECORD +41 -41
- {letta_nightly-0.7.8.dev20250502104219.dist-info → letta_nightly-0.7.9.dev20250503104103.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.8.dev20250502104219.dist-info → letta_nightly-0.7.9.dev20250503104103.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.8.dev20250502104219.dist-info → letta_nightly-0.7.9.dev20250503104103.dist-info}/entry_points.txt +0 -0
    
        letta/__init__.py
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 | 
            -
            __version__ = "0.7. | 
| 1 | 
            +
            __version__ = "0.7.9"
         | 
| 2 2 |  | 
| 3 3 | 
             
            # import clients
         | 
| 4 4 | 
             
            from letta.client.client import LocalClient, RESTClient, create_client
         | 
| 5 5 |  | 
| 6 | 
            -
            #  | 
| 6 | 
            +
            # imports for easier access
         | 
| 7 7 | 
             
            from letta.schemas.agent import AgentState
         | 
| 8 8 | 
             
            from letta.schemas.block import Block
         | 
| 9 9 | 
             
            from letta.schemas.embedding_config import EmbeddingConfig
         | 
    
        letta/agents/helpers.py
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            import xml.etree.ElementTree as ET
         | 
| 1 2 | 
             
            from typing import List, Tuple
         | 
| 2 3 |  | 
| 3 4 | 
             
            from letta.schemas.agent import AgentState
         | 
| @@ -20,7 +21,10 @@ def _create_letta_response(new_in_context_messages: list[Message], use_assistant | |
| 20 21 |  | 
| 21 22 |  | 
| 22 23 | 
             
            def _prepare_in_context_messages(
         | 
| 23 | 
            -
                input_messages: List[MessageCreate], | 
| 24 | 
            +
                input_messages: List[MessageCreate],
         | 
| 25 | 
            +
                agent_state: AgentState,
         | 
| 26 | 
            +
                message_manager: MessageManager,
         | 
| 27 | 
            +
                actor: User,
         | 
| 24 28 | 
             
            ) -> Tuple[List[Message], List[Message]]:
         | 
| 25 29 | 
             
                """
         | 
| 26 30 | 
             
                Prepares in-context messages for an agent, based on the current state and a new user input.
         | 
| @@ -50,3 +54,56 @@ def _prepare_in_context_messages( | |
| 50 54 | 
             
                )
         | 
| 51 55 |  | 
| 52 56 | 
             
                return current_in_context_messages, new_in_context_messages
         | 
| 57 | 
            +
             | 
| 58 | 
            +
             | 
| 59 | 
            +
            def serialize_message_history(messages: List[str], context: str) -> str:
         | 
| 60 | 
            +
                """
         | 
| 61 | 
            +
                Produce an XML document like:
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                <memory>
         | 
| 64 | 
            +
                  <messages>
         | 
| 65 | 
            +
                    <message>…</message>
         | 
| 66 | 
            +
                    <message>…</message>
         | 
| 67 | 
            +
                    …
         | 
| 68 | 
            +
                  </messages>
         | 
| 69 | 
            +
                  <context>…</context>
         | 
| 70 | 
            +
                </memory>
         | 
| 71 | 
            +
                """
         | 
| 72 | 
            +
                root = ET.Element("memory")
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                msgs_el = ET.SubElement(root, "messages")
         | 
| 75 | 
            +
                for msg in messages:
         | 
| 76 | 
            +
                    m = ET.SubElement(msgs_el, "message")
         | 
| 77 | 
            +
                    m.text = msg
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                sum_el = ET.SubElement(root, "context")
         | 
| 80 | 
            +
                sum_el.text = context
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                # ET.tostring will escape reserved chars for you
         | 
| 83 | 
            +
                return ET.tostring(root, encoding="unicode")
         | 
| 84 | 
            +
             | 
| 85 | 
            +
             | 
| 86 | 
            +
            def deserialize_message_history(xml_str: str) -> Tuple[List[str], str]:
         | 
| 87 | 
            +
                """
         | 
| 88 | 
            +
                Parse the XML back into (messages, context). Raises ValueError if tags are missing.
         | 
| 89 | 
            +
                """
         | 
| 90 | 
            +
                try:
         | 
| 91 | 
            +
                    root = ET.fromstring(xml_str)
         | 
| 92 | 
            +
                except ET.ParseError as e:
         | 
| 93 | 
            +
                    raise ValueError(f"Invalid XML: {e}")
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                msgs_el = root.find("messages")
         | 
| 96 | 
            +
                if msgs_el is None:
         | 
| 97 | 
            +
                    raise ValueError("Missing <messages> section")
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                messages = []
         | 
| 100 | 
            +
                for m in msgs_el.findall("message"):
         | 
| 101 | 
            +
                    # .text may be None if empty, so coerce to empty string
         | 
| 102 | 
            +
                    messages.append(m.text or "")
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                sum_el = root.find("context")
         | 
| 105 | 
            +
                if sum_el is None:
         | 
| 106 | 
            +
                    raise ValueError("Missing <context> section")
         | 
| 107 | 
            +
                context = sum_el.text or ""
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                return messages, context
         | 
    
        letta/agents/letta_agent.py
    CHANGED
    
    | @@ -62,6 +62,14 @@ class LettaAgent(BaseAgent): | |
| 62 62 | 
             
                @trace_method
         | 
| 63 63 | 
             
                async def step(self, input_messages: List[MessageCreate], max_steps: int = 10) -> LettaResponse:
         | 
| 64 64 | 
             
                    agent_state = self.agent_manager.get_agent_by_id(self.agent_id, actor=self.actor)
         | 
| 65 | 
            +
                    current_in_context_messages, new_in_context_messages = await self._step(
         | 
| 66 | 
            +
                        agent_state=agent_state, input_messages=input_messages, max_steps=max_steps
         | 
| 67 | 
            +
                    )
         | 
| 68 | 
            +
                    return _create_letta_response(new_in_context_messages=new_in_context_messages, use_assistant_message=self.use_assistant_message)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                async def _step(
         | 
| 71 | 
            +
                    self, agent_state: AgentState, input_messages: List[MessageCreate], max_steps: int = 10
         | 
| 72 | 
            +
                ) -> Tuple[List[Message], List[Message]]:
         | 
| 65 73 | 
             
                    current_in_context_messages, new_in_context_messages = _prepare_in_context_messages(
         | 
| 66 74 | 
             
                        input_messages, agent_state, self.message_manager, self.actor
         | 
| 67 75 | 
             
                    )
         | 
| @@ -72,7 +80,7 @@ class LettaAgent(BaseAgent): | |
| 72 80 | 
             
                        put_inner_thoughts_first=True,
         | 
| 73 81 | 
             
                        actor_id=self.actor.id,
         | 
| 74 82 | 
             
                    )
         | 
| 75 | 
            -
                    for  | 
| 83 | 
            +
                    for _ in range(max_steps):
         | 
| 76 84 | 
             
                        response = await self._get_ai_reply(
         | 
| 77 85 | 
             
                            llm_client=llm_client,
         | 
| 78 86 | 
             
                            in_context_messages=current_in_context_messages + new_in_context_messages,
         | 
| @@ -83,6 +91,7 @@ class LettaAgent(BaseAgent): | |
| 83 91 | 
             
                        )
         | 
| 84 92 |  | 
| 85 93 | 
             
                        tool_call = response.choices[0].message.tool_calls[0]
         | 
| 94 | 
            +
             | 
| 86 95 | 
             
                        persisted_messages, should_continue = await self._handle_ai_response(tool_call, agent_state, tool_rules_solver)
         | 
| 87 96 | 
             
                        self.response_messages.extend(persisted_messages)
         | 
| 88 97 | 
             
                        new_in_context_messages.extend(persisted_messages)
         | 
| @@ -95,7 +104,7 @@ class LettaAgent(BaseAgent): | |
| 95 104 | 
             
                        message_ids = [m.id for m in (current_in_context_messages + new_in_context_messages)]
         | 
| 96 105 | 
             
                        self.agent_manager.set_in_context_messages(agent_id=self.agent_id, message_ids=message_ids, actor=self.actor)
         | 
| 97 106 |  | 
| 98 | 
            -
                    return  | 
| 107 | 
            +
                    return current_in_context_messages, new_in_context_messages
         | 
| 99 108 |  | 
| 100 109 | 
             
                @trace_method
         | 
| 101 110 | 
             
                async def step_stream(
         | 
| @@ -117,7 +126,7 @@ class LettaAgent(BaseAgent): | |
| 117 126 | 
             
                        actor_id=self.actor.id,
         | 
| 118 127 | 
             
                    )
         | 
| 119 128 |  | 
| 120 | 
            -
                    for  | 
| 129 | 
            +
                    for _ in range(max_steps):
         | 
| 121 130 | 
             
                        stream = await self._get_ai_reply(
         | 
| 122 131 | 
             
                            llm_client=llm_client,
         | 
| 123 132 | 
             
                            in_context_messages=current_in_context_messages + new_in_context_messages,
         | 
| @@ -181,6 +190,7 @@ class LettaAgent(BaseAgent): | |
| 181 190 | 
             
                            ToolType.LETTA_MEMORY_CORE,
         | 
| 182 191 | 
             
                            ToolType.LETTA_MULTI_AGENT_CORE,
         | 
| 183 192 | 
             
                            ToolType.LETTA_SLEEPTIME_CORE,
         | 
| 193 | 
            +
                            ToolType.LETTA_VOICE_SLEEPTIME_CORE,
         | 
| 184 194 | 
             
                        }
         | 
| 185 195 | 
             
                        or (t.tool_type == ToolType.LETTA_MULTI_AGENT_CORE and t.name == "send_message_to_agents_matching_tags")
         | 
| 186 196 | 
             
                        or (t.tool_type == ToolType.EXTERNAL_COMPOSIO)
         | 
| @@ -137,21 +137,37 @@ class LettaAgentBatch: | |
| 137 137 | 
             
                    log_event(name="load_and_prepare_agents")
         | 
| 138 138 | 
             
                    agent_messages_mapping: Dict[str, List[Message]] = {}
         | 
| 139 139 | 
             
                    agent_tools_mapping: Dict[str, List[dict]] = {}
         | 
| 140 | 
            +
                    # TODO: This isn't optimal, moving fast - prone to bugs because we pass around this half formed pydantic object
         | 
| 141 | 
            +
                    agent_batch_item_mapping: Dict[str, LLMBatchItem] = {}
         | 
| 140 142 | 
             
                    agent_states = []
         | 
| 141 143 | 
             
                    for batch_request in batch_requests:
         | 
| 142 144 | 
             
                        agent_id = batch_request.agent_id
         | 
| 143 145 | 
             
                        agent_state = self.agent_manager.get_agent_by_id(agent_id, actor=self.actor)
         | 
| 144 146 | 
             
                        agent_states.append(agent_state)
         | 
| 145 147 |  | 
| 146 | 
            -
                        agent_messages_mapping[agent_id] = self._get_in_context_messages_per_agent(
         | 
| 147 | 
            -
                            agent_state=agent_state, input_messages=batch_request.messages
         | 
| 148 | 
            -
                        )
         | 
| 149 | 
            -
             | 
| 150 148 | 
             
                        if agent_id not in agent_step_state_mapping:
         | 
| 151 149 | 
             
                            agent_step_state_mapping[agent_id] = AgentStepState(
         | 
| 152 150 | 
             
                                step_number=0, tool_rules_solver=ToolRulesSolver(tool_rules=agent_state.tool_rules)
         | 
| 153 151 | 
             
                            )
         | 
| 154 152 |  | 
| 153 | 
            +
                        llm_batch_item = LLMBatchItem(
         | 
| 154 | 
            +
                            llm_batch_id="",  # TODO: This is hacky, it gets filled in later
         | 
| 155 | 
            +
                            agent_id=agent_state.id,
         | 
| 156 | 
            +
                            llm_config=agent_state.llm_config,
         | 
| 157 | 
            +
                            request_status=JobStatus.created,
         | 
| 158 | 
            +
                            step_status=AgentStepStatus.paused,
         | 
| 159 | 
            +
                            step_state=agent_step_state_mapping[agent_id],
         | 
| 160 | 
            +
                        )
         | 
| 161 | 
            +
                        agent_batch_item_mapping[agent_id] = llm_batch_item
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                        # Fill in the batch_item_id for the message
         | 
| 164 | 
            +
                        for msg in batch_request.messages:
         | 
| 165 | 
            +
                            msg.batch_item_id = llm_batch_item.id
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                        agent_messages_mapping[agent_id] = self._prepare_in_context_messages_per_agent(
         | 
| 168 | 
            +
                            agent_state=agent_state, input_messages=batch_request.messages
         | 
| 169 | 
            +
                        )
         | 
| 170 | 
            +
             | 
| 155 171 | 
             
                        agent_tools_mapping[agent_id] = self._prepare_tools_per_agent(agent_state, agent_step_state_mapping[agent_id].tool_rules_solver)
         | 
| 156 172 |  | 
| 157 173 | 
             
                    log_event(name="init_llm_client")
         | 
| @@ -182,21 +198,14 @@ class LettaAgentBatch: | |
| 182 198 | 
             
                    log_event(name="prepare_batch_items")
         | 
| 183 199 | 
             
                    batch_items = []
         | 
| 184 200 | 
             
                    for state in agent_states:
         | 
| 185 | 
            -
                         | 
| 186 | 
            -
                         | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
                                agent_id=state.id,
         | 
| 190 | 
            -
                                llm_config=state.llm_config,
         | 
| 191 | 
            -
                                request_status=JobStatus.created,
         | 
| 192 | 
            -
                                step_status=AgentStepStatus.paused,
         | 
| 193 | 
            -
                                step_state=step_state,
         | 
| 194 | 
            -
                            )
         | 
| 195 | 
            -
                        )
         | 
| 201 | 
            +
                        llm_batch_item = agent_batch_item_mapping[state.id]
         | 
| 202 | 
            +
                        # TODO This is hacky
         | 
| 203 | 
            +
                        llm_batch_item.llm_batch_id = llm_batch_job.id
         | 
| 204 | 
            +
                        batch_items.append(llm_batch_item)
         | 
| 196 205 |  | 
| 197 206 | 
             
                    if batch_items:
         | 
| 198 207 | 
             
                        log_event(name="bulk_create_batch_items")
         | 
| 199 | 
            -
                        self.batch_manager.create_llm_batch_items_bulk(batch_items, actor=self.actor)
         | 
| 208 | 
            +
                        batch_items_persisted = self.batch_manager.create_llm_batch_items_bulk(batch_items, actor=self.actor)
         | 
| 200 209 |  | 
| 201 210 | 
             
                    log_event(name="return_batch_response")
         | 
| 202 211 | 
             
                    return LettaBatchResponse(
         | 
| @@ -335,9 +344,14 @@ class LettaAgentBatch: | |
| 335 344 | 
             
                    exec_results: Sequence[Tuple[str, Tuple[str, bool]]],
         | 
| 336 345 | 
             
                    ctx: _ResumeContext,
         | 
| 337 346 | 
             
                ) -> Dict[str, List[Message]]:
         | 
| 347 | 
            +
                    # TODO: This is redundant, we should have this ready on the ctx
         | 
| 348 | 
            +
                    # TODO: I am doing it quick and dirty for now
         | 
| 349 | 
            +
                    agent_item_map: Dict[str, LLMBatchItem] = {item.agent_id: item for item in ctx.batch_items}
         | 
| 350 | 
            +
             | 
| 338 351 | 
             
                    msg_map: Dict[str, List[Message]] = {}
         | 
| 339 352 | 
             
                    for aid, (tool_res, success) in exec_results:
         | 
| 340 353 | 
             
                        msgs = self._create_tool_call_messages(
         | 
| 354 | 
            +
                            llm_batch_item_id=agent_item_map[aid].id,
         | 
| 341 355 | 
             
                            agent_state=ctx.agent_state_map[aid],
         | 
| 342 356 | 
             
                            tool_call_name=ctx.tool_call_name_map[aid],
         | 
| 343 357 | 
             
                            tool_call_args=ctx.tool_call_args_map[aid],
         | 
| @@ -399,6 +413,7 @@ class LettaAgentBatch: | |
| 399 413 |  | 
| 400 414 | 
             
                def _create_tool_call_messages(
         | 
| 401 415 | 
             
                    self,
         | 
| 416 | 
            +
                    llm_batch_item_id: str,
         | 
| 402 417 | 
             
                    agent_state: AgentState,
         | 
| 403 418 | 
             
                    tool_call_name: str,
         | 
| 404 419 | 
             
                    tool_call_args: Dict[str, Any],
         | 
| @@ -421,6 +436,7 @@ class LettaAgentBatch: | |
| 421 436 | 
             
                        reasoning_content=reasoning_content,
         | 
| 422 437 | 
             
                        pre_computed_assistant_message_id=None,
         | 
| 423 438 | 
             
                        pre_computed_tool_message_id=None,
         | 
| 439 | 
            +
                        llm_batch_item_id=llm_batch_item_id,
         | 
| 424 440 | 
             
                    )
         | 
| 425 441 |  | 
| 426 442 | 
             
                    return tool_call_messages
         | 
| @@ -477,7 +493,7 @@ class LettaAgentBatch: | |
| 477 493 | 
             
                    valid_tool_names = tool_rules_solver.get_allowed_tool_names(available_tools=set([t.name for t in tools]))
         | 
| 478 494 | 
             
                    return [enable_strict_mode(t.json_schema) for t in tools if t.name in set(valid_tool_names)]
         | 
| 479 495 |  | 
| 480 | 
            -
                def  | 
| 496 | 
            +
                def _prepare_in_context_messages_per_agent(self, agent_state: AgentState, input_messages: List[MessageCreate]) -> List[Message]:
         | 
| 481 497 | 
             
                    current_in_context_messages, new_in_context_messages = _prepare_in_context_messages(
         | 
| 482 498 | 
             
                        input_messages, agent_state, self.message_manager, self.actor
         | 
| 483 499 | 
             
                    )
         | 
    
        letta/agents/voice_agent.py
    CHANGED
    
    | @@ -97,13 +97,12 @@ class VoiceAgent(BaseAgent): | |
| 97 97 | 
             
                        summarizer_agent=VoiceSleeptimeAgent(
         | 
| 98 98 | 
             
                            agent_id=voice_sleeptime_agent_id,
         | 
| 99 99 | 
             
                            convo_agent_state=agent_state,
         | 
| 100 | 
            -
                            openai_client=self.openai_client,
         | 
| 101 100 | 
             
                            message_manager=self.message_manager,
         | 
| 102 101 | 
             
                            agent_manager=self.agent_manager,
         | 
| 103 102 | 
             
                            actor=self.actor,
         | 
| 104 103 | 
             
                            block_manager=self.block_manager,
         | 
| 104 | 
            +
                            passage_manager=self.passage_manager,
         | 
| 105 105 | 
             
                            target_block_label=self.summary_block_label,
         | 
| 106 | 
            -
                            message_transcripts=[],
         | 
| 107 106 | 
             
                        ),
         | 
| 108 107 | 
             
                        message_buffer_limit=agent_state.multi_agent_group.max_message_buffer_length,
         | 
| 109 108 | 
             
                        message_buffer_min=agent_state.multi_agent_group.min_message_buffer_length,
         |