letta-nightly 0.6.54.dev20250420104127__py3-none-any.whl → 0.7.0.dev20250421104249__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/constants.py +3 -3
- letta/data_sources/connectors.py +7 -1
- letta/errors.py +10 -0
- letta/llm_api/google_ai_client.py +3 -0
- letta/orm/message.py +1 -2
- letta/schemas/openai/chat_completion_request.py +2 -2
- letta/schemas/providers.py +0 -1
- letta/server/rest_api/routers/v1/providers.py +10 -7
- letta/server/server.py +8 -1
- letta/services/agent_manager.py +311 -230
- letta/services/block_manager.py +23 -0
- letta/services/helpers/agent_manager_helper.py +2 -0
- letta/services/provider_manager.py +2 -2
- {letta_nightly-0.6.54.dev20250420104127.dist-info → letta_nightly-0.7.0.dev20250421104249.dist-info}/METADATA +4 -16
- {letta_nightly-0.6.54.dev20250420104127.dist-info → letta_nightly-0.7.0.dev20250421104249.dist-info}/RECORD +19 -19
- {letta_nightly-0.6.54.dev20250420104127.dist-info → letta_nightly-0.7.0.dev20250421104249.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.54.dev20250420104127.dist-info → letta_nightly-0.7.0.dev20250421104249.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.54.dev20250420104127.dist-info → letta_nightly-0.7.0.dev20250421104249.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
letta/constants.py
CHANGED
@@ -60,9 +60,9 @@ BASE_SLEEPTIME_TOOLS = [
|
|
60
60
|
"memory_insert",
|
61
61
|
"memory_rethink",
|
62
62
|
"memory_finish_edits",
|
63
|
-
"archival_memory_insert",
|
64
|
-
"archival_memory_search",
|
65
|
-
"conversation_search",
|
63
|
+
# "archival_memory_insert",
|
64
|
+
# "archival_memory_search",
|
65
|
+
# "conversation_search",
|
66
66
|
]
|
67
67
|
# Multi agent tools
|
68
68
|
MULTI_AGENT_TOOLS = ["send_message_to_agent_and_wait_for_reply", "send_message_to_agents_matching_tags", "send_message_to_agent_async"]
|
letta/data_sources/connectors.py
CHANGED
@@ -159,7 +159,13 @@ class DirectoryConnector(DataConnector):
|
|
159
159
|
from llama_index.core.node_parser import TokenTextSplitter
|
160
160
|
|
161
161
|
parser = TokenTextSplitter(chunk_size=chunk_size)
|
162
|
-
|
162
|
+
if file.file_type == "application/pdf":
|
163
|
+
from llama_index.readers.file import PDFReader
|
164
|
+
|
165
|
+
reader = PDFReader()
|
166
|
+
documents = reader.load_data(file=file.file_path)
|
167
|
+
else:
|
168
|
+
documents = SimpleDirectoryReader(input_files=[file.file_path]).load_data()
|
163
169
|
nodes = parser.get_nodes_from_documents(documents)
|
164
170
|
for node in nodes:
|
165
171
|
yield node.text, None
|
letta/errors.py
CHANGED
@@ -204,3 +204,13 @@ class InvalidInnerMonologueError(LettaMessageError):
|
|
204
204
|
"""Error raised when a message has a malformed inner monologue."""
|
205
205
|
|
206
206
|
default_error_message = "The message has a malformed inner monologue."
|
207
|
+
|
208
|
+
|
209
|
+
class HandleNotFoundError(LettaError):
|
210
|
+
"""Error raised when a handle is not found."""
|
211
|
+
|
212
|
+
def __init__(self, handle: str, available_handles: List[str]):
|
213
|
+
super().__init__(
|
214
|
+
message=f"Handle {handle} not found, must be one of {available_handles}",
|
215
|
+
code=ErrorCode.NOT_FOUND,
|
216
|
+
)
|
@@ -56,6 +56,9 @@ class GoogleAIClient(LLMClientBase):
|
|
56
56
|
tool_names = [t.function.name for t in tool_objs]
|
57
57
|
# Convert to the exact payload style Google expects
|
58
58
|
tools = self.convert_tools_to_google_ai_format(tool_objs)
|
59
|
+
else:
|
60
|
+
tool_names = []
|
61
|
+
|
59
62
|
contents = self.add_dummy_model_messages(
|
60
63
|
[m.to_google_ai_dict() for m in messages],
|
61
64
|
)
|
letta/orm/message.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import List, Optional
|
2
2
|
|
3
3
|
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
|
4
|
-
from sqlalchemy import BigInteger, FetchedValue, ForeignKey, Index,
|
4
|
+
from sqlalchemy import BigInteger, FetchedValue, ForeignKey, Index, event, text
|
5
5
|
from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
|
6
6
|
|
7
7
|
from letta.orm.custom_columns import MessageContentColumn, ToolCallColumn, ToolReturnColumn
|
@@ -48,7 +48,6 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
48
48
|
# Monotonically increasing sequence for efficient/correct listing
|
49
49
|
sequence_id: Mapped[int] = mapped_column(
|
50
50
|
BigInteger,
|
51
|
-
Sequence("message_seq_id"),
|
52
51
|
server_default=FetchedValue(),
|
53
52
|
unique=True,
|
54
53
|
nullable=False,
|
@@ -46,7 +46,7 @@ ChatMessage = Union[SystemMessage, UserMessage, AssistantMessage, ToolMessage]
|
|
46
46
|
def cast_message_to_subtype(m_dict: dict) -> ChatMessage:
|
47
47
|
"""Cast a dictionary to one of the individual message types"""
|
48
48
|
role = m_dict.get("role")
|
49
|
-
if role == "system":
|
49
|
+
if role == "system" or role == "developer":
|
50
50
|
return SystemMessage(**m_dict)
|
51
51
|
elif role == "user":
|
52
52
|
return UserMessage(**m_dict)
|
@@ -55,7 +55,7 @@ def cast_message_to_subtype(m_dict: dict) -> ChatMessage:
|
|
55
55
|
elif role == "tool":
|
56
56
|
return ToolMessage(**m_dict)
|
57
57
|
else:
|
58
|
-
raise ValueError("Unknown message role")
|
58
|
+
raise ValueError(f"Unknown message role: {role}")
|
59
59
|
|
60
60
|
|
61
61
|
class ResponseFormat(BaseModel):
|
letta/schemas/providers.py
CHANGED
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|
11
11
|
router = APIRouter(prefix="/providers", tags=["providers"])
|
12
12
|
|
13
13
|
|
14
|
-
@router.get("/",
|
14
|
+
@router.get("/", response_model=List[Provider], operation_id="list_providers")
|
15
15
|
def list_providers(
|
16
16
|
after: Optional[str] = Query(None),
|
17
17
|
limit: Optional[int] = Query(50),
|
@@ -31,7 +31,7 @@ def list_providers(
|
|
31
31
|
return providers
|
32
32
|
|
33
33
|
|
34
|
-
@router.post("/",
|
34
|
+
@router.post("/", response_model=Provider, operation_id="create_provider")
|
35
35
|
def create_provider(
|
36
36
|
request: ProviderCreate = Body(...),
|
37
37
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -47,8 +47,9 @@ def create_provider(
|
|
47
47
|
return provider
|
48
48
|
|
49
49
|
|
50
|
-
@router.patch("/",
|
50
|
+
@router.patch("/{provider_id}", response_model=Provider, operation_id="modify_provider")
|
51
51
|
def modify_provider(
|
52
|
+
provider_id: str,
|
52
53
|
request: ProviderUpdate = Body(...),
|
53
54
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
54
55
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -57,13 +58,12 @@ def modify_provider(
|
|
57
58
|
Update an existing custom provider
|
58
59
|
"""
|
59
60
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
60
|
-
|
61
|
-
return provider
|
61
|
+
return server.provider_manager.update_provider(provider_id=provider_id, request=request, actor=actor)
|
62
62
|
|
63
63
|
|
64
|
-
@router.delete("/",
|
64
|
+
@router.delete("/{provider_id}", response_model=None, operation_id="delete_provider")
|
65
65
|
def delete_provider(
|
66
|
-
provider_id: str
|
66
|
+
provider_id: str,
|
67
67
|
actor_id: Optional[str] = Header(None, alias="user_id"),
|
68
68
|
server: "SyncServer" = Depends(get_letta_server),
|
69
69
|
):
|
@@ -73,6 +73,9 @@ def delete_provider(
|
|
73
73
|
try:
|
74
74
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
75
75
|
server.provider_manager.delete_provider_by_id(provider_id=provider_id, actor=actor)
|
76
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Provider id={provider_id} successfully deleted"})
|
77
|
+
except NoResultFound:
|
78
|
+
raise HTTPException(status_code=404, detail=f"Provider provider_id={provider_id} not found for user_id={actor.id}.")
|
76
79
|
except HTTPException:
|
77
80
|
raise
|
78
81
|
except Exception as e:
|
letta/server/server.py
CHANGED
@@ -20,6 +20,7 @@ import letta.system as system
|
|
20
20
|
from letta.agent import Agent, save_agent
|
21
21
|
from letta.config import LettaConfig
|
22
22
|
from letta.data_sources.connectors import DataConnector, load_data
|
23
|
+
from letta.errors import HandleNotFoundError
|
23
24
|
from letta.functions.mcp_client.base_client import BaseMCPClient
|
24
25
|
from letta.functions.mcp_client.sse_client import MCP_CONFIG_TOPLEVEL_KEY, SSEMCPClient
|
25
26
|
from letta.functions.mcp_client.stdio_client import StdioMCPClient
|
@@ -702,6 +703,8 @@ class SyncServer(Server):
|
|
702
703
|
@trace_method
|
703
704
|
def get_cached_llm_config(self, **kwargs):
|
704
705
|
key = make_key(**kwargs)
|
706
|
+
print(self._llm_config_cache)
|
707
|
+
print("KEY", key)
|
705
708
|
if key not in self._llm_config_cache:
|
706
709
|
self._llm_config_cache[key] = self.get_llm_config_from_handle(**kwargs)
|
707
710
|
return self._llm_config_cache[key]
|
@@ -1179,10 +1182,12 @@ class SyncServer(Server):
|
|
1179
1182
|
provider = self.get_provider_from_name(provider_name)
|
1180
1183
|
|
1181
1184
|
llm_configs = [config for config in provider.list_llm_models() if config.handle == handle]
|
1185
|
+
print("LLM CONFIGS", llm_configs)
|
1182
1186
|
if not llm_configs:
|
1183
1187
|
llm_configs = [config for config in provider.list_llm_models() if config.model == model_name]
|
1184
1188
|
if not llm_configs:
|
1185
|
-
|
1189
|
+
available_handles = [config.handle for config in provider.list_llm_models()]
|
1190
|
+
raise HandleNotFoundError(handle, available_handles)
|
1186
1191
|
except ValueError as e:
|
1187
1192
|
llm_configs = [config for config in self.get_local_llm_configs() if config.handle == handle]
|
1188
1193
|
if not llm_configs:
|
@@ -1190,6 +1195,8 @@ class SyncServer(Server):
|
|
1190
1195
|
if not llm_configs:
|
1191
1196
|
raise e
|
1192
1197
|
|
1198
|
+
print("CONFIGS", llm_configs)
|
1199
|
+
|
1193
1200
|
if len(llm_configs) == 1:
|
1194
1201
|
llm_config = llm_configs[0]
|
1195
1202
|
elif len(llm_configs) > 1:
|
letta/services/agent_manager.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from typing import Dict, List, Optional
|
1
|
+
from datetime import datetime, timezone
|
2
|
+
from typing import Dict, List, Optional, Set, Tuple
|
3
3
|
|
4
4
|
import numpy as np
|
5
|
-
|
5
|
+
import sqlalchemy as sa
|
6
|
+
from sqlalchemy import Select, and_, delete, func, insert, literal, or_, select, union_all
|
7
|
+
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
6
8
|
|
7
9
|
from letta.constants import (
|
8
10
|
BASE_MEMORY_TOOLS,
|
@@ -19,13 +21,16 @@ from letta.log import get_logger
|
|
19
21
|
from letta.orm import Agent as AgentModel
|
20
22
|
from letta.orm import AgentPassage, AgentsTags
|
21
23
|
from letta.orm import Block as BlockModel
|
24
|
+
from letta.orm import BlocksAgents
|
22
25
|
from letta.orm import Group as GroupModel
|
23
|
-
from letta.orm import
|
26
|
+
from letta.orm import IdentitiesAgents
|
24
27
|
from letta.orm import Source as SourceModel
|
25
28
|
from letta.orm import SourcePassage, SourcesAgents
|
26
29
|
from letta.orm import Tool as ToolModel
|
30
|
+
from letta.orm import ToolsAgents
|
27
31
|
from letta.orm.enums import ToolType
|
28
32
|
from letta.orm.errors import NoResultFound
|
33
|
+
from letta.orm.sandbox_config import AgentEnvironmentVariable
|
29
34
|
from letta.orm.sandbox_config import AgentEnvironmentVariable as AgentEnvironmentVariableModel
|
30
35
|
from letta.orm.sqlalchemy_base import AccessType
|
31
36
|
from letta.orm.sqlite_functions import adapt_array
|
@@ -36,16 +41,14 @@ from letta.schemas.block import BlockUpdate
|
|
36
41
|
from letta.schemas.embedding_config import EmbeddingConfig
|
37
42
|
from letta.schemas.group import Group as PydanticGroup
|
38
43
|
from letta.schemas.group import ManagerType
|
39
|
-
from letta.schemas.llm_config import LLMConfig
|
40
44
|
from letta.schemas.memory import Memory
|
45
|
+
from letta.schemas.message import Message
|
41
46
|
from letta.schemas.message import Message as PydanticMessage
|
42
47
|
from letta.schemas.message import MessageCreate, MessageUpdate
|
43
48
|
from letta.schemas.passage import Passage as PydanticPassage
|
44
49
|
from letta.schemas.source import Source as PydanticSource
|
45
50
|
from letta.schemas.tool import Tool as PydanticTool
|
46
|
-
from letta.schemas.tool_rule import ContinueToolRule
|
47
|
-
from letta.schemas.tool_rule import TerminalToolRule as PydanticTerminalToolRule
|
48
|
-
from letta.schemas.tool_rule import ToolRule as PydanticToolRule
|
51
|
+
from letta.schemas.tool_rule import ContinueToolRule, TerminalToolRule
|
49
52
|
from letta.schemas.user import User as PydanticUser
|
50
53
|
from letta.serialize_schemas import MarshmallowAgentSchema
|
51
54
|
from letta.serialize_schemas.marshmallow_message import SerializedMessageSchema
|
@@ -58,7 +61,6 @@ from letta.services.helpers.agent_manager_helper import (
|
|
58
61
|
_apply_pagination,
|
59
62
|
_apply_tag_filter,
|
60
63
|
_process_relationship,
|
61
|
-
_process_tags,
|
62
64
|
check_supports_structured_output,
|
63
65
|
compile_system_message,
|
64
66
|
derive_system_message,
|
@@ -77,7 +79,6 @@ from letta.utils import enforce_types, united_diff
|
|
77
79
|
logger = get_logger(__name__)
|
78
80
|
|
79
81
|
|
80
|
-
# Agent Manager Class
|
81
82
|
class AgentManager:
|
82
83
|
"""Manager class to handle business logic related to Agents."""
|
83
84
|
|
@@ -92,124 +93,218 @@ class AgentManager:
|
|
92
93
|
self.passage_manager = PassageManager()
|
93
94
|
self.identity_manager = IdentityManager()
|
94
95
|
|
96
|
+
@staticmethod
|
97
|
+
def _resolve_tools(session, names: Set[str], ids: Set[str], org_id: str) -> Tuple[Dict[str, str], Dict[str, str]]:
|
98
|
+
"""
|
99
|
+
Bulk‑fetch all ToolModel rows matching either name ∈ names or id ∈ ids
|
100
|
+
(and scoped to this organization), and return two maps:
|
101
|
+
name_to_id, id_to_name.
|
102
|
+
Raises if any requested name or id was not found.
|
103
|
+
"""
|
104
|
+
stmt = select(ToolModel.id, ToolModel.name).where(
|
105
|
+
ToolModel.organization_id == org_id,
|
106
|
+
or_(
|
107
|
+
ToolModel.name.in_(names),
|
108
|
+
ToolModel.id.in_(ids),
|
109
|
+
),
|
110
|
+
)
|
111
|
+
rows = session.execute(stmt).all()
|
112
|
+
name_to_id = {name: tid for tid, name in rows}
|
113
|
+
id_to_name = {tid: name for tid, name in rows}
|
114
|
+
|
115
|
+
missing_names = names - set(name_to_id.keys())
|
116
|
+
missing_ids = ids - set(id_to_name.keys())
|
117
|
+
if missing_names:
|
118
|
+
raise ValueError(f"Tools not found by name: {missing_names}")
|
119
|
+
if missing_ids:
|
120
|
+
raise ValueError(f"Tools not found by id: {missing_ids}")
|
121
|
+
|
122
|
+
return name_to_id, id_to_name
|
123
|
+
|
124
|
+
@staticmethod
|
125
|
+
@trace_method
|
126
|
+
def _bulk_insert_pivot(session, table, rows: list[dict]):
|
127
|
+
if not rows:
|
128
|
+
return
|
129
|
+
|
130
|
+
dialect = session.bind.dialect.name
|
131
|
+
if dialect == "postgresql":
|
132
|
+
stmt = pg_insert(table).values(rows).on_conflict_do_nothing()
|
133
|
+
elif dialect == "sqlite":
|
134
|
+
stmt = sa.insert(table).values(rows).prefix_with("OR IGNORE")
|
135
|
+
else:
|
136
|
+
# fallback: filter out exact-duplicate dicts in Python
|
137
|
+
seen = set()
|
138
|
+
filtered = []
|
139
|
+
for row in rows:
|
140
|
+
key = tuple(sorted(row.items()))
|
141
|
+
if key not in seen:
|
142
|
+
seen.add(key)
|
143
|
+
filtered.append(row)
|
144
|
+
stmt = sa.insert(table).values(filtered)
|
145
|
+
|
146
|
+
session.execute(stmt)
|
147
|
+
|
148
|
+
@staticmethod
|
149
|
+
@trace_method
|
150
|
+
def _replace_pivot_rows(session, table, agent_id: str, rows: list[dict]):
|
151
|
+
"""
|
152
|
+
Replace all pivot rows for an agent with *exactly* the provided list.
|
153
|
+
Uses two bulk statements (DELETE + INSERT ... ON CONFLICT DO NOTHING).
|
154
|
+
"""
|
155
|
+
# delete all existing rows for this agent
|
156
|
+
session.execute(delete(table).where(table.c.agent_id == agent_id))
|
157
|
+
if rows:
|
158
|
+
AgentManager._bulk_insert_pivot(session, table, rows)
|
159
|
+
|
95
160
|
# ======================================================================================================================
|
96
161
|
# Basic CRUD operations
|
97
162
|
# ======================================================================================================================
|
98
163
|
@trace_method
|
99
|
-
|
100
|
-
|
101
|
-
self,
|
102
|
-
agent_create: CreateAgent,
|
103
|
-
actor: PydanticUser,
|
104
|
-
) -> PydanticAgentState:
|
105
|
-
system = derive_system_message(
|
106
|
-
agent_type=agent_create.agent_type,
|
107
|
-
enable_sleeptime=agent_create.enable_sleeptime,
|
108
|
-
system=agent_create.system,
|
109
|
-
)
|
110
|
-
|
164
|
+
def create_agent(self, agent_create: CreateAgent, actor: PydanticUser) -> PydanticAgentState:
|
165
|
+
# validate required configs
|
111
166
|
if not agent_create.llm_config or not agent_create.embedding_config:
|
112
167
|
raise ValueError("llm_config and embedding_config are required")
|
113
168
|
|
114
|
-
#
|
115
|
-
block_ids = list(agent_create.block_ids or [])
|
169
|
+
# blocks
|
170
|
+
block_ids = list(agent_create.block_ids or [])
|
116
171
|
if agent_create.memory_blocks:
|
117
|
-
for
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
172
|
+
pydantic_blocks = [PydanticBlock(**b.model_dump(to_orm=True)) for b in agent_create.memory_blocks]
|
173
|
+
created_blocks = self.block_manager.batch_create_blocks(
|
174
|
+
pydantic_blocks,
|
175
|
+
actor=actor,
|
176
|
+
)
|
177
|
+
block_ids.extend([blk.id for blk in created_blocks])
|
123
178
|
|
124
|
-
#
|
179
|
+
# tools
|
180
|
+
tool_names = set(agent_create.tools or [])
|
125
181
|
if agent_create.include_base_tools:
|
126
182
|
if agent_create.agent_type == AgentType.sleeptime_agent:
|
127
|
-
tool_names
|
183
|
+
tool_names |= set(BASE_SLEEPTIME_TOOLS)
|
184
|
+
elif agent_create.enable_sleeptime:
|
185
|
+
tool_names |= set(BASE_SLEEPTIME_CHAT_TOOLS)
|
128
186
|
else:
|
129
|
-
|
130
|
-
tool_names.extend(BASE_SLEEPTIME_CHAT_TOOLS)
|
131
|
-
else:
|
132
|
-
tool_names.extend(BASE_TOOLS + BASE_MEMORY_TOOLS)
|
187
|
+
tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS)
|
133
188
|
if agent_create.include_multi_agent_tools:
|
134
|
-
tool_names
|
135
|
-
|
136
|
-
# remove duplicates
|
137
|
-
tool_names = list(set(tool_names))
|
138
|
-
|
139
|
-
# convert tool names to ids
|
140
|
-
tool_ids = []
|
141
|
-
for tool_name in tool_names:
|
142
|
-
tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
143
|
-
if not tool:
|
144
|
-
raise ValueError(f"Tool {tool_name} not found")
|
145
|
-
tool_ids.append(tool.id)
|
146
|
-
|
147
|
-
# add passed in `tool_ids`
|
148
|
-
for tool_id in agent_create.tool_ids or []:
|
149
|
-
if tool_id not in tool_ids:
|
150
|
-
tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
|
151
|
-
if tool:
|
152
|
-
tool_ids.append(tool.id)
|
153
|
-
tool_names.append(tool.name)
|
154
|
-
else:
|
155
|
-
raise ValueError(f"Tool {tool_id} not found")
|
156
|
-
|
157
|
-
# add default tool rules
|
158
|
-
tool_rules = agent_create.tool_rules or []
|
159
|
-
if agent_create.include_base_tool_rules:
|
160
|
-
# apply default tool rules
|
161
|
-
for tool_name in tool_names:
|
162
|
-
if tool_name == "send_message" or tool_name == "send_message_to_agent_async" or tool_name == "memory_finish_edits":
|
163
|
-
tool_rules.append(PydanticTerminalToolRule(tool_name=tool_name))
|
164
|
-
elif tool_name in BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS:
|
165
|
-
tool_rules.append(PydanticContinueToolRule(tool_name=tool_name))
|
166
|
-
|
167
|
-
# if custom rules, check tool rules are valid
|
168
|
-
if agent_create.tool_rules:
|
169
|
-
check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=agent_create.tool_rules)
|
170
|
-
|
171
|
-
# Create the agent
|
172
|
-
agent_state = self._create_agent(
|
173
|
-
name=agent_create.name,
|
174
|
-
system=system,
|
175
|
-
agent_type=agent_create.agent_type,
|
176
|
-
llm_config=agent_create.llm_config,
|
177
|
-
embedding_config=agent_create.embedding_config,
|
178
|
-
block_ids=block_ids,
|
179
|
-
tool_ids=tool_ids,
|
180
|
-
source_ids=agent_create.source_ids or [],
|
181
|
-
tags=agent_create.tags or [],
|
182
|
-
identity_ids=agent_create.identity_ids or [],
|
183
|
-
description=agent_create.description,
|
184
|
-
metadata=agent_create.metadata,
|
185
|
-
tool_rules=tool_rules,
|
186
|
-
actor=actor,
|
187
|
-
project_id=agent_create.project_id,
|
188
|
-
template_id=agent_create.template_id,
|
189
|
-
base_template_id=agent_create.base_template_id,
|
190
|
-
message_buffer_autoclear=agent_create.message_buffer_autoclear,
|
191
|
-
enable_sleeptime=agent_create.enable_sleeptime,
|
192
|
-
)
|
189
|
+
tool_names |= set(MULTI_AGENT_TOOLS)
|
193
190
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
actor=actor,
|
200
|
-
)
|
191
|
+
supplied_ids = set(agent_create.tool_ids or [])
|
192
|
+
|
193
|
+
source_ids = agent_create.source_ids or []
|
194
|
+
identity_ids = agent_create.identity_ids or []
|
195
|
+
tag_values = agent_create.tags or []
|
201
196
|
|
202
|
-
|
197
|
+
with self.session_maker() as session:
|
198
|
+
with session.begin():
|
199
|
+
name_to_id, id_to_name = self._resolve_tools(
|
200
|
+
session,
|
201
|
+
tool_names,
|
202
|
+
supplied_ids,
|
203
|
+
actor.organization_id,
|
204
|
+
)
|
205
|
+
|
206
|
+
tool_ids = set(name_to_id.values()) | set(id_to_name.keys())
|
207
|
+
tool_names = set(name_to_id.keys()) # now canonical
|
208
|
+
|
209
|
+
tool_rules = list(agent_create.tool_rules or [])
|
210
|
+
if agent_create.include_base_tool_rules:
|
211
|
+
for tn in tool_names:
|
212
|
+
if tn in {"send_message", "send_message_to_agent_async", "memory_finish_edits"}:
|
213
|
+
tool_rules.append(TerminalToolRule(tool_name=tn))
|
214
|
+
elif tn in (BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS):
|
215
|
+
tool_rules.append(ContinueToolRule(tool_name=tn))
|
216
|
+
|
217
|
+
if tool_rules:
|
218
|
+
check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=tool_rules)
|
219
|
+
|
220
|
+
new_agent = AgentModel(
|
221
|
+
name=agent_create.name,
|
222
|
+
system=derive_system_message(
|
223
|
+
agent_type=agent_create.agent_type,
|
224
|
+
enable_sleeptime=agent_create.enable_sleeptime,
|
225
|
+
system=agent_create.system,
|
226
|
+
),
|
227
|
+
agent_type=agent_create.agent_type,
|
228
|
+
llm_config=agent_create.llm_config,
|
229
|
+
embedding_config=agent_create.embedding_config,
|
230
|
+
organization_id=actor.organization_id,
|
231
|
+
description=agent_create.description,
|
232
|
+
metadata_=agent_create.metadata,
|
233
|
+
tool_rules=tool_rules,
|
234
|
+
project_id=agent_create.project_id,
|
235
|
+
template_id=agent_create.template_id,
|
236
|
+
base_template_id=agent_create.base_template_id,
|
237
|
+
message_buffer_autoclear=agent_create.message_buffer_autoclear,
|
238
|
+
enable_sleeptime=agent_create.enable_sleeptime,
|
239
|
+
created_by_id=actor.id,
|
240
|
+
last_updated_by_id=actor.id,
|
241
|
+
)
|
242
|
+
session.add(new_agent)
|
243
|
+
session.flush()
|
244
|
+
aid = new_agent.id
|
245
|
+
|
246
|
+
self._bulk_insert_pivot(
|
247
|
+
session,
|
248
|
+
ToolsAgents.__table__,
|
249
|
+
[{"agent_id": aid, "tool_id": tid} for tid in tool_ids],
|
250
|
+
)
|
251
|
+
|
252
|
+
if block_ids:
|
253
|
+
rows = [
|
254
|
+
{"agent_id": aid, "block_id": bid, "block_label": lbl}
|
255
|
+
for bid, lbl in session.execute(select(BlockModel.id, BlockModel.label).where(BlockModel.id.in_(block_ids))).all()
|
256
|
+
]
|
257
|
+
self._bulk_insert_pivot(session, BlocksAgents.__table__, rows)
|
258
|
+
|
259
|
+
self._bulk_insert_pivot(
|
260
|
+
session,
|
261
|
+
SourcesAgents.__table__,
|
262
|
+
[{"agent_id": aid, "source_id": sid} for sid in source_ids],
|
263
|
+
)
|
264
|
+
self._bulk_insert_pivot(
|
265
|
+
session,
|
266
|
+
AgentsTags.__table__,
|
267
|
+
[{"agent_id": aid, "tag": tag} for tag in tag_values],
|
268
|
+
)
|
269
|
+
self._bulk_insert_pivot(
|
270
|
+
session,
|
271
|
+
IdentitiesAgents.__table__,
|
272
|
+
[{"agent_id": aid, "identity_id": iid} for iid in identity_ids],
|
273
|
+
)
|
274
|
+
|
275
|
+
if agent_create.tool_exec_environment_variables:
|
276
|
+
env_rows = [
|
277
|
+
{
|
278
|
+
"agent_id": aid,
|
279
|
+
"key": key,
|
280
|
+
"value": val,
|
281
|
+
"organization_id": actor.organization_id,
|
282
|
+
}
|
283
|
+
for key, val in agent_create.tool_exec_environment_variables.items()
|
284
|
+
]
|
285
|
+
session.execute(insert(AgentEnvironmentVariable).values(env_rows))
|
286
|
+
|
287
|
+
# initial message sequence
|
288
|
+
init_messages = self._generate_initial_message_sequence(
|
289
|
+
actor,
|
290
|
+
agent_state=new_agent.to_pydantic(include_relationships={"memory"}),
|
291
|
+
supplied_initial_message_sequence=agent_create.initial_message_sequence,
|
292
|
+
)
|
293
|
+
new_agent.message_ids = [msg.id for msg in init_messages]
|
294
|
+
|
295
|
+
session.refresh(new_agent)
|
296
|
+
|
297
|
+
self.message_manager.create_many_messages(pydantic_msgs=init_messages, actor=actor)
|
298
|
+
return new_agent.to_pydantic()
|
203
299
|
|
204
300
|
@enforce_types
|
205
|
-
def
|
206
|
-
self, actor: PydanticUser, agent_state: PydanticAgentState,
|
207
|
-
) ->
|
301
|
+
def _generate_initial_message_sequence(
|
302
|
+
self, actor: PydanticUser, agent_state: PydanticAgentState, supplied_initial_message_sequence: Optional[List[MessageCreate]] = None
|
303
|
+
) -> List[Message]:
|
208
304
|
init_messages = initialize_message_sequence(
|
209
305
|
agent_state=agent_state, memory_edit_timestamp=get_utc_time(), include_initial_boot_message=True
|
210
306
|
)
|
211
|
-
|
212
|
-
if initial_message_sequence is not None:
|
307
|
+
if supplied_initial_message_sequence is not None:
|
213
308
|
# We always need the system prompt up front
|
214
309
|
system_message_obj = PydanticMessage.dict_to_message(
|
215
310
|
agent_id=agent_state.id,
|
@@ -219,7 +314,7 @@ class AgentManager:
|
|
219
314
|
# Don't use anything else in the pregen sequence, instead use the provided sequence
|
220
315
|
init_messages = [system_message_obj]
|
221
316
|
init_messages.extend(
|
222
|
-
package_initial_message_sequence(agent_state.id,
|
317
|
+
package_initial_message_sequence(agent_state.id, supplied_initial_message_sequence, agent_state.llm_config.model, actor)
|
223
318
|
)
|
224
319
|
else:
|
225
320
|
init_messages = [
|
@@ -227,145 +322,131 @@ class AgentManager:
|
|
227
322
|
for msg in init_messages
|
228
323
|
]
|
229
324
|
|
325
|
+
return init_messages
|
326
|
+
|
327
|
+
@enforce_types
|
328
|
+
def append_initial_message_sequence_to_in_context_messages(
|
329
|
+
self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None
|
330
|
+
) -> PydanticAgentState:
|
331
|
+
init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence)
|
230
332
|
return self.append_to_in_context_messages(init_messages, agent_id=agent_state.id, actor=actor)
|
231
333
|
|
232
334
|
@enforce_types
|
233
|
-
def
|
335
|
+
def update_agent(
|
234
336
|
self,
|
337
|
+
agent_id: str,
|
338
|
+
agent_update: UpdateAgent,
|
235
339
|
actor: PydanticUser,
|
236
|
-
name: str,
|
237
|
-
system: str,
|
238
|
-
agent_type: AgentType,
|
239
|
-
llm_config: LLMConfig,
|
240
|
-
embedding_config: EmbeddingConfig,
|
241
|
-
block_ids: List[str],
|
242
|
-
tool_ids: List[str],
|
243
|
-
source_ids: List[str],
|
244
|
-
tags: List[str],
|
245
|
-
identity_ids: List[str],
|
246
|
-
description: Optional[str] = None,
|
247
|
-
metadata: Optional[Dict] = None,
|
248
|
-
tool_rules: Optional[List[PydanticToolRule]] = None,
|
249
|
-
project_id: Optional[str] = None,
|
250
|
-
template_id: Optional[str] = None,
|
251
|
-
base_template_id: Optional[str] = None,
|
252
|
-
message_buffer_autoclear: bool = False,
|
253
|
-
enable_sleeptime: Optional[bool] = None,
|
254
340
|
) -> PydanticAgentState:
|
255
|
-
"""Create a new agent."""
|
256
|
-
with self.session_maker() as session:
|
257
|
-
# Prepare the agent data
|
258
|
-
data = {
|
259
|
-
"name": name,
|
260
|
-
"system": system,
|
261
|
-
"agent_type": agent_type,
|
262
|
-
"llm_config": llm_config,
|
263
|
-
"embedding_config": embedding_config,
|
264
|
-
"organization_id": actor.organization_id,
|
265
|
-
"description": description,
|
266
|
-
"metadata_": metadata,
|
267
|
-
"tool_rules": tool_rules,
|
268
|
-
"project_id": project_id,
|
269
|
-
"template_id": template_id,
|
270
|
-
"base_template_id": base_template_id,
|
271
|
-
"message_buffer_autoclear": message_buffer_autoclear,
|
272
|
-
"enable_sleeptime": enable_sleeptime,
|
273
|
-
}
|
274
|
-
|
275
|
-
# Create the new agent using SqlalchemyBase.create
|
276
|
-
new_agent = AgentModel(**data)
|
277
|
-
_process_relationship(session, new_agent, "tools", ToolModel, tool_ids, replace=True)
|
278
|
-
_process_relationship(session, new_agent, "sources", SourceModel, source_ids, replace=True)
|
279
|
-
_process_relationship(session, new_agent, "core_memory", BlockModel, block_ids, replace=True)
|
280
|
-
_process_tags(new_agent, tags, replace=True)
|
281
|
-
_process_relationship(session, new_agent, "identities", IdentityModel, identity_ids, replace=True)
|
282
341
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
342
|
+
new_tools = set(agent_update.tool_ids or [])
|
343
|
+
new_sources = set(agent_update.source_ids or [])
|
344
|
+
new_blocks = set(agent_update.block_ids or [])
|
345
|
+
new_idents = set(agent_update.identity_ids or [])
|
346
|
+
new_tags = set(agent_update.tags or [])
|
347
|
+
|
348
|
+
with self.session_maker() as session, session.begin():
|
349
|
+
|
350
|
+
agent: AgentModel = AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
|
351
|
+
agent.updated_at = datetime.now(timezone.utc)
|
352
|
+
agent.last_updated_by_id = actor.id
|
353
|
+
|
354
|
+
scalar_updates = {
|
355
|
+
"name": agent_update.name,
|
356
|
+
"system": agent_update.system,
|
357
|
+
"llm_config": agent_update.llm_config,
|
358
|
+
"embedding_config": agent_update.embedding_config,
|
359
|
+
"message_ids": agent_update.message_ids,
|
360
|
+
"tool_rules": agent_update.tool_rules,
|
361
|
+
"description": agent_update.description,
|
362
|
+
"project_id": agent_update.project_id,
|
363
|
+
"template_id": agent_update.template_id,
|
364
|
+
"base_template_id": agent_update.base_template_id,
|
365
|
+
"message_buffer_autoclear": agent_update.message_buffer_autoclear,
|
366
|
+
"enable_sleeptime": agent_update.enable_sleeptime,
|
367
|
+
}
|
368
|
+
for col, val in scalar_updates.items():
|
369
|
+
if val is not None:
|
370
|
+
setattr(agent, col, val)
|
287
371
|
|
288
|
-
|
289
|
-
|
290
|
-
agent_state = self._update_agent(agent_id=agent_id, agent_update=agent_update, actor=actor)
|
372
|
+
if agent_update.metadata is not None:
|
373
|
+
agent.metadata_ = agent_update.metadata
|
291
374
|
|
292
|
-
|
293
|
-
if agent_update.tool_exec_environment_variables:
|
294
|
-
agent_state = self._set_environment_variables(
|
295
|
-
agent_id=agent_state.id,
|
296
|
-
env_vars=agent_update.tool_exec_environment_variables,
|
297
|
-
actor=actor,
|
298
|
-
)
|
375
|
+
aid = agent.id
|
299
376
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
agent_state = self.rebuild_system_prompt(agent_id=agent_state.id, actor=actor, force=True, update_timestamp=False)
|
377
|
+
if agent_update.tool_ids is not None:
|
378
|
+
self._replace_pivot_rows(
|
379
|
+
session,
|
380
|
+
ToolsAgents.__table__,
|
381
|
+
aid,
|
382
|
+
[{"agent_id": aid, "tool_id": tid} for tid in new_tools],
|
383
|
+
)
|
384
|
+
session.expire(agent, ["tools"])
|
309
385
|
|
310
|
-
|
386
|
+
if agent_update.source_ids is not None:
|
387
|
+
self._replace_pivot_rows(
|
388
|
+
session,
|
389
|
+
SourcesAgents.__table__,
|
390
|
+
aid,
|
391
|
+
[{"agent_id": aid, "source_id": sid} for sid in new_sources],
|
392
|
+
)
|
393
|
+
session.expire(agent, ["sources"])
|
311
394
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
395
|
+
if agent_update.block_ids is not None:
|
396
|
+
rows = []
|
397
|
+
if new_blocks:
|
398
|
+
label_map = {
|
399
|
+
bid: lbl
|
400
|
+
for bid, lbl in session.execute(select(BlockModel.id, BlockModel.label).where(BlockModel.id.in_(new_blocks)))
|
401
|
+
}
|
402
|
+
rows = [{"agent_id": aid, "block_id": bid, "block_label": label_map[bid]} for bid in new_blocks]
|
316
403
|
|
317
|
-
|
318
|
-
|
319
|
-
agent_update: UpdateAgent object containing the updated fields.
|
320
|
-
actor: User performing the action.
|
404
|
+
self._replace_pivot_rows(session, BlocksAgents.__table__, aid, rows)
|
405
|
+
session.expire(agent, ["core_memory"])
|
321
406
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
407
|
+
if agent_update.identity_ids is not None:
|
408
|
+
self._replace_pivot_rows(
|
409
|
+
session,
|
410
|
+
IdentitiesAgents.__table__,
|
411
|
+
aid,
|
412
|
+
[{"agent_id": aid, "identity_id": iid} for iid in new_idents],
|
413
|
+
)
|
414
|
+
session.expire(agent, ["identities"])
|
328
415
|
|
329
|
-
# Update scalar fields directly
|
330
|
-
scalar_fields = {
|
331
|
-
"name",
|
332
|
-
"system",
|
333
|
-
"llm_config",
|
334
|
-
"embedding_config",
|
335
|
-
"message_ids",
|
336
|
-
"tool_rules",
|
337
|
-
"description",
|
338
|
-
"metadata",
|
339
|
-
"project_id",
|
340
|
-
"template_id",
|
341
|
-
"base_template_id",
|
342
|
-
"message_buffer_autoclear",
|
343
|
-
"enable_sleeptime",
|
344
|
-
}
|
345
|
-
for field in scalar_fields:
|
346
|
-
value = getattr(agent_update, field, None)
|
347
|
-
if value is not None:
|
348
|
-
if field == "metadata":
|
349
|
-
setattr(agent, "metadata_", value)
|
350
|
-
else:
|
351
|
-
setattr(agent, field, value)
|
352
|
-
|
353
|
-
# Update relationships using _process_relationship and _process_tags
|
354
|
-
if agent_update.tool_ids is not None:
|
355
|
-
_process_relationship(session, agent, "tools", ToolModel, agent_update.tool_ids, replace=True)
|
356
|
-
if agent_update.source_ids is not None:
|
357
|
-
_process_relationship(session, agent, "sources", SourceModel, agent_update.source_ids, replace=True)
|
358
|
-
if agent_update.block_ids is not None:
|
359
|
-
_process_relationship(session, agent, "core_memory", BlockModel, agent_update.block_ids, replace=True)
|
360
416
|
if agent_update.tags is not None:
|
361
|
-
|
362
|
-
|
363
|
-
|
417
|
+
self._replace_pivot_rows(
|
418
|
+
session,
|
419
|
+
AgentsTags.__table__,
|
420
|
+
aid,
|
421
|
+
[{"agent_id": aid, "tag": tag} for tag in new_tags],
|
422
|
+
)
|
423
|
+
session.expire(agent, ["tags"])
|
424
|
+
|
425
|
+
if agent_update.tool_exec_environment_variables is not None:
|
426
|
+
session.execute(delete(AgentEnvironmentVariable).where(AgentEnvironmentVariable.agent_id == aid))
|
427
|
+
env_rows = [
|
428
|
+
{
|
429
|
+
"agent_id": aid,
|
430
|
+
"key": k,
|
431
|
+
"value": v,
|
432
|
+
"organization_id": agent.organization_id,
|
433
|
+
}
|
434
|
+
for k, v in agent_update.tool_exec_environment_variables.items()
|
435
|
+
]
|
436
|
+
if env_rows:
|
437
|
+
self._bulk_insert_pivot(session, AgentEnvironmentVariable.__table__, env_rows)
|
438
|
+
session.expire(agent, ["tool_exec_environment_variables"])
|
439
|
+
|
440
|
+
if agent_update.enable_sleeptime and agent_update.system is None:
|
441
|
+
agent.system = derive_system_message(
|
442
|
+
agent_type=agent.agent_type,
|
443
|
+
enable_sleeptime=agent_update.enable_sleeptime,
|
444
|
+
system=agent.system,
|
445
|
+
)
|
364
446
|
|
365
|
-
|
366
|
-
|
447
|
+
session.flush()
|
448
|
+
session.refresh(agent)
|
367
449
|
|
368
|
-
# Convert to PydanticAgentState and return
|
369
450
|
return agent.to_pydantic()
|
370
451
|
|
371
452
|
# TODO: Make this general and think about how to roll this into sqlalchemybase
|
letta/services/block_manager.py
CHANGED
@@ -37,6 +37,29 @@ class BlockManager:
|
|
37
37
|
block.create(session, actor=actor)
|
38
38
|
return block.to_pydantic()
|
39
39
|
|
40
|
+
@enforce_types
|
41
|
+
def batch_create_blocks(self, blocks: List[PydanticBlock], actor: PydanticUser) -> List[PydanticBlock]:
|
42
|
+
"""
|
43
|
+
Batch-create multiple Blocks in one transaction for better performance.
|
44
|
+
Args:
|
45
|
+
blocks: List of PydanticBlock schemas to create
|
46
|
+
actor: The user performing the operation
|
47
|
+
Returns:
|
48
|
+
List of created PydanticBlock instances (with IDs, timestamps, etc.)
|
49
|
+
"""
|
50
|
+
if not blocks:
|
51
|
+
return []
|
52
|
+
|
53
|
+
with self.session_maker() as session:
|
54
|
+
block_models = [
|
55
|
+
BlockModel(**block.model_dump(to_orm=True, exclude_none=True), organization_id=actor.organization_id) for block in blocks
|
56
|
+
]
|
57
|
+
|
58
|
+
created_models = BlockModel.batch_create(items=block_models, db_session=session, actor=actor)
|
59
|
+
|
60
|
+
# Convert back to Pydantic
|
61
|
+
return [m.to_pydantic() for m in created_models]
|
62
|
+
|
40
63
|
@enforce_types
|
41
64
|
def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock:
|
42
65
|
"""Update a block by its ID with the given BlockUpdate object."""
|
@@ -21,9 +21,11 @@ from letta.schemas.passage import Passage as PydanticPassage
|
|
21
21
|
from letta.schemas.tool_rule import ToolRule
|
22
22
|
from letta.schemas.user import User
|
23
23
|
from letta.system import get_initial_boot_messages, get_login_event
|
24
|
+
from letta.tracing import trace_method
|
24
25
|
|
25
26
|
|
26
27
|
# Static methods
|
28
|
+
@trace_method
|
27
29
|
def _process_relationship(
|
28
30
|
session, agent: AgentModel, relationship_name: str, model_class, item_ids: List[str], allow_partial=False, replace=True
|
29
31
|
):
|
@@ -29,11 +29,11 @@ class ProviderManager:
|
|
29
29
|
return new_provider.to_pydantic()
|
30
30
|
|
31
31
|
@enforce_types
|
32
|
-
def update_provider(self, provider_update: ProviderUpdate, actor: PydanticUser) -> PydanticProvider:
|
32
|
+
def update_provider(self, provider_id: str, provider_update: ProviderUpdate, actor: PydanticUser) -> PydanticProvider:
|
33
33
|
"""Update provider details."""
|
34
34
|
with self.session_maker() as session:
|
35
35
|
# Retrieve the existing provider by ID
|
36
|
-
existing_provider = ProviderModel.read(db_session=session, identifier=
|
36
|
+
existing_provider = ProviderModel.read(db_session=session, identifier=provider_id, actor=actor)
|
37
37
|
|
38
38
|
# Update only the fields that are provided in ProviderUpdate
|
39
39
|
update_data = provider_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: letta-nightly
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0.dev20250421104249
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
5
5
|
License: Apache License
|
6
6
|
Author: Letta Team
|
@@ -51,11 +51,12 @@ Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
|
|
51
51
|
Requires-Dist: jinja2 (>=3.1.5,<4.0.0)
|
52
52
|
Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
|
53
53
|
Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
|
54
|
-
Requires-Dist: letta_client (>=0.1.
|
54
|
+
Requires-Dist: letta_client (>=0.1.124,<0.2.0)
|
55
55
|
Requires-Dist: llama-index (>=0.12.2,<0.13.0)
|
56
56
|
Requires-Dist: llama-index-embeddings-openai (>=0.3.1,<0.4.0)
|
57
57
|
Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "desktop" or extra == "all"
|
58
58
|
Requires-Dist: marshmallow-sqlalchemy (>=1.4.1,<2.0.0)
|
59
|
+
Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
|
59
60
|
Requires-Dist: mcp (>=1.3.0,<2.0.0)
|
60
61
|
Requires-Dist: nltk (>=3.8.1,<4.0.0)
|
61
62
|
Requires-Dist: numpy (>=1.26.2,<2.0.0)
|
@@ -107,26 +108,13 @@ Description-Content-Type: text/markdown
|
|
107
108
|
|
108
109
|
<div align="center">
|
109
110
|
<h1>Letta (previously MemGPT)</h1>
|
110
|
-
|
111
|
-
**☄️ New release: Letta Agent Development Environment (_read more [here](#-access-the-ade-agent-development-environment)_) ☄️**
|
112
|
-
|
113
|
-
<p align="center">
|
114
|
-
<picture>
|
115
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png">
|
116
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot_light.png">
|
117
|
-
<img alt="Letta logo" src="https://raw.githubusercontent.com/letta-ai/letta/refs/heads/main/assets/example_ade_screenshot.png" width="800">
|
118
|
-
</picture>
|
119
|
-
</p>
|
120
|
-
|
121
|
-
---
|
122
|
-
|
123
111
|
<h3>
|
124
112
|
|
125
113
|
[Homepage](https://letta.com) // [Documentation](https://docs.letta.com) // [ADE](https://docs.letta.com/agent-development-environment) // [Letta Cloud](https://forms.letta.com/early-access)
|
126
114
|
|
127
115
|
</h3>
|
128
116
|
|
129
|
-
**👾 Letta** is an open source framework for building
|
117
|
+
**👾 Letta** is an open source framework for building **stateful agents** with advanced reasoning capabilities and transparent long-term memory. The Letta framework is white box and model-agnostic.
|
130
118
|
|
131
119
|
[](https://discord.gg/letta)
|
132
120
|
[](https://twitter.com/Letta_AI)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
letta/__init__.py,sha256=
|
1
|
+
letta/__init__.py,sha256=EQrpCriqdG699Hu-3af2VUn66tanxOkvy4kEeGRrOT8,917
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
3
3
|
letta/agent.py,sha256=V7Hn2xU1wfp16bfuRsaGMntln49OTc05yiemM5A8XI0,70279
|
4
4
|
letta/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -19,11 +19,11 @@ letta/client/client.py,sha256=3uNsMqQyebbjFeWHHyqcManPUH7JzkBzmA9OgOLQrQs,137582
|
|
19
19
|
letta/client/streaming.py,sha256=UsDS_tDTsA3HgYryIDvGGmx_dWfnfQwtmEwLi4Z89Ik,4701
|
20
20
|
letta/client/utils.py,sha256=VCGV-op5ZSmurd4yw7Vhf93XDQ0BkyBT8qsuV7EqfiU,2859
|
21
21
|
letta/config.py,sha256=JFGY4TWW0Wm5fTbZamOwWqk5G8Nn-TXyhgByGoAqy2c,12375
|
22
|
-
letta/constants.py,sha256=
|
23
|
-
letta/data_sources/connectors.py,sha256=
|
22
|
+
letta/constants.py,sha256=munrDJiDg06_9natYWv9mMu8OOS_tr0CdosA0YF5xQw,8571
|
23
|
+
letta/data_sources/connectors.py,sha256=m4ALtjlaZpSlJnlq2VMNzx0onowNpjvoa4ZjAB5SMQw,7243
|
24
24
|
letta/data_sources/connectors_helper.py,sha256=oQpVlc-BjSz9sTZ7sp4PsJSXJbBKpZPi3Dam03CURTQ,3376
|
25
25
|
letta/embeddings.py,sha256=KvC2bl5tARpVY9xcFmw4Cwu1vN0DoH266v2mSUZqwkY,10528
|
26
|
-
letta/errors.py,sha256=
|
26
|
+
letta/errors.py,sha256=GCgFBvluPxebfz1_wB0y-82N8rlVPhGIn-x7UfV3wVU,6961
|
27
27
|
letta/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
letta/functions/ast_parsers.py,sha256=CQI0rUoIXcLKAev_GYrXcldRIGN5ZQtk5u4FLoHe5sE,5728
|
29
29
|
letta/functions/function_sets/base.py,sha256=Z-trOG7dKbOWpJlsEBNSGlwcQnPuRXqnQ9NMed98nbY,16392
|
@@ -73,7 +73,7 @@ letta/llm_api/azure_openai.py,sha256=YAkXwKyfnJFNhB45pkJVFsoxUNB_M74rQYchtw_CN6I
|
|
73
73
|
letta/llm_api/azure_openai_constants.py,sha256=ZaR2IasJThijG0uhLKJThrixdAxLPD2IojfeaJ-KQMQ,294
|
74
74
|
letta/llm_api/cohere.py,sha256=7Be_t5jt5EtWX8UUScJkX-IF9SpiZw9cDEcRC6PaxUM,14841
|
75
75
|
letta/llm_api/deepseek.py,sha256=b1mSW8gnBrpAI8d2GcBpDyLYDnuC-P1UP6xJPalfQS4,12456
|
76
|
-
letta/llm_api/google_ai_client.py,sha256=
|
76
|
+
letta/llm_api/google_ai_client.py,sha256=_uXwB5GcDO4kBTs2qtwy3Yz82rkgn8j4ytqYJDmhSW8,22686
|
77
77
|
letta/llm_api/google_constants.py,sha256=1dqwt-YwdYGnAHV5rIPfGHfE3ybPzSn_48vlNYfd-bk,588
|
78
78
|
letta/llm_api/google_vertex_client.py,sha256=hDj0pthZrht-dsdzyE6vZYBuSzyiLazSdcgpI76lEqg,11144
|
79
79
|
letta/llm_api/helpers.py,sha256=sLYv30UnKBRVPuhU_KDXfKFdbkUONiDAyVEwGr86l3A,16780
|
@@ -147,7 +147,7 @@ letta/orm/job.py,sha256=PuSaDJlUNYRISgtmzjJFX1744q1cx5gixlSAtiIs19o,2597
|
|
147
147
|
letta/orm/job_messages.py,sha256=SgwaYPYwwAC3dBtl9Xye_TWUl9H_-m95S95TTcfPyOg,1245
|
148
148
|
letta/orm/llm_batch_items.py,sha256=eoB4Vi90GC4FIGQIpv4RYoJtuLFy_14VEGWdW2Wy95U,2756
|
149
149
|
letta/orm/llm_batch_job.py,sha256=LaeOrnNf6FMm6ff2kOCEAjtbSuz4C5KYU5OmM90F1PY,2368
|
150
|
-
letta/orm/message.py,sha256=
|
150
|
+
letta/orm/message.py,sha256=1IMlyvpfw1HqJel_V_tA5GxkNnJiE9mwO0mNgAIFam8,4685
|
151
151
|
letta/orm/mixins.py,sha256=9c79Kfr-Z1hL-SDYKeoptx_yMTbBwJJBo9nrKEzSDAc,1622
|
152
152
|
letta/orm/organization.py,sha256=STQ5x5zXoPhfagiRQX6j2lWgOqwznPp-K019MPjbY0s,3599
|
153
153
|
letta/orm/passage.py,sha256=luoQMBAm2DwWpcGtQziMDjDP5JZZNv1pnEmx-InNHFo,3090
|
@@ -214,14 +214,14 @@ letta/schemas/llm_config.py,sha256=UA3_a4qv5Kb_jMQSCEZLZ0PLwVyOC332SxIcvZBM5bA,7
|
|
214
214
|
letta/schemas/llm_config_overrides.py,sha256=-oRglCTcajF6UAK3RAa0FLWVuKODPI1v403fDIWMAtA,1815
|
215
215
|
letta/schemas/memory.py,sha256=GOYDfPKzbWftUWO9Hv4KW7xAi1EIQmC8zpP7qvEkVHw,10245
|
216
216
|
letta/schemas/message.py,sha256=cXcQjwzNE7tbwZGuFu-x8T_yUb3EyVrbHYZKSJ4GpbA,48520
|
217
|
-
letta/schemas/openai/chat_completion_request.py,sha256=
|
217
|
+
letta/schemas/openai/chat_completion_request.py,sha256=PZHzx--ooqJLsHTo1NpHwfywm5L8Id-CUXaKR6eXuko,4171
|
218
218
|
letta/schemas/openai/chat_completion_response.py,sha256=yoepGZkg5PIobGqvATJruPdV4odpIUDHWniodSQo3PY,4433
|
219
219
|
letta/schemas/openai/chat_completions.py,sha256=l0e9sT9boTD5VBU5YtJ0s7qUtCfFGB2K-gQLeEZ2LHU,3599
|
220
220
|
letta/schemas/openai/embedding_response.py,sha256=WKIZpXab1Av7v6sxKG8feW3ZtpQUNosmLVSuhXYa_xU,357
|
221
221
|
letta/schemas/openai/openai.py,sha256=Hilo5BiLAGabzxCwnwfzK5QrWqwYD8epaEKFa4Pwndk,7970
|
222
222
|
letta/schemas/organization.py,sha256=TXrHN4IBQnX-mWvRuCOH57XZSLYCVOY0wWm2_UzDQIA,1279
|
223
223
|
letta/schemas/passage.py,sha256=RG0vkaewEu4a_NAZM-FVyMammHjqpPP0RDYAdu27g6A,3723
|
224
|
-
letta/schemas/providers.py,sha256=
|
224
|
+
letta/schemas/providers.py,sha256=lUz9QvMm_-wUUJZ5OGRsefsora0Y_55s3xQwnzL8gOw,51643
|
225
225
|
letta/schemas/run.py,sha256=SRqPRziINIiPunjOhE_NlbnQYgxTvqmbauni_yfBQRA,2085
|
226
226
|
letta/schemas/sandbox_config.py,sha256=SZCo3FSMz-DIBMKGu0atT4tsVFXGsqMFPaJnjrxpkX4,5993
|
227
227
|
letta/schemas/source.py,sha256=IuenIFs7B8uOuYJIHXqR1E28wVSa-pUX6NkLZH7cukg,3141
|
@@ -265,7 +265,7 @@ letta/server/rest_api/routers/v1/jobs.py,sha256=4oeJfI2odNGubU_g7WSORJhn_usFsbRa
|
|
265
265
|
letta/server/rest_api/routers/v1/llms.py,sha256=lYp5URXtZk1yu_Pe-p1Wq1uQ0qeb6aWtx78rXSB7N_E,881
|
266
266
|
letta/server/rest_api/routers/v1/messages.py,sha256=bHB51Qu1q3IngwaYXLPdn7gmi3K66rf-9iLkdTWcpBI,4530
|
267
267
|
letta/server/rest_api/routers/v1/organizations.py,sha256=r7rj-cA3shgAgM0b2JCMqjYsDIFv3ruZjU7SYbPGGqg,2831
|
268
|
-
letta/server/rest_api/routers/v1/providers.py,sha256=
|
268
|
+
letta/server/rest_api/routers/v1/providers.py,sha256=MVfAUvXj_2jx8XFwSigM-8CuCfEATW60h8J5UEmAhp0,3146
|
269
269
|
letta/server/rest_api/routers/v1/runs.py,sha256=9nuJRjBtRgZPq3CiCEUA_3S2xPHFP5DsJxIenH5OO34,8847
|
270
270
|
letta/server/rest_api/routers/v1/sandbox_configs.py,sha256=9hqnnMwJ3wCwO-Bezu3Xl8i3TDSIuInw3gSeHaKUXfE,8526
|
271
271
|
letta/server/rest_api/routers/v1/sources.py,sha256=igrT2w2fkwMDhw8LZMjRug00YG5mYg08C6ZLpRHeYq4,10270
|
@@ -276,7 +276,7 @@ letta/server/rest_api/routers/v1/users.py,sha256=G5DBHSkPfBgVHN2Wkm-rVYiLQAudwQc
|
|
276
276
|
letta/server/rest_api/routers/v1/voice.py,sha256=0lerWjrKLkt4gXLhZl1cIcgstOz9Q2HZwc67L58BCXE,2451
|
277
277
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
278
278
|
letta/server/rest_api/utils.py,sha256=OKUWg7u4vmROVweqRrs83bQvS958bZAoR_bUFDwwqsc,14861
|
279
|
-
letta/server/server.py,sha256=
|
279
|
+
letta/server/server.py,sha256=M_x7zMlLu1jCBA2ocMND2ZFT50l50KZ7AqVRcoxu834,81484
|
280
280
|
letta/server/startup.sh,sha256=MRXh1RKbS5lyA7XAsk7O6Q4LEKOqnv5B-dwe0SnTHeQ,2514
|
281
281
|
letta/server/static_files/assets/index-048c9598.js,sha256=mR16XppvselwKCcNgONs4L7kZEVa4OEERm4lNZYtLSk,146819
|
282
282
|
letta/server/static_files/assets/index-0e31b727.css,sha256=SBbja96uiQVLDhDOroHgM6NSl7tS4lpJRCREgSS_hA8,7672
|
@@ -290,10 +290,10 @@ letta/server/ws_api/interface.py,sha256=TWl9vkcMCnLsUtgsuENZ-ku2oMDA-OUTzLh_yNRo
|
|
290
290
|
letta/server/ws_api/protocol.py,sha256=5mDgpfNZn_kNwHnpt5Dsuw8gdNH298sgxTGed3etzYg,1836
|
291
291
|
letta/server/ws_api/server.py,sha256=cBSzf-V4zT1bL_0i54OTI3cMXhTIIxqjSRF8pYjk7fg,5835
|
292
292
|
letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
293
|
-
letta/services/agent_manager.py,sha256=
|
294
|
-
letta/services/block_manager.py,sha256=
|
293
|
+
letta/services/agent_manager.py,sha256=kng5jZenzuA-a2LLB1mDSA-nbDmR3kQdCRltAnIBtwY,70852
|
294
|
+
letta/services/block_manager.py,sha256=rphbpGBIEDFvCJ5GJt6A1OfbURGFQZ3WardljELQyAc,15225
|
295
295
|
letta/services/group_manager.py,sha256=z1woWRRnjkWrGG_auSicXr2bJaJ653JV6PYl2N_AUfw,12224
|
296
|
-
letta/services/helpers/agent_manager_helper.py,sha256=
|
296
|
+
letta/services/helpers/agent_manager_helper.py,sha256=57ARg5TcmE_JdrWmhz5uaNHAt1NGlJ3wQH1tP2XOiAs,17825
|
297
297
|
letta/services/helpers/tool_execution_helper.py,sha256=lLoebs1kZKjw62y1PxHbIDkHq_heJN2ZT0gKje-R8oo,6941
|
298
298
|
letta/services/identity_manager.py,sha256=PqnUnM3OGCRnd5NPjGonwYBnYAQK8rpiissdNYUouGU,9389
|
299
299
|
letta/services/job_manager.py,sha256=Z9cSD-IqpiOIWE_UOTBUEIxGITrJ10JT1G3ske-0yIY,17166
|
@@ -302,7 +302,7 @@ letta/services/message_manager.py,sha256=_n7mij6bXyHqn4lppszjiXVQ7gHaiyPKEM_t6k8
|
|
302
302
|
letta/services/organization_manager.py,sha256=Ax0KmPSc_YYsYaxeld9gc7ST-J6DemHQ542DD7l7AWA,3989
|
303
303
|
letta/services/passage_manager.py,sha256=KY18gHTbx8ROBsOeR7ZAefTMGZwzbxYqOjbadqVFiyQ,9121
|
304
304
|
letta/services/per_agent_lock_manager.py,sha256=porM0cKKANQ1FvcGXOO_qM7ARk5Fgi1HVEAhXsAg9-4,546
|
305
|
-
letta/services/provider_manager.py,sha256=
|
305
|
+
letta/services/provider_manager.py,sha256=_gEBW0tYIf2vJEGGYxk-nvogrFI9sjFl_97MSL5WC2s,3759
|
306
306
|
letta/services/sandbox_config_manager.py,sha256=ATgZNWNpkdIQDUPy4ABsguHQba2PZf51-c4Ji60MzLE,13361
|
307
307
|
letta/services/source_manager.py,sha256=SE24AiPPhpvZMGDD047H3_ZDD7OM4zHbTW1JXjPEv7U,7672
|
308
308
|
letta/services/step_manager.py,sha256=B64iYn6Dt9yRKsSJ5vLxWQR2t-apvPLfUZyzrUsJTpI,5335
|
@@ -325,8 +325,8 @@ letta/streaming_utils.py,sha256=jLqFTVhUL76FeOuYk8TaRQHmPTf3HSRc2EoJwxJNK6U,1194
|
|
325
325
|
letta/system.py,sha256=dnOrS2FlRMwijQnOvfrky0Lg8wEw-FUq2zzfAJOUSKA,8477
|
326
326
|
letta/tracing.py,sha256=RstWXpfWVF77nmb_ISORVWd9IQw2Ky3de40k_S70yKI,8258
|
327
327
|
letta/utils.py,sha256=IZFvtj9WYcrxUbkoUUYGDxMYQYdn5SgfqsvnARGsAzc,32245
|
328
|
-
letta_nightly-0.
|
329
|
-
letta_nightly-0.
|
330
|
-
letta_nightly-0.
|
331
|
-
letta_nightly-0.
|
332
|
-
letta_nightly-0.
|
328
|
+
letta_nightly-0.7.0.dev20250421104249.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
329
|
+
letta_nightly-0.7.0.dev20250421104249.dist-info/METADATA,sha256=QWbK2qhFH3whOhriePYciTizbTeu3j8mL8l7xDolra8,22282
|
330
|
+
letta_nightly-0.7.0.dev20250421104249.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
331
|
+
letta_nightly-0.7.0.dev20250421104249.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
332
|
+
letta_nightly-0.7.0.dev20250421104249.dist-info/RECORD,,
|
File without changes
|
File without changes
|