letta-nightly 0.7.6.dev20250430104233__py3-none-any.whl → 0.7.8.dev20250501064110__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/agent.py +8 -12
- letta/agents/exceptions.py +6 -0
- letta/agents/helpers.py +1 -1
- letta/agents/letta_agent.py +48 -35
- letta/agents/letta_agent_batch.py +6 -2
- letta/agents/voice_agent.py +41 -59
- letta/agents/{ephemeral_memory_agent.py → voice_sleeptime_agent.py} +106 -129
- letta/client/client.py +3 -3
- letta/constants.py +18 -2
- letta/functions/composio_helpers.py +100 -0
- letta/functions/function_sets/base.py +0 -10
- letta/functions/function_sets/voice.py +92 -0
- letta/functions/functions.py +4 -2
- letta/functions/helpers.py +19 -101
- letta/groups/helpers.py +1 -0
- letta/groups/sleeptime_multi_agent.py +5 -1
- letta/helpers/message_helper.py +21 -4
- letta/helpers/tool_execution_helper.py +1 -1
- letta/interfaces/anthropic_streaming_interface.py +165 -158
- letta/interfaces/openai_chat_completions_streaming_interface.py +1 -1
- letta/llm_api/anthropic.py +15 -10
- letta/llm_api/anthropic_client.py +5 -1
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/llm_api_tools.py +7 -0
- letta/llm_api/llm_client.py +12 -2
- letta/llm_api/llm_client_base.py +4 -0
- letta/llm_api/openai.py +9 -3
- letta/llm_api/openai_client.py +18 -4
- letta/memory.py +3 -1
- letta/orm/enums.py +1 -0
- letta/orm/group.py +2 -0
- letta/orm/provider.py +10 -0
- letta/personas/examples/voice_memory_persona.txt +5 -0
- letta/prompts/system/voice_chat.txt +29 -0
- letta/prompts/system/voice_sleeptime.txt +74 -0
- letta/schemas/agent.py +14 -2
- letta/schemas/enums.py +11 -0
- letta/schemas/group.py +37 -2
- letta/schemas/llm_config.py +1 -0
- letta/schemas/llm_config_overrides.py +2 -2
- letta/schemas/message.py +4 -3
- letta/schemas/providers.py +75 -213
- letta/schemas/tool.py +8 -12
- letta/server/rest_api/app.py +12 -0
- letta/server/rest_api/chat_completions_interface.py +1 -1
- letta/server/rest_api/interface.py +8 -10
- letta/server/rest_api/{optimistic_json_parser.py → json_parser.py} +62 -26
- letta/server/rest_api/routers/v1/agents.py +1 -1
- letta/server/rest_api/routers/v1/embeddings.py +4 -3
- letta/server/rest_api/routers/v1/llms.py +4 -3
- letta/server/rest_api/routers/v1/providers.py +4 -1
- letta/server/rest_api/routers/v1/voice.py +0 -2
- letta/server/rest_api/utils.py +22 -33
- letta/server/server.py +91 -37
- letta/services/agent_manager.py +14 -7
- letta/services/group_manager.py +61 -0
- letta/services/helpers/agent_manager_helper.py +69 -12
- letta/services/message_manager.py +2 -2
- letta/services/passage_manager.py +13 -4
- letta/services/provider_manager.py +25 -14
- letta/services/summarizer/summarizer.py +20 -15
- letta/services/tool_executor/tool_execution_manager.py +1 -1
- letta/services/tool_executor/tool_executor.py +3 -3
- letta/services/tool_manager.py +32 -7
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/METADATA +4 -5
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/RECORD +70 -64
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/entry_points.txt +0 -0
@@ -28,7 +28,7 @@ from letta.schemas.letta_message import (
|
|
28
28
|
from letta.schemas.letta_message_content import ReasoningContent, RedactedReasoningContent, TextContent
|
29
29
|
from letta.schemas.message import Message
|
30
30
|
from letta.schemas.openai.chat_completion_response import ChatCompletionChunkResponse
|
31
|
-
from letta.server.rest_api.
|
31
|
+
from letta.server.rest_api.json_parser import OptimisticJSONParser
|
32
32
|
from letta.streaming_interface import AgentChunkStreamingInterface
|
33
33
|
from letta.streaming_utils import FunctionArgumentsStreamHandler, JSONInnerThoughtsExtractor
|
34
34
|
from letta.utils import parse_json
|
@@ -291,7 +291,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
291
291
|
self.streaming_chat_completion_json_reader = FunctionArgumentsStreamHandler(json_key=assistant_message_tool_kwarg)
|
292
292
|
|
293
293
|
# @matt's changes here, adopting new optimistic json parser
|
294
|
-
self.current_function_arguments =
|
294
|
+
self.current_function_arguments = ""
|
295
295
|
self.optimistic_json_parser = OptimisticJSONParser()
|
296
296
|
self.current_json_parse_result = {}
|
297
297
|
|
@@ -387,7 +387,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
387
387
|
def stream_start(self):
|
388
388
|
"""Initialize streaming by activating the generator and clearing any old chunks."""
|
389
389
|
self.streaming_chat_completion_mode_function_name = None
|
390
|
-
self.current_function_arguments =
|
390
|
+
self.current_function_arguments = ""
|
391
391
|
self.current_json_parse_result = {}
|
392
392
|
|
393
393
|
if not self._active:
|
@@ -398,7 +398,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
398
398
|
def stream_end(self):
|
399
399
|
"""Clean up the stream by deactivating and clearing chunks."""
|
400
400
|
self.streaming_chat_completion_mode_function_name = None
|
401
|
-
self.current_function_arguments =
|
401
|
+
self.current_function_arguments = ""
|
402
402
|
self.current_json_parse_result = {}
|
403
403
|
|
404
404
|
# if not self.streaming_chat_completion_mode and not self.nonstreaming_legacy_mode:
|
@@ -609,14 +609,13 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
609
609
|
# early exit to turn into content mode
|
610
610
|
return None
|
611
611
|
if tool_call.function.arguments:
|
612
|
-
self.current_function_arguments
|
612
|
+
self.current_function_arguments += tool_call.function.arguments
|
613
613
|
|
614
614
|
# if we're in the middle of parsing a send_message, we'll keep processing the JSON chunks
|
615
615
|
if tool_call.function.arguments and self.streaming_chat_completion_mode_function_name == self.assistant_message_tool_name:
|
616
616
|
# Strip out any extras tokens
|
617
617
|
# In the case that we just have the prefix of something, no message yet, then we should early exit to move to the next chunk
|
618
|
-
|
619
|
-
parsed_args = self.optimistic_json_parser.parse(combined_args)
|
618
|
+
parsed_args = self.optimistic_json_parser.parse(self.current_function_arguments)
|
620
619
|
|
621
620
|
if parsed_args.get(self.assistant_message_tool_kwarg) and parsed_args.get(
|
622
621
|
self.assistant_message_tool_kwarg
|
@@ -686,7 +685,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
686
685
|
# updates_inner_thoughts = ""
|
687
686
|
# else: # OpenAI
|
688
687
|
# updates_main_json, updates_inner_thoughts = self.function_args_reader.process_fragment(tool_call.function.arguments)
|
689
|
-
self.current_function_arguments
|
688
|
+
self.current_function_arguments += tool_call.function.arguments
|
690
689
|
updates_main_json, updates_inner_thoughts = self.function_args_reader.process_fragment(tool_call.function.arguments)
|
691
690
|
|
692
691
|
# If we have inner thoughts, we should output them as a chunk
|
@@ -805,8 +804,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
|
|
805
804
|
# TODO: THIS IS HORRIBLE
|
806
805
|
# TODO: WE USE THE OLD JSON PARSER EARLIER (WHICH DOES NOTHING) AND NOW THE NEW JSON PARSER
|
807
806
|
# TODO: THIS IS TOTALLY WRONG AND BAD, BUT SAVING FOR A LARGER REWRITE IN THE NEAR FUTURE
|
808
|
-
|
809
|
-
parsed_args = self.optimistic_json_parser.parse(combined_args)
|
807
|
+
parsed_args = self.optimistic_json_parser.parse(self.current_function_arguments)
|
810
808
|
|
811
809
|
if parsed_args.get(self.assistant_message_tool_kwarg) and parsed_args.get(
|
812
810
|
self.assistant_message_tool_kwarg
|
@@ -1,7 +1,43 @@
|
|
1
1
|
import json
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from typing import Any
|
2
4
|
|
5
|
+
from pydantic_core import from_json
|
3
6
|
|
4
|
-
|
7
|
+
from letta.log import get_logger
|
8
|
+
|
9
|
+
logger = get_logger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
class JSONParser(ABC):
|
13
|
+
@abstractmethod
|
14
|
+
def parse(self, input_str: str) -> Any:
|
15
|
+
raise NotImplementedError()
|
16
|
+
|
17
|
+
|
18
|
+
class PydanticJSONParser(JSONParser):
|
19
|
+
"""
|
20
|
+
https://docs.pydantic.dev/latest/concepts/json/#json-parsing
|
21
|
+
If `strict` is True, we will not allow for partial parsing of JSON.
|
22
|
+
|
23
|
+
Compared with `OptimisticJSONParser`, this parser is more strict.
|
24
|
+
Note: This will not partially parse strings which may be decrease parsing speed for message strings
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, strict=False):
|
28
|
+
self.strict = strict
|
29
|
+
|
30
|
+
def parse(self, input_str: str) -> Any:
|
31
|
+
if not input_str:
|
32
|
+
return {}
|
33
|
+
try:
|
34
|
+
return from_json(input_str, allow_partial="trailing-strings" if not self.strict else False)
|
35
|
+
except ValueError as e:
|
36
|
+
logger.error(f"Failed to parse JSON: {e}")
|
37
|
+
raise
|
38
|
+
|
39
|
+
|
40
|
+
class OptimisticJSONParser(JSONParser):
|
5
41
|
"""
|
6
42
|
A JSON parser that attempts to parse a given string using `json.loads`,
|
7
43
|
and if that fails, it parses as much valid JSON as possible while
|
@@ -13,25 +49,25 @@ class OptimisticJSONParser:
|
|
13
49
|
def __init__(self, strict=False):
|
14
50
|
self.strict = strict
|
15
51
|
self.parsers = {
|
16
|
-
" ": self.
|
17
|
-
"\r": self.
|
18
|
-
"\n": self.
|
19
|
-
"\t": self.
|
20
|
-
"[": self.
|
21
|
-
"{": self.
|
22
|
-
'"': self.
|
23
|
-
"t": self.
|
24
|
-
"f": self.
|
25
|
-
"n": self.
|
52
|
+
" ": self._parse_space,
|
53
|
+
"\r": self._parse_space,
|
54
|
+
"\n": self._parse_space,
|
55
|
+
"\t": self._parse_space,
|
56
|
+
"[": self._parse_array,
|
57
|
+
"{": self._parse_object,
|
58
|
+
'"': self._parse_string,
|
59
|
+
"t": self._parse_true,
|
60
|
+
"f": self._parse_false,
|
61
|
+
"n": self._parse_null,
|
26
62
|
}
|
27
63
|
# Register number parser for digits and signs
|
28
64
|
for char in "0123456789.-":
|
29
65
|
self.parsers[char] = self.parse_number
|
30
66
|
|
31
67
|
self.last_parse_reminding = None
|
32
|
-
self.on_extra_token = self.
|
68
|
+
self.on_extra_token = self._default_on_extra_token
|
33
69
|
|
34
|
-
def
|
70
|
+
def _default_on_extra_token(self, text, data, reminding):
|
35
71
|
print(f"Parsed JSON with extra tokens: {data}, remaining: {reminding}")
|
36
72
|
|
37
73
|
def parse(self, input_str):
|
@@ -45,7 +81,7 @@ class OptimisticJSONParser:
|
|
45
81
|
try:
|
46
82
|
return json.loads(input_str)
|
47
83
|
except json.JSONDecodeError as decode_error:
|
48
|
-
data, reminding = self.
|
84
|
+
data, reminding = self._parse_any(input_str, decode_error)
|
49
85
|
self.last_parse_reminding = reminding
|
50
86
|
if self.on_extra_token and reminding:
|
51
87
|
self.on_extra_token(input_str, data, reminding)
|
@@ -53,7 +89,7 @@ class OptimisticJSONParser:
|
|
53
89
|
else:
|
54
90
|
return json.loads("{}")
|
55
91
|
|
56
|
-
def
|
92
|
+
def _parse_any(self, input_str, decode_error):
|
57
93
|
"""Determine which parser to use based on the first character."""
|
58
94
|
if not input_str:
|
59
95
|
raise decode_error
|
@@ -62,11 +98,11 @@ class OptimisticJSONParser:
|
|
62
98
|
raise decode_error
|
63
99
|
return parser(input_str, decode_error)
|
64
100
|
|
65
|
-
def
|
101
|
+
def _parse_space(self, input_str, decode_error):
|
66
102
|
"""Strip leading whitespace and parse again."""
|
67
|
-
return self.
|
103
|
+
return self._parse_any(input_str.strip(), decode_error)
|
68
104
|
|
69
|
-
def
|
105
|
+
def _parse_array(self, input_str, decode_error):
|
70
106
|
"""Parse a JSON array, returning the list and remaining string."""
|
71
107
|
# Skip the '['
|
72
108
|
input_str = input_str[1:]
|
@@ -77,7 +113,7 @@ class OptimisticJSONParser:
|
|
77
113
|
# Skip the ']'
|
78
114
|
input_str = input_str[1:]
|
79
115
|
break
|
80
|
-
value, input_str = self.
|
116
|
+
value, input_str = self._parse_any(input_str, decode_error)
|
81
117
|
array_values.append(value)
|
82
118
|
input_str = input_str.strip()
|
83
119
|
if input_str.startswith(","):
|
@@ -85,7 +121,7 @@ class OptimisticJSONParser:
|
|
85
121
|
input_str = input_str[1:].strip()
|
86
122
|
return array_values, input_str
|
87
123
|
|
88
|
-
def
|
124
|
+
def _parse_object(self, input_str, decode_error):
|
89
125
|
"""Parse a JSON object, returning the dict and remaining string."""
|
90
126
|
# Skip the '{'
|
91
127
|
input_str = input_str[1:]
|
@@ -96,7 +132,7 @@ class OptimisticJSONParser:
|
|
96
132
|
# Skip the '}'
|
97
133
|
input_str = input_str[1:]
|
98
134
|
break
|
99
|
-
key, input_str = self.
|
135
|
+
key, input_str = self._parse_any(input_str, decode_error)
|
100
136
|
input_str = input_str.strip()
|
101
137
|
|
102
138
|
if not input_str or input_str[0] == "}":
|
@@ -113,7 +149,7 @@ class OptimisticJSONParser:
|
|
113
149
|
input_str = input_str[1:]
|
114
150
|
break
|
115
151
|
|
116
|
-
value, input_str = self.
|
152
|
+
value, input_str = self._parse_any(input_str, decode_error)
|
117
153
|
obj[key] = value
|
118
154
|
input_str = input_str.strip()
|
119
155
|
if input_str.startswith(","):
|
@@ -121,7 +157,7 @@ class OptimisticJSONParser:
|
|
121
157
|
input_str = input_str[1:].strip()
|
122
158
|
return obj, input_str
|
123
159
|
|
124
|
-
def
|
160
|
+
def _parse_string(self, input_str, decode_error):
|
125
161
|
"""Parse a JSON string, respecting escaped quotes if present."""
|
126
162
|
end = input_str.find('"', 1)
|
127
163
|
while end != -1 and input_str[end - 1] == "\\":
|
@@ -166,19 +202,19 @@ class OptimisticJSONParser:
|
|
166
202
|
|
167
203
|
return num, remainder
|
168
204
|
|
169
|
-
def
|
205
|
+
def _parse_true(self, input_str, decode_error):
|
170
206
|
"""Parse a 'true' value."""
|
171
207
|
if input_str.startswith(("t", "T")):
|
172
208
|
return True, input_str[4:]
|
173
209
|
raise decode_error
|
174
210
|
|
175
|
-
def
|
211
|
+
def _parse_false(self, input_str, decode_error):
|
176
212
|
"""Parse a 'false' value."""
|
177
213
|
if input_str.startswith(("f", "F")):
|
178
214
|
return False, input_str[5:]
|
179
215
|
raise decode_error
|
180
216
|
|
181
|
-
def
|
217
|
+
def _parse_null(self, input_str, decode_error):
|
182
218
|
"""Parse a 'null' value."""
|
183
219
|
if input_str.startswith("n"):
|
184
220
|
return None, input_str[4:]
|
@@ -678,7 +678,7 @@ async def send_message_streaming(
|
|
678
678
|
server: SyncServer = Depends(get_letta_server),
|
679
679
|
request: LettaStreamingRequest = Body(...),
|
680
680
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
681
|
-
):
|
681
|
+
) -> StreamingResponse | LettaResponse:
|
682
682
|
"""
|
683
683
|
Process a user message and return the agent's response.
|
684
684
|
This endpoint accepts a message from a user and processes it through the agent.
|
@@ -9,12 +9,13 @@ router = APIRouter(prefix="/embeddings", tags=["embeddings"])
|
|
9
9
|
|
10
10
|
|
11
11
|
@router.get("/total_storage_size", response_model=float, operation_id="get_total_storage_size")
|
12
|
-
def
|
12
|
+
def get_embeddings_total_storage_size(
|
13
13
|
server: SyncServer = Depends(get_letta_server),
|
14
14
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
15
|
+
storage_unit: Optional[str] = Header("GB", alias="storage_unit"), # Extract storage unit from header, default to GB
|
15
16
|
):
|
16
17
|
"""
|
17
|
-
Get the total size of all embeddings in the database for a user in
|
18
|
+
Get the total size of all embeddings in the database for a user in the storage unit given.
|
18
19
|
"""
|
19
20
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
20
|
-
return server.passage_manager.
|
21
|
+
return server.passage_manager.estimate_embeddings_size(actor=actor, storage_unit=storage_unit)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
from typing import TYPE_CHECKING, List
|
1
|
+
from typing import TYPE_CHECKING, List, Optional
|
2
2
|
|
3
|
-
from fastapi import APIRouter, Depends
|
3
|
+
from fastapi import APIRouter, Depends, Query
|
4
4
|
|
5
5
|
from letta.schemas.embedding_config import EmbeddingConfig
|
6
6
|
from letta.schemas.llm_config import LLMConfig
|
@@ -14,10 +14,11 @@ router = APIRouter(prefix="/models", tags=["models", "llms"])
|
|
14
14
|
|
15
15
|
@router.get("/", response_model=List[LLMConfig], operation_id="list_models")
|
16
16
|
def list_llm_models(
|
17
|
+
byok_only: Optional[bool] = Query(None),
|
17
18
|
server: "SyncServer" = Depends(get_letta_server),
|
18
19
|
):
|
19
20
|
|
20
|
-
models = server.list_llm_models()
|
21
|
+
models = server.list_llm_models(byok_only=byok_only)
|
21
22
|
# print(models)
|
22
23
|
return models
|
23
24
|
|
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, List, Optional
|
|
2
2
|
|
3
3
|
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
|
4
4
|
|
5
|
+
from letta.schemas.enums import ProviderType
|
5
6
|
from letta.schemas.providers import Provider, ProviderCreate, ProviderUpdate
|
6
7
|
from letta.server.rest_api.utils import get_letta_server
|
7
8
|
|
@@ -13,6 +14,8 @@ router = APIRouter(prefix="/providers", tags=["providers"])
|
|
13
14
|
|
14
15
|
@router.get("/", response_model=List[Provider], operation_id="list_providers")
|
15
16
|
def list_providers(
|
17
|
+
name: Optional[str] = Query(None),
|
18
|
+
provider_type: Optional[ProviderType] = Query(None),
|
16
19
|
after: Optional[str] = Query(None),
|
17
20
|
limit: Optional[int] = Query(50),
|
18
21
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
@@ -23,7 +26,7 @@ def list_providers(
|
|
23
26
|
"""
|
24
27
|
try:
|
25
28
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
26
|
-
providers = server.provider_manager.list_providers(after=after, limit=limit, actor=actor)
|
29
|
+
providers = server.provider_manager.list_providers(after=after, limit=limit, actor=actor, name=name, provider_type=provider_type)
|
27
30
|
except HTTPException:
|
28
31
|
raise
|
29
32
|
except Exception as e:
|
letta/server/rest_api/utils.py
CHANGED
@@ -16,6 +16,7 @@ from pydantic import BaseModel
|
|
16
16
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, FUNC_FAILED_HEARTBEAT_MESSAGE, REQ_HEARTBEAT_MESSAGE
|
17
17
|
from letta.errors import ContextWindowExceededError, RateLimitExceededError
|
18
18
|
from letta.helpers.datetime_helpers import get_utc_time
|
19
|
+
from letta.helpers.message_helper import convert_message_creates_to_messages
|
19
20
|
from letta.log import get_logger
|
20
21
|
from letta.schemas.enums import MessageRole
|
21
22
|
from letta.schemas.letta_message_content import OmittedReasoningContent, ReasoningContent, RedactedReasoningContent, TextContent
|
@@ -143,27 +144,15 @@ def log_error_to_sentry(e):
|
|
143
144
|
def create_input_messages(input_messages: List[MessageCreate], agent_id: str, actor: User) -> List[Message]:
|
144
145
|
"""
|
145
146
|
Converts a user input message into the internal structured format.
|
147
|
+
|
148
|
+
TODO (cliandy): this effectively duplicates the functionality of `convert_message_creates_to_messages`,
|
149
|
+
we should unify this when it's clear what message attributes we need.
|
146
150
|
"""
|
147
|
-
new_messages = []
|
148
|
-
for input_message in input_messages:
|
149
|
-
# Construct the Message object
|
150
|
-
new_message = Message(
|
151
|
-
id=f"message-{uuid.uuid4()}",
|
152
|
-
role=input_message.role,
|
153
|
-
content=input_message.content,
|
154
|
-
name=input_message.name,
|
155
|
-
otid=input_message.otid,
|
156
|
-
sender_id=input_message.sender_id,
|
157
|
-
organization_id=actor.organization_id,
|
158
|
-
agent_id=agent_id,
|
159
|
-
model=None,
|
160
|
-
tool_calls=None,
|
161
|
-
tool_call_id=None,
|
162
|
-
created_at=get_utc_time(),
|
163
|
-
)
|
164
|
-
new_messages.append(new_message)
|
165
151
|
|
166
|
-
|
152
|
+
messages = convert_message_creates_to_messages(input_messages, agent_id, wrap_user_message=False, wrap_system_message=False)
|
153
|
+
for message in messages:
|
154
|
+
message.organization_id = actor.organization_id
|
155
|
+
return messages
|
167
156
|
|
168
157
|
|
169
158
|
def create_letta_messages_from_llm_response(
|
@@ -210,20 +199,20 @@ def create_letta_messages_from_llm_response(
|
|
210
199
|
|
211
200
|
# TODO: Use ToolReturnContent instead of TextContent
|
212
201
|
# TODO: This helps preserve ordering
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
202
|
+
tool_message = Message(
|
203
|
+
role=MessageRole.tool,
|
204
|
+
content=[TextContent(text=package_function_response(function_call_success, function_response))],
|
205
|
+
organization_id=actor.organization_id,
|
206
|
+
agent_id=agent_id,
|
207
|
+
model=model,
|
208
|
+
tool_calls=[],
|
209
|
+
tool_call_id=tool_call_id,
|
210
|
+
created_at=get_utc_time(),
|
211
|
+
name=function_name,
|
212
|
+
)
|
213
|
+
if pre_computed_tool_message_id:
|
214
|
+
tool_message.id = pre_computed_tool_message_id
|
215
|
+
messages.append(tool_message)
|
227
216
|
|
228
217
|
if add_heartbeat_request_system_message:
|
229
218
|
heartbeat_system_message = create_heartbeat_system_message(
|