letta-nightly 0.6.39.dev20250313162623__py3-none-any.whl → 0.6.40.dev20250314173529__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.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/agent.py +13 -3
- letta/agents/ephemeral_agent.py +2 -1
- letta/agents/low_latency_agent.py +8 -0
- letta/dynamic_multi_agent.py +274 -0
- letta/functions/function_sets/base.py +1 -0
- letta/functions/function_sets/extras.py +2 -1
- letta/functions/function_sets/multi_agent.py +17 -0
- letta/functions/helpers.py +41 -0
- letta/helpers/converters.py +67 -0
- letta/helpers/mcp_helpers.py +26 -5
- letta/llm_api/openai.py +1 -1
- letta/memory.py +2 -1
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +69 -20
- letta/orm/custom_columns.py +15 -0
- letta/orm/group.py +33 -0
- letta/orm/groups_agents.py +13 -0
- letta/orm/message.py +7 -4
- letta/orm/organization.py +1 -0
- letta/orm/sqlalchemy_base.py +3 -3
- letta/round_robin_multi_agent.py +152 -0
- letta/schemas/agent.py +3 -0
- letta/schemas/enums.py +0 -4
- letta/schemas/group.py +65 -0
- letta/schemas/letta_message.py +167 -106
- letta/schemas/letta_message_content.py +192 -0
- letta/schemas/message.py +28 -36
- letta/serialize_schemas/__init__.py +1 -1
- letta/serialize_schemas/marshmallow_agent.py +108 -0
- letta/serialize_schemas/{agent_environment_variable.py → marshmallow_agent_environment_variable.py} +1 -1
- letta/serialize_schemas/marshmallow_base.py +52 -0
- letta/serialize_schemas/{block.py → marshmallow_block.py} +1 -1
- letta/serialize_schemas/{custom_fields.py → marshmallow_custom_fields.py} +12 -0
- letta/serialize_schemas/marshmallow_message.py +42 -0
- letta/serialize_schemas/{tag.py → marshmallow_tag.py} +12 -2
- letta/serialize_schemas/{tool.py → marshmallow_tool.py} +1 -1
- letta/serialize_schemas/pydantic_agent_schema.py +111 -0
- letta/server/rest_api/app.py +15 -0
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +46 -40
- letta/server/rest_api/routers/v1/groups.py +233 -0
- letta/server/rest_api/routers/v1/tools.py +31 -3
- letta/server/rest_api/utils.py +1 -1
- letta/server/server.py +267 -12
- letta/services/agent_manager.py +65 -28
- letta/services/group_manager.py +147 -0
- letta/services/helpers/agent_manager_helper.py +151 -1
- letta/services/message_manager.py +11 -3
- letta/services/passage_manager.py +15 -0
- letta/settings.py +5 -0
- letta/supervisor_multi_agent.py +103 -0
- {letta_nightly-0.6.39.dev20250313162623.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/METADATA +1 -2
- {letta_nightly-0.6.39.dev20250313162623.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/RECORD +56 -46
- letta/serialize_schemas/agent.py +0 -80
- letta/serialize_schemas/base.py +0 -64
- letta/serialize_schemas/message.py +0 -29
- {letta_nightly-0.6.39.dev20250313162623.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.39.dev20250313162623.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.39.dev20250313162623.dist-info → letta_nightly-0.6.40.dev20250314173529.dist-info}/entry_points.txt +0 -0
letta/schemas/message.py
CHANGED
|
@@ -9,26 +9,25 @@ from typing import Any, Dict, List, Literal, Optional, Union
|
|
|
9
9
|
|
|
10
10
|
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
|
|
11
11
|
from openai.types.chat.chat_completion_message_tool_call import Function as OpenAIFunction
|
|
12
|
-
from pydantic import BaseModel, Field, field_validator
|
|
12
|
+
from pydantic import BaseModel, Field, field_validator
|
|
13
13
|
|
|
14
14
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, TOOL_CALL_ID_MAX_LEN
|
|
15
15
|
from letta.helpers.datetime_helpers import get_utc_time, is_utc_datetime
|
|
16
16
|
from letta.helpers.json_helpers import json_dumps
|
|
17
17
|
from letta.local_llm.constants import INNER_THOUGHTS_KWARG
|
|
18
|
-
from letta.schemas.enums import
|
|
18
|
+
from letta.schemas.enums import MessageRole
|
|
19
19
|
from letta.schemas.letta_base import OrmMetadataBase
|
|
20
20
|
from letta.schemas.letta_message import (
|
|
21
21
|
AssistantMessage,
|
|
22
22
|
LettaMessage,
|
|
23
|
-
MessageContentUnion,
|
|
24
23
|
ReasoningMessage,
|
|
25
24
|
SystemMessage,
|
|
26
|
-
TextContent,
|
|
27
25
|
ToolCall,
|
|
28
26
|
ToolCallMessage,
|
|
29
27
|
ToolReturnMessage,
|
|
30
28
|
UserMessage,
|
|
31
29
|
)
|
|
30
|
+
from letta.schemas.letta_message_content import LettaMessageContentUnion, TextContent, get_letta_message_content_union_str_json_schema
|
|
32
31
|
from letta.system import unpack_message
|
|
33
32
|
|
|
34
33
|
|
|
@@ -66,15 +65,30 @@ class MessageCreate(BaseModel):
|
|
|
66
65
|
MessageRole.user,
|
|
67
66
|
MessageRole.system,
|
|
68
67
|
] = Field(..., description="The role of the participant.")
|
|
69
|
-
content: Union[str, List[
|
|
68
|
+
content: Union[str, List[LettaMessageContentUnion]] = Field(
|
|
69
|
+
...,
|
|
70
|
+
description="The content of the message.",
|
|
71
|
+
json_schema_extra=get_letta_message_content_union_str_json_schema(),
|
|
72
|
+
)
|
|
70
73
|
name: Optional[str] = Field(None, description="The name of the participant.")
|
|
71
74
|
|
|
75
|
+
def model_dump(self, to_orm: bool = False, **kwargs) -> Dict[str, Any]:
|
|
76
|
+
data = super().model_dump(**kwargs)
|
|
77
|
+
if to_orm and "content" in data:
|
|
78
|
+
if isinstance(data["content"], str):
|
|
79
|
+
data["content"] = [TextContent(text=data["content"])]
|
|
80
|
+
return data
|
|
81
|
+
|
|
72
82
|
|
|
73
83
|
class MessageUpdate(BaseModel):
|
|
74
84
|
"""Request to update a message"""
|
|
75
85
|
|
|
76
86
|
role: Optional[MessageRole] = Field(None, description="The role of the participant.")
|
|
77
|
-
content: Optional[Union[str, List[
|
|
87
|
+
content: Optional[Union[str, List[LettaMessageContentUnion]]] = Field(
|
|
88
|
+
None,
|
|
89
|
+
description="The content of the message.",
|
|
90
|
+
json_schema_extra=get_letta_message_content_union_str_json_schema(),
|
|
91
|
+
)
|
|
78
92
|
# NOTE: probably doesn't make sense to allow remapping user_id or agent_id (vs creating a new message)
|
|
79
93
|
# user_id: Optional[str] = Field(None, description="The unique identifier of the user.")
|
|
80
94
|
# agent_id: Optional[str] = Field(None, description="The unique identifier of the agent.")
|
|
@@ -90,12 +104,7 @@ class MessageUpdate(BaseModel):
|
|
|
90
104
|
data = super().model_dump(**kwargs)
|
|
91
105
|
if to_orm and "content" in data:
|
|
92
106
|
if isinstance(data["content"], str):
|
|
93
|
-
data["
|
|
94
|
-
else:
|
|
95
|
-
for content in data["content"]:
|
|
96
|
-
if content["type"] == "text":
|
|
97
|
-
data["text"] = content["text"]
|
|
98
|
-
del data["content"]
|
|
107
|
+
data["content"] = [TextContent(text=data["content"])]
|
|
99
108
|
return data
|
|
100
109
|
|
|
101
110
|
|
|
@@ -119,7 +128,7 @@ class Message(BaseMessage):
|
|
|
119
128
|
|
|
120
129
|
id: str = BaseMessage.generate_id_field()
|
|
121
130
|
role: MessageRole = Field(..., description="The role of the participant.")
|
|
122
|
-
content: Optional[List[
|
|
131
|
+
content: Optional[List[LettaMessageContentUnion]] = Field(None, description="The content of the message.")
|
|
123
132
|
organization_id: Optional[str] = Field(None, description="The unique identifier of the organization.")
|
|
124
133
|
agent_id: Optional[str] = Field(None, description="The unique identifier of the agent.")
|
|
125
134
|
model: Optional[str] = Field(None, description="The model used to make the function call.")
|
|
@@ -129,6 +138,7 @@ class Message(BaseMessage):
|
|
|
129
138
|
step_id: Optional[str] = Field(None, description="The id of the step that this message was created in.")
|
|
130
139
|
otid: Optional[str] = Field(None, description="The offline threading id associated with this message")
|
|
131
140
|
tool_returns: Optional[List[ToolReturn]] = Field(None, description="Tool execution return information for prior tool calls")
|
|
141
|
+
group_id: Optional[str] = Field(None, description="The multi-agent group that the message was sent in")
|
|
132
142
|
|
|
133
143
|
# This overrides the optional base orm schema, created_at MUST exist on all messages objects
|
|
134
144
|
created_at: datetime = Field(default_factory=get_utc_time, description="The timestamp when the object was created.")
|
|
@@ -140,24 +150,6 @@ class Message(BaseMessage):
|
|
|
140
150
|
assert v in roles, f"Role must be one of {roles}"
|
|
141
151
|
return v
|
|
142
152
|
|
|
143
|
-
@model_validator(mode="before")
|
|
144
|
-
@classmethod
|
|
145
|
-
def convert_from_orm(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
146
|
-
if isinstance(data, dict):
|
|
147
|
-
if "text" in data and "content" not in data:
|
|
148
|
-
data["content"] = [TextContent(text=data["text"])]
|
|
149
|
-
del data["text"]
|
|
150
|
-
return data
|
|
151
|
-
|
|
152
|
-
def model_dump(self, to_orm: bool = False, **kwargs) -> Dict[str, Any]:
|
|
153
|
-
data = super().model_dump(**kwargs)
|
|
154
|
-
if to_orm:
|
|
155
|
-
for content in data["content"]:
|
|
156
|
-
if content["type"] == "text":
|
|
157
|
-
data["text"] = content["text"]
|
|
158
|
-
del data["content"]
|
|
159
|
-
return data
|
|
160
|
-
|
|
161
153
|
def to_json(self):
|
|
162
154
|
json_message = vars(self)
|
|
163
155
|
if json_message["tool_calls"] is not None:
|
|
@@ -214,7 +206,7 @@ class Message(BaseMessage):
|
|
|
214
206
|
assistant_message_tool_kwarg: str = DEFAULT_MESSAGE_TOOL_KWARG,
|
|
215
207
|
) -> List[LettaMessage]:
|
|
216
208
|
"""Convert message object (in DB format) to the style used by the original Letta API"""
|
|
217
|
-
if self.content and len(self.content) == 1 and self.content[0]
|
|
209
|
+
if self.content and len(self.content) == 1 and isinstance(self.content[0], TextContent):
|
|
218
210
|
text_content = self.content[0].text
|
|
219
211
|
else:
|
|
220
212
|
text_content = None
|
|
@@ -485,7 +477,7 @@ class Message(BaseMessage):
|
|
|
485
477
|
"""Go from Message class to ChatCompletion message object"""
|
|
486
478
|
|
|
487
479
|
# TODO change to pydantic casting, eg `return SystemMessageModel(self)`
|
|
488
|
-
if self.content and len(self.content) == 1 and self.content[0]
|
|
480
|
+
if self.content and len(self.content) == 1 and isinstance(self.content[0], TextContent):
|
|
489
481
|
text_content = self.content[0].text
|
|
490
482
|
else:
|
|
491
483
|
text_content = None
|
|
@@ -560,7 +552,7 @@ class Message(BaseMessage):
|
|
|
560
552
|
Args:
|
|
561
553
|
inner_thoughts_xml_tag (str): The XML tag to wrap around inner thoughts
|
|
562
554
|
"""
|
|
563
|
-
if self.content and len(self.content) == 1 and self.content[0]
|
|
555
|
+
if self.content and len(self.content) == 1 and isinstance(self.content[0], TextContent):
|
|
564
556
|
text_content = self.content[0].text
|
|
565
557
|
else:
|
|
566
558
|
text_content = None
|
|
@@ -655,7 +647,7 @@ class Message(BaseMessage):
|
|
|
655
647
|
# type Content: https://ai.google.dev/api/rest/v1/Content / https://ai.google.dev/api/rest/v1beta/Content
|
|
656
648
|
# parts[]: Part
|
|
657
649
|
# role: str ('user' or 'model')
|
|
658
|
-
if self.content and len(self.content) == 1 and self.content[0]
|
|
650
|
+
if self.content and len(self.content) == 1 and isinstance(self.content[0], TextContent):
|
|
659
651
|
text_content = self.content[0].text
|
|
660
652
|
else:
|
|
661
653
|
text_content = None
|
|
@@ -781,7 +773,7 @@ class Message(BaseMessage):
|
|
|
781
773
|
|
|
782
774
|
# TODO: update this prompt style once guidance from Cohere on
|
|
783
775
|
# embedded function calls in multi-turn conversation become more clear
|
|
784
|
-
if self.content and len(self.content) == 1 and self.content[0]
|
|
776
|
+
if self.content and len(self.content) == 1 and isinstance(self.content[0], TextContent):
|
|
785
777
|
text_content = self.content[0].text
|
|
786
778
|
else:
|
|
787
779
|
text_content = None
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from letta.serialize_schemas.
|
|
1
|
+
from letta.serialize_schemas.marshmallow_agent import MarshmallowAgentSchema
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from marshmallow import fields, post_dump, pre_load
|
|
4
|
+
|
|
5
|
+
import letta
|
|
6
|
+
from letta.orm import Agent
|
|
7
|
+
from letta.schemas.agent import AgentState as PydanticAgentState
|
|
8
|
+
from letta.schemas.user import User
|
|
9
|
+
from letta.serialize_schemas.marshmallow_agent_environment_variable import SerializedAgentEnvironmentVariableSchema
|
|
10
|
+
from letta.serialize_schemas.marshmallow_base import BaseSchema
|
|
11
|
+
from letta.serialize_schemas.marshmallow_block import SerializedBlockSchema
|
|
12
|
+
from letta.serialize_schemas.marshmallow_custom_fields import EmbeddingConfigField, LLMConfigField, ToolRulesField
|
|
13
|
+
from letta.serialize_schemas.marshmallow_message import SerializedMessageSchema
|
|
14
|
+
from letta.serialize_schemas.marshmallow_tag import SerializedAgentTagSchema
|
|
15
|
+
from letta.serialize_schemas.marshmallow_tool import SerializedToolSchema
|
|
16
|
+
from letta.server.db import SessionLocal
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MarshmallowAgentSchema(BaseSchema):
|
|
20
|
+
"""
|
|
21
|
+
Marshmallow schema for serializing/deserializing Agent objects.
|
|
22
|
+
Excludes relational fields.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__pydantic_model__ = PydanticAgentState
|
|
26
|
+
|
|
27
|
+
FIELD_VERSION = "version"
|
|
28
|
+
FIELD_MESSAGES = "messages"
|
|
29
|
+
FIELD_MESSAGE_IDS = "message_ids"
|
|
30
|
+
FIELD_IN_CONTEXT = "in_context"
|
|
31
|
+
FIELD_ID = "id"
|
|
32
|
+
|
|
33
|
+
llm_config = LLMConfigField()
|
|
34
|
+
embedding_config = EmbeddingConfigField()
|
|
35
|
+
tool_rules = ToolRulesField()
|
|
36
|
+
|
|
37
|
+
messages = fields.List(fields.Nested(SerializedMessageSchema))
|
|
38
|
+
core_memory = fields.List(fields.Nested(SerializedBlockSchema))
|
|
39
|
+
tools = fields.List(fields.Nested(SerializedToolSchema))
|
|
40
|
+
tool_exec_environment_variables = fields.List(fields.Nested(SerializedAgentEnvironmentVariableSchema))
|
|
41
|
+
tags = fields.List(fields.Nested(SerializedAgentTagSchema))
|
|
42
|
+
|
|
43
|
+
def __init__(self, *args, session: SessionLocal, actor: User, **kwargs):
|
|
44
|
+
super().__init__(*args, actor=actor, **kwargs)
|
|
45
|
+
self.session = session
|
|
46
|
+
|
|
47
|
+
# Propagate session and actor to nested schemas automatically
|
|
48
|
+
for field in self.fields.values():
|
|
49
|
+
if isinstance(field, fields.List) and isinstance(field.inner, fields.Nested):
|
|
50
|
+
field.inner.schema.session = session
|
|
51
|
+
field.inner.schema.actor = actor
|
|
52
|
+
elif isinstance(field, fields.Nested):
|
|
53
|
+
field.schema.session = session
|
|
54
|
+
field.schema.actor = actor
|
|
55
|
+
|
|
56
|
+
@post_dump
|
|
57
|
+
def sanitize_ids(self, data: Dict, **kwargs):
|
|
58
|
+
"""
|
|
59
|
+
- Removes `message_ids`
|
|
60
|
+
- Adds versioning
|
|
61
|
+
- Marks messages as in-context
|
|
62
|
+
- Removes individual message `id` fields
|
|
63
|
+
"""
|
|
64
|
+
data = super().sanitize_ids(data, **kwargs)
|
|
65
|
+
data[self.FIELD_VERSION] = letta.__version__
|
|
66
|
+
|
|
67
|
+
message_ids = set(data.pop(self.FIELD_MESSAGE_IDS, [])) # Store and remove message_ids
|
|
68
|
+
|
|
69
|
+
for message in data.get(self.FIELD_MESSAGES, []):
|
|
70
|
+
message[self.FIELD_IN_CONTEXT] = message[self.FIELD_ID] in message_ids # Mark messages as in-context
|
|
71
|
+
message.pop(self.FIELD_ID, None) # Remove the id field
|
|
72
|
+
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
@pre_load
|
|
76
|
+
def check_version(self, data, **kwargs):
|
|
77
|
+
"""Check version and remove it from the schema"""
|
|
78
|
+
version = data[self.FIELD_VERSION]
|
|
79
|
+
if version != letta.__version__:
|
|
80
|
+
print(f"Version mismatch: expected {letta.__version__}, got {version}")
|
|
81
|
+
del data[self.FIELD_VERSION]
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
@pre_load
|
|
85
|
+
def remap_in_context_messages(self, data, **kwargs):
|
|
86
|
+
"""
|
|
87
|
+
Restores `message_ids` by collecting message IDs where `in_context` is True,
|
|
88
|
+
generates new IDs for all messages, and removes `in_context` from all messages.
|
|
89
|
+
"""
|
|
90
|
+
message_ids = []
|
|
91
|
+
for msg in data.get(self.FIELD_MESSAGES, []):
|
|
92
|
+
msg[self.FIELD_ID] = SerializedMessageSchema.generate_id() # Generate new ID
|
|
93
|
+
if msg.pop(self.FIELD_IN_CONTEXT, False): # If it was in-context, track its new ID
|
|
94
|
+
message_ids.append(msg[self.FIELD_ID])
|
|
95
|
+
|
|
96
|
+
data[self.FIELD_MESSAGE_IDS] = message_ids
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
class Meta(BaseSchema.Meta):
|
|
100
|
+
model = Agent
|
|
101
|
+
exclude = BaseSchema.Meta.exclude + (
|
|
102
|
+
"project_id",
|
|
103
|
+
"template_id",
|
|
104
|
+
"base_template_id",
|
|
105
|
+
"sources",
|
|
106
|
+
"source_passages",
|
|
107
|
+
"agent_passages",
|
|
108
|
+
)
|
letta/serialize_schemas/{agent_environment_variable.py → marshmallow_agent_environment_variable.py}
RENAMED
|
@@ -2,7 +2,7 @@ import uuid
|
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from letta.orm.sandbox_config import AgentEnvironmentVariable
|
|
5
|
-
from letta.serialize_schemas.
|
|
5
|
+
from letta.serialize_schemas.marshmallow_base import BaseSchema
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class SerializedAgentEnvironmentVariableSchema(BaseSchema):
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
|
|
3
|
+
from marshmallow import post_dump, pre_load
|
|
4
|
+
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
|
5
|
+
|
|
6
|
+
from letta.schemas.user import User
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseSchema(SQLAlchemyAutoSchema):
|
|
10
|
+
"""
|
|
11
|
+
Base schema for all SQLAlchemy models.
|
|
12
|
+
This ensures all schemas share the same session.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__pydantic_model__ = None
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, actor: Optional[User] = None, **kwargs):
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
self.actor = actor
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def generate_id(cls) -> Optional[str]:
|
|
23
|
+
if cls.__pydantic_model__:
|
|
24
|
+
return cls.__pydantic_model__.generate_id()
|
|
25
|
+
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
@post_dump
|
|
29
|
+
def sanitize_ids(self, data: Dict, **kwargs) -> Dict:
|
|
30
|
+
# delete id
|
|
31
|
+
del data["id"]
|
|
32
|
+
del data["_created_by_id"]
|
|
33
|
+
del data["_last_updated_by_id"]
|
|
34
|
+
del data["organization"]
|
|
35
|
+
|
|
36
|
+
return data
|
|
37
|
+
|
|
38
|
+
@pre_load
|
|
39
|
+
def regenerate_ids(self, data: Dict, **kwargs) -> Dict:
|
|
40
|
+
if self.Meta.model:
|
|
41
|
+
data["id"] = self.generate_id()
|
|
42
|
+
data["_created_by_id"] = self.actor.id
|
|
43
|
+
data["_last_updated_by_id"] = self.actor.id
|
|
44
|
+
data["organization"] = self.actor.organization_id
|
|
45
|
+
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
class Meta:
|
|
49
|
+
model = None
|
|
50
|
+
include_relationships = True
|
|
51
|
+
load_instance = True
|
|
52
|
+
exclude = ()
|
|
@@ -3,10 +3,12 @@ from marshmallow import fields
|
|
|
3
3
|
from letta.helpers.converters import (
|
|
4
4
|
deserialize_embedding_config,
|
|
5
5
|
deserialize_llm_config,
|
|
6
|
+
deserialize_message_content,
|
|
6
7
|
deserialize_tool_calls,
|
|
7
8
|
deserialize_tool_rules,
|
|
8
9
|
serialize_embedding_config,
|
|
9
10
|
serialize_llm_config,
|
|
11
|
+
serialize_message_content,
|
|
10
12
|
serialize_tool_calls,
|
|
11
13
|
serialize_tool_rules,
|
|
12
14
|
)
|
|
@@ -67,3 +69,13 @@ class ToolCallField(fields.Field):
|
|
|
67
69
|
|
|
68
70
|
def _deserialize(self, value, attr, data, **kwargs):
|
|
69
71
|
return deserialize_tool_calls(value)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class MessageContentField(fields.Field):
|
|
75
|
+
"""Marshmallow field for handling a list of Message Content Part objects."""
|
|
76
|
+
|
|
77
|
+
def _serialize(self, value, attr, obj, **kwargs):
|
|
78
|
+
return serialize_message_content(value)
|
|
79
|
+
|
|
80
|
+
def _deserialize(self, value, attr, data, **kwargs):
|
|
81
|
+
return deserialize_message_content(value)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from marshmallow import post_dump, pre_load
|
|
4
|
+
|
|
5
|
+
from letta.orm.message import Message
|
|
6
|
+
from letta.schemas.message import Message as PydanticMessage
|
|
7
|
+
from letta.serialize_schemas.marshmallow_base import BaseSchema
|
|
8
|
+
from letta.serialize_schemas.marshmallow_custom_fields import ToolCallField
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SerializedMessageSchema(BaseSchema):
|
|
12
|
+
"""
|
|
13
|
+
Marshmallow schema for serializing/deserializing Message objects.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__pydantic_model__ = PydanticMessage
|
|
17
|
+
|
|
18
|
+
tool_calls = ToolCallField()
|
|
19
|
+
|
|
20
|
+
@post_dump
|
|
21
|
+
def sanitize_ids(self, data: Dict, **kwargs) -> Dict:
|
|
22
|
+
# keep id for remapping later on agent dump
|
|
23
|
+
# agent dump will then get rid of message ids
|
|
24
|
+
del data["_created_by_id"]
|
|
25
|
+
del data["_last_updated_by_id"]
|
|
26
|
+
del data["organization"]
|
|
27
|
+
|
|
28
|
+
return data
|
|
29
|
+
|
|
30
|
+
@pre_load
|
|
31
|
+
def regenerate_ids(self, data: Dict, **kwargs) -> Dict:
|
|
32
|
+
if self.Meta.model:
|
|
33
|
+
# Skip regenerating ID, as agent dump will do it
|
|
34
|
+
data["_created_by_id"] = self.actor.id
|
|
35
|
+
data["_last_updated_by_id"] = self.actor.id
|
|
36
|
+
data["organization"] = self.actor.organization_id
|
|
37
|
+
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
class Meta(BaseSchema.Meta):
|
|
41
|
+
model = Message
|
|
42
|
+
exclude = BaseSchema.Meta.exclude + ("step", "job_message", "agent", "otid", "is_deleted")
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from marshmallow import fields, post_dump, pre_load
|
|
2
4
|
|
|
3
5
|
from letta.orm.agents_tags import AgentsTags
|
|
4
|
-
from letta.serialize_schemas.
|
|
6
|
+
from letta.serialize_schemas.marshmallow_base import BaseSchema
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class SerializedAgentTagSchema(BaseSchema):
|
|
@@ -13,6 +15,14 @@ class SerializedAgentTagSchema(BaseSchema):
|
|
|
13
15
|
|
|
14
16
|
tag = fields.String(required=True)
|
|
15
17
|
|
|
18
|
+
@post_dump
|
|
19
|
+
def sanitize_ids(self, data: Dict, **kwargs):
|
|
20
|
+
return data
|
|
21
|
+
|
|
22
|
+
@pre_load
|
|
23
|
+
def regenerate_ids(self, data: Dict, **kwargs) -> Dict:
|
|
24
|
+
return data
|
|
25
|
+
|
|
16
26
|
class Meta(BaseSchema.Meta):
|
|
17
27
|
model = AgentsTags
|
|
18
28
|
exclude = BaseSchema.Meta.exclude + ("agent",)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from letta.schemas.embedding_config import EmbeddingConfig
|
|
6
|
+
from letta.schemas.letta_message_content import TextContent
|
|
7
|
+
from letta.schemas.llm_config import LLMConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CoreMemoryBlockSchema(BaseModel):
|
|
11
|
+
created_at: str
|
|
12
|
+
description: Optional[str]
|
|
13
|
+
identities: List[Any]
|
|
14
|
+
is_deleted: bool
|
|
15
|
+
is_template: bool
|
|
16
|
+
label: str
|
|
17
|
+
limit: int
|
|
18
|
+
metadata_: Dict[str, Any] = Field(default_factory=dict)
|
|
19
|
+
template_name: Optional[str]
|
|
20
|
+
updated_at: str
|
|
21
|
+
value: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MessageSchema(BaseModel):
|
|
25
|
+
created_at: str
|
|
26
|
+
group_id: Optional[str]
|
|
27
|
+
in_context: bool
|
|
28
|
+
model: Optional[str]
|
|
29
|
+
name: Optional[str]
|
|
30
|
+
role: str
|
|
31
|
+
content: List[TextContent] # TODO: Expand to more in the future
|
|
32
|
+
tool_call_id: Optional[str]
|
|
33
|
+
tool_calls: List[Any]
|
|
34
|
+
tool_returns: List[Any]
|
|
35
|
+
updated_at: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TagSchema(BaseModel):
|
|
39
|
+
tag: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ToolEnvVarSchema(BaseModel):
|
|
43
|
+
created_at: str
|
|
44
|
+
description: Optional[str]
|
|
45
|
+
is_deleted: bool
|
|
46
|
+
key: str
|
|
47
|
+
updated_at: str
|
|
48
|
+
value: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ToolRuleSchema(BaseModel):
|
|
52
|
+
tool_name: str
|
|
53
|
+
type: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ParameterProperties(BaseModel):
|
|
57
|
+
type: str
|
|
58
|
+
description: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ParametersSchema(BaseModel):
|
|
62
|
+
type: Optional[str] = "object"
|
|
63
|
+
properties: Dict[str, ParameterProperties]
|
|
64
|
+
required: List[str] = Field(default_factory=list)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ToolJSONSchema(BaseModel):
|
|
68
|
+
name: str
|
|
69
|
+
description: str
|
|
70
|
+
parameters: ParametersSchema # <— nested strong typing
|
|
71
|
+
type: Optional[str] = None # top-level 'type' if it exists
|
|
72
|
+
required: Optional[List[str]] = Field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ToolSchema(BaseModel):
|
|
76
|
+
args_json_schema: Optional[Any]
|
|
77
|
+
created_at: str
|
|
78
|
+
description: str
|
|
79
|
+
is_deleted: bool
|
|
80
|
+
json_schema: ToolJSONSchema
|
|
81
|
+
name: str
|
|
82
|
+
return_char_limit: int
|
|
83
|
+
source_code: Optional[str]
|
|
84
|
+
source_type: str
|
|
85
|
+
tags: List[str]
|
|
86
|
+
tool_type: str
|
|
87
|
+
updated_at: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AgentSchema(BaseModel):
|
|
91
|
+
agent_type: str
|
|
92
|
+
core_memory: List[CoreMemoryBlockSchema]
|
|
93
|
+
created_at: str
|
|
94
|
+
description: str
|
|
95
|
+
embedding_config: EmbeddingConfig
|
|
96
|
+
groups: List[Any]
|
|
97
|
+
identities: List[Any]
|
|
98
|
+
is_deleted: bool
|
|
99
|
+
llm_config: LLMConfig
|
|
100
|
+
message_buffer_autoclear: bool
|
|
101
|
+
messages: List[MessageSchema]
|
|
102
|
+
metadata_: Dict
|
|
103
|
+
multi_agent_group: Optional[Any]
|
|
104
|
+
name: str
|
|
105
|
+
system: str
|
|
106
|
+
tags: List[TagSchema]
|
|
107
|
+
tool_exec_environment_variables: List[ToolEnvVarSchema]
|
|
108
|
+
tool_rules: List[ToolRuleSchema]
|
|
109
|
+
tools: List[ToolSchema]
|
|
110
|
+
updated_at: str
|
|
111
|
+
version: str
|
letta/server/rest_api/app.py
CHANGED
|
@@ -17,6 +17,11 @@ from letta.errors import BedrockPermissionError, LettaAgentNotFoundError, LettaU
|
|
|
17
17
|
from letta.log import get_logger
|
|
18
18
|
from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
|
|
19
19
|
from letta.schemas.letta_message import create_letta_message_union_schema
|
|
20
|
+
from letta.schemas.letta_message_content import (
|
|
21
|
+
create_letta_assistant_message_content_union_schema,
|
|
22
|
+
create_letta_message_content_union_schema,
|
|
23
|
+
create_letta_user_message_content_union_schema,
|
|
24
|
+
)
|
|
20
25
|
from letta.server.constants import REST_DEFAULT_PORT
|
|
21
26
|
|
|
22
27
|
# NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests
|
|
@@ -68,6 +73,10 @@ def generate_openapi_schema(app: FastAPI):
|
|
|
68
73
|
letta_docs["paths"] = {k: v for k, v in letta_docs["paths"].items() if not k.startswith("/openai")}
|
|
69
74
|
letta_docs["info"]["title"] = "Letta API"
|
|
70
75
|
letta_docs["components"]["schemas"]["LettaMessageUnion"] = create_letta_message_union_schema()
|
|
76
|
+
letta_docs["components"]["schemas"]["LettaMessageContentUnion"] = create_letta_message_content_union_schema()
|
|
77
|
+
letta_docs["components"]["schemas"]["LettaAssistantMessageContentUnion"] = create_letta_assistant_message_content_union_schema()
|
|
78
|
+
letta_docs["components"]["schemas"]["LettaUserMessageContentUnion"] = create_letta_user_message_content_union_schema()
|
|
79
|
+
|
|
71
80
|
for name, docs in [
|
|
72
81
|
(
|
|
73
82
|
"letta",
|
|
@@ -320,6 +329,9 @@ def start_server(
|
|
|
320
329
|
app,
|
|
321
330
|
host=host or "localhost",
|
|
322
331
|
port=port or REST_DEFAULT_PORT,
|
|
332
|
+
workers=settings.uvicorn_workers,
|
|
333
|
+
reload=settings.uvicorn_reload,
|
|
334
|
+
timeout_keep_alive=settings.uvicorn_timeout_keep_alive,
|
|
323
335
|
ssl_keyfile="certs/localhost-key.pem",
|
|
324
336
|
ssl_certfile="certs/localhost.pem",
|
|
325
337
|
)
|
|
@@ -336,4 +348,7 @@ def start_server(
|
|
|
336
348
|
app,
|
|
337
349
|
host=host or "localhost",
|
|
338
350
|
port=port or REST_DEFAULT_PORT,
|
|
351
|
+
workers=settings.uvicorn_workers,
|
|
352
|
+
reload=settings.uvicorn_reload,
|
|
353
|
+
timeout_keep_alive=settings.uvicorn_timeout_keep_alive,
|
|
339
354
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from letta.server.rest_api.routers.v1.agents import router as agents_router
|
|
2
2
|
from letta.server.rest_api.routers.v1.blocks import router as blocks_router
|
|
3
|
+
from letta.server.rest_api.routers.v1.groups import router as groups_router
|
|
3
4
|
from letta.server.rest_api.routers.v1.health import router as health_router
|
|
4
5
|
from letta.server.rest_api.routers.v1.identities import router as identities_router
|
|
5
6
|
from letta.server.rest_api.routers.v1.jobs import router as jobs_router
|
|
@@ -17,6 +18,7 @@ ROUTERS = [
|
|
|
17
18
|
tools_router,
|
|
18
19
|
sources_router,
|
|
19
20
|
agents_router,
|
|
21
|
+
groups_router,
|
|
20
22
|
identities_router,
|
|
21
23
|
llm_router,
|
|
22
24
|
blocks_router,
|