letta-nightly 0.5.1.dev20241028104150__py3-none-any.whl → 0.5.1.dev20241029104141__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/cli/cli.py +2 -4
- letta/client/client.py +17 -61
- letta/llm_api/llm_api_tools.py +0 -1
- letta/orm/base.py +10 -5
- letta/orm/sqlalchemy_base.py +50 -63
- letta/orm/tool.py +3 -12
- letta/orm/user.py +1 -3
- letta/providers.py +1 -1
- letta/schemas/tool.py +5 -18
- letta/server/rest_api/routers/v1/agents.py +2 -4
- letta/server/rest_api/routers/v1/tools.py +8 -9
- letta/server/server.py +48 -23
- letta/server/startup.sh +2 -2
- letta/services/tool_manager.py +32 -66
- letta/services/user_manager.py +2 -5
- {letta_nightly-0.5.1.dev20241028104150.dist-info → letta_nightly-0.5.1.dev20241029104141.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.1.dev20241028104150.dist-info → letta_nightly-0.5.1.dev20241029104141.dist-info}/RECORD +20 -20
- {letta_nightly-0.5.1.dev20241028104150.dist-info → letta_nightly-0.5.1.dev20241029104141.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241028104150.dist-info → letta_nightly-0.5.1.dev20241029104141.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.1.dev20241028104150.dist-info → letta_nightly-0.5.1.dev20241029104141.dist-info}/entry_points.txt +0 -0
letta/cli/cli.py
CHANGED
|
@@ -218,9 +218,7 @@ def run(
|
|
|
218
218
|
)
|
|
219
219
|
|
|
220
220
|
# create agent
|
|
221
|
-
tools = [
|
|
222
|
-
server.tool_manager.get_tool_by_name_and_user_id(tool_name=tool_name, user_id=client.user_id) for tool_name in agent_state.tools
|
|
223
|
-
]
|
|
221
|
+
tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.tools]
|
|
224
222
|
letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools)
|
|
225
223
|
|
|
226
224
|
else: # create new agent
|
|
@@ -300,7 +298,7 @@ def run(
|
|
|
300
298
|
)
|
|
301
299
|
assert isinstance(agent_state.memory, Memory), f"Expected Memory, got {type(agent_state.memory)}"
|
|
302
300
|
typer.secho(f"-> 🛠️ {len(agent_state.tools)} tools: {', '.join([t for t in agent_state.tools])}", fg=typer.colors.WHITE)
|
|
303
|
-
tools = [server.tool_manager.
|
|
301
|
+
tools = [server.tool_manager.get_tool_by_name(tool_name, actor=client.user) for tool_name in agent_state.tools]
|
|
304
302
|
|
|
305
303
|
letta_agent = Agent(
|
|
306
304
|
interface=interface(),
|
letta/client/client.py
CHANGED
|
@@ -276,10 +276,10 @@ class AbstractClient(object):
|
|
|
276
276
|
) -> List[Message]:
|
|
277
277
|
raise NotImplementedError
|
|
278
278
|
|
|
279
|
-
def
|
|
279
|
+
def list_model_configs(self) -> List[LLMConfig]:
|
|
280
280
|
raise NotImplementedError
|
|
281
281
|
|
|
282
|
-
def
|
|
282
|
+
def list_embedding_configs(self) -> List[EmbeddingConfig]:
|
|
283
283
|
raise NotImplementedError
|
|
284
284
|
|
|
285
285
|
|
|
@@ -1234,32 +1234,6 @@ class RESTClient(AbstractClient):
|
|
|
1234
1234
|
assert response.status_code == 200, f"Failed to detach source from agent: {response.text}"
|
|
1235
1235
|
return Source(**response.json())
|
|
1236
1236
|
|
|
1237
|
-
# server configuration commands
|
|
1238
|
-
|
|
1239
|
-
def list_models(self):
|
|
1240
|
-
"""
|
|
1241
|
-
List available LLM models
|
|
1242
|
-
|
|
1243
|
-
Returns:
|
|
1244
|
-
models (List[LLMConfig]): List of LLM models
|
|
1245
|
-
"""
|
|
1246
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/models", headers=self.headers)
|
|
1247
|
-
if response.status_code != 200:
|
|
1248
|
-
raise ValueError(f"Failed to list models: {response.text}")
|
|
1249
|
-
return [LLMConfig(**model) for model in response.json()]
|
|
1250
|
-
|
|
1251
|
-
def list_embedding_models(self):
|
|
1252
|
-
"""
|
|
1253
|
-
List available embedding models
|
|
1254
|
-
|
|
1255
|
-
Returns:
|
|
1256
|
-
models (List[EmbeddingConfig]): List of embedding models
|
|
1257
|
-
"""
|
|
1258
|
-
response = requests.get(f"{self.base_url}/{self.api_prefix}/models/embedding", headers=self.headers)
|
|
1259
|
-
if response.status_code != 200:
|
|
1260
|
-
raise ValueError(f"Failed to list embedding models: {response.text}")
|
|
1261
|
-
return [EmbeddingConfig(**model) for model in response.json()]
|
|
1262
|
-
|
|
1263
1237
|
# tools
|
|
1264
1238
|
|
|
1265
1239
|
def get_tool_id(self, tool_name: str):
|
|
@@ -1546,6 +1520,9 @@ class LocalClient(AbstractClient):
|
|
|
1546
1520
|
# get default user
|
|
1547
1521
|
self.user_id = self.server.user_manager.DEFAULT_USER_ID
|
|
1548
1522
|
|
|
1523
|
+
self.user = self.server.get_user_or_default(self.user_id)
|
|
1524
|
+
self.organization = self.server.get_organization_or_default(self.org_id)
|
|
1525
|
+
|
|
1549
1526
|
# agents
|
|
1550
1527
|
def list_agents(self) -> List[AgentState]:
|
|
1551
1528
|
self.interface.clear()
|
|
@@ -1648,7 +1625,7 @@ class LocalClient(AbstractClient):
|
|
|
1648
1625
|
llm_config=llm_config if llm_config else self._default_llm_config,
|
|
1649
1626
|
embedding_config=embedding_config if embedding_config else self._default_embedding_config,
|
|
1650
1627
|
),
|
|
1651
|
-
|
|
1628
|
+
actor=self.user,
|
|
1652
1629
|
)
|
|
1653
1630
|
return agent_state
|
|
1654
1631
|
|
|
@@ -1720,7 +1697,7 @@ class LocalClient(AbstractClient):
|
|
|
1720
1697
|
message_ids=message_ids,
|
|
1721
1698
|
memory=memory,
|
|
1722
1699
|
),
|
|
1723
|
-
|
|
1700
|
+
actor=self.user,
|
|
1724
1701
|
)
|
|
1725
1702
|
return agent_state
|
|
1726
1703
|
|
|
@@ -2198,24 +2175,22 @@ class LocalClient(AbstractClient):
|
|
|
2198
2175
|
def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
2199
2176
|
tool_create = ToolCreate.from_langchain(
|
|
2200
2177
|
langchain_tool=langchain_tool,
|
|
2201
|
-
user_id=self.user_id,
|
|
2202
2178
|
organization_id=self.org_id,
|
|
2203
2179
|
additional_imports_module_attr_map=additional_imports_module_attr_map,
|
|
2204
2180
|
)
|
|
2205
|
-
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2181
|
+
return self.server.tool_manager.create_or_update_tool(tool_create, actor=self.user)
|
|
2206
2182
|
|
|
2207
2183
|
def load_crewai_tool(self, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
2208
2184
|
tool_create = ToolCreate.from_crewai(
|
|
2209
2185
|
crewai_tool=crewai_tool,
|
|
2210
2186
|
additional_imports_module_attr_map=additional_imports_module_attr_map,
|
|
2211
|
-
user_id=self.user_id,
|
|
2212
2187
|
organization_id=self.org_id,
|
|
2213
2188
|
)
|
|
2214
|
-
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2189
|
+
return self.server.tool_manager.create_or_update_tool(tool_create, actor=self.user)
|
|
2215
2190
|
|
|
2216
2191
|
def load_composio_tool(self, action: "ActionType") -> Tool:
|
|
2217
|
-
tool_create = ToolCreate.from_composio(action=action,
|
|
2218
|
-
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2192
|
+
tool_create = ToolCreate.from_composio(action=action, organization_id=self.org_id)
|
|
2193
|
+
return self.server.tool_manager.create_or_update_tool(tool_create, actor=self.user)
|
|
2219
2194
|
|
|
2220
2195
|
# TODO: Use the above function `add_tool` here as there is duplicate logic
|
|
2221
2196
|
def create_tool(
|
|
@@ -2250,14 +2225,13 @@ class LocalClient(AbstractClient):
|
|
|
2250
2225
|
# call server function
|
|
2251
2226
|
return self.server.tool_manager.create_or_update_tool(
|
|
2252
2227
|
ToolCreate(
|
|
2253
|
-
user_id=self.user_id,
|
|
2254
|
-
organization_id=self.org_id,
|
|
2255
2228
|
source_type=source_type,
|
|
2256
2229
|
source_code=source_code,
|
|
2257
2230
|
name=name,
|
|
2258
2231
|
tags=tags,
|
|
2259
2232
|
terminal=terminal,
|
|
2260
2233
|
),
|
|
2234
|
+
actor=self.user,
|
|
2261
2235
|
)
|
|
2262
2236
|
|
|
2263
2237
|
def update_tool(
|
|
@@ -2289,7 +2263,7 @@ class LocalClient(AbstractClient):
|
|
|
2289
2263
|
# Filter out any None values from the dictionary
|
|
2290
2264
|
update_data = {key: value for key, value in update_data.items() if value is not None}
|
|
2291
2265
|
|
|
2292
|
-
return self.server.tool_manager.update_tool_by_id(id, ToolUpdate(**update_data))
|
|
2266
|
+
return self.server.tool_manager.update_tool_by_id(tool_id=id, tool_update=ToolUpdate(**update_data), actor=self.user)
|
|
2293
2267
|
|
|
2294
2268
|
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
2295
2269
|
"""
|
|
@@ -2298,7 +2272,7 @@ class LocalClient(AbstractClient):
|
|
|
2298
2272
|
Returns:
|
|
2299
2273
|
tools (List[Tool]): List of tools
|
|
2300
2274
|
"""
|
|
2301
|
-
return self.server.tool_manager.
|
|
2275
|
+
return self.server.tool_manager.list_tools(cursor=cursor, limit=limit, actor=self.user)
|
|
2302
2276
|
|
|
2303
2277
|
def get_tool(self, id: str) -> Optional[Tool]:
|
|
2304
2278
|
"""
|
|
@@ -2310,7 +2284,7 @@ class LocalClient(AbstractClient):
|
|
|
2310
2284
|
Returns:
|
|
2311
2285
|
tool (Tool): Tool
|
|
2312
2286
|
"""
|
|
2313
|
-
return self.server.tool_manager.get_tool_by_id(id)
|
|
2287
|
+
return self.server.tool_manager.get_tool_by_id(id, actor=self.user)
|
|
2314
2288
|
|
|
2315
2289
|
def delete_tool(self, id: str):
|
|
2316
2290
|
"""
|
|
@@ -2319,7 +2293,7 @@ class LocalClient(AbstractClient):
|
|
|
2319
2293
|
Args:
|
|
2320
2294
|
id (str): ID of the tool
|
|
2321
2295
|
"""
|
|
2322
|
-
return self.server.tool_manager.delete_tool_by_id(id)
|
|
2296
|
+
return self.server.tool_manager.delete_tool_by_id(id, user_id=self.user_id)
|
|
2323
2297
|
|
|
2324
2298
|
def get_tool_id(self, name: str) -> Optional[str]:
|
|
2325
2299
|
"""
|
|
@@ -2331,7 +2305,7 @@ class LocalClient(AbstractClient):
|
|
|
2331
2305
|
Returns:
|
|
2332
2306
|
id (str): ID of the tool (`None` if not found)
|
|
2333
2307
|
"""
|
|
2334
|
-
tool = self.server.tool_manager.
|
|
2308
|
+
tool = self.server.tool_manager.get_tool_by_name(tool_name=name, actor=self.user)
|
|
2335
2309
|
return tool.id
|
|
2336
2310
|
|
|
2337
2311
|
def load_data(self, connector: DataConnector, source_name: str):
|
|
@@ -2572,24 +2546,6 @@ class LocalClient(AbstractClient):
|
|
|
2572
2546
|
return_message_object=True,
|
|
2573
2547
|
)
|
|
2574
2548
|
|
|
2575
|
-
def list_models(self) -> List[LLMConfig]:
|
|
2576
|
-
"""
|
|
2577
|
-
List available LLM models
|
|
2578
|
-
|
|
2579
|
-
Returns:
|
|
2580
|
-
models (List[LLMConfig]): List of LLM models
|
|
2581
|
-
"""
|
|
2582
|
-
return self.server.list_models()
|
|
2583
|
-
|
|
2584
|
-
def list_embedding_models(self) -> List[EmbeddingConfig]:
|
|
2585
|
-
"""
|
|
2586
|
-
List available embedding models
|
|
2587
|
-
|
|
2588
|
-
Returns:
|
|
2589
|
-
models (List[EmbeddingConfig]): List of embedding models
|
|
2590
|
-
"""
|
|
2591
|
-
return [self.server.server_embedding_config]
|
|
2592
|
-
|
|
2593
2549
|
def list_blocks(self, label: Optional[str] = None, templates_only: Optional[bool] = True) -> List[Block]:
|
|
2594
2550
|
"""
|
|
2595
2551
|
List available blocks
|
letta/llm_api/llm_api_tools.py
CHANGED
|
@@ -313,7 +313,6 @@ def create(
|
|
|
313
313
|
stream_interface.stream_start()
|
|
314
314
|
try:
|
|
315
315
|
# groq uses the openai chat completions API, so this component should be reusable
|
|
316
|
-
assert model_settings.groq_api_key is not None, "Groq key is missing"
|
|
317
316
|
response = openai_chat_completions_request(
|
|
318
317
|
url=llm_config.model_endpoint,
|
|
319
318
|
api_key=model_settings.groq_api_key,
|
letta/orm/base.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Optional
|
|
3
|
-
from uuid import UUID
|
|
4
3
|
|
|
5
|
-
from sqlalchemy import
|
|
6
|
-
from sqlalchemy import Boolean, DateTime, func, text
|
|
4
|
+
from sqlalchemy import Boolean, DateTime, String, func, text
|
|
7
5
|
from sqlalchemy.orm import (
|
|
8
6
|
DeclarativeBase,
|
|
9
7
|
Mapped,
|
|
@@ -25,6 +23,13 @@ class CommonSqlalchemyMetaMixins(Base):
|
|
|
25
23
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), server_default=func.now(), server_onupdate=func.now())
|
|
26
24
|
is_deleted: Mapped[bool] = mapped_column(Boolean, server_default=text("FALSE"))
|
|
27
25
|
|
|
26
|
+
def _set_created_and_updated_by_fields(self, actor_id: str) -> None:
|
|
27
|
+
"""Populate created_by_id and last_updated_by_id based on actor."""
|
|
28
|
+
if not self.created_by_id:
|
|
29
|
+
self.created_by_id = actor_id
|
|
30
|
+
# Always set the last_updated_by_id when updating
|
|
31
|
+
self.last_updated_by_id = actor_id
|
|
32
|
+
|
|
28
33
|
@declared_attr
|
|
29
34
|
def _created_by_id(cls):
|
|
30
35
|
return cls._user_by_id()
|
|
@@ -38,7 +43,7 @@ class CommonSqlalchemyMetaMixins(Base):
|
|
|
38
43
|
"""a flexible non-constrained record of a user.
|
|
39
44
|
This way users can get added, deleted etc without history freaking out
|
|
40
45
|
"""
|
|
41
|
-
return mapped_column(
|
|
46
|
+
return mapped_column(String, nullable=True)
|
|
42
47
|
|
|
43
48
|
@property
|
|
44
49
|
def last_updated_by_id(self) -> Optional[str]:
|
|
@@ -72,4 +77,4 @@ class CommonSqlalchemyMetaMixins(Base):
|
|
|
72
77
|
return
|
|
73
78
|
prefix, id_ = value.split("-", 1)
|
|
74
79
|
assert prefix == "user", f"{prefix} is not a valid id prefix for a user id"
|
|
75
|
-
setattr(self, full_prop,
|
|
80
|
+
setattr(self, full_prop, id_)
|
letta/orm/sqlalchemy_base.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, List, Literal, Optional, Type
|
|
2
|
-
from uuid import
|
|
1
|
+
from typing import TYPE_CHECKING, List, Literal, Optional, Type
|
|
2
|
+
from uuid import uuid4
|
|
3
3
|
|
|
4
4
|
from humps import depascalize
|
|
5
5
|
from sqlalchemy import Boolean, String, select
|
|
@@ -88,7 +88,7 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
88
88
|
def read(
|
|
89
89
|
cls,
|
|
90
90
|
db_session: "Session",
|
|
91
|
-
identifier:
|
|
91
|
+
identifier: Optional[str] = None,
|
|
92
92
|
actor: Optional["User"] = None,
|
|
93
93
|
access: Optional[List[Literal["read", "write", "admin"]]] = ["read"],
|
|
94
94
|
**kwargs,
|
|
@@ -105,19 +105,29 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
105
105
|
Raises:
|
|
106
106
|
NoResultFound: if the object is not found
|
|
107
107
|
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
|
|
108
|
+
# Start the query
|
|
109
|
+
query = select(cls)
|
|
110
|
+
|
|
111
|
+
# If an identifier is provided, add it to the query conditions
|
|
112
|
+
if identifier is not None:
|
|
113
|
+
identifier = cls.get_uid_from_identifier(identifier)
|
|
114
|
+
query = query.where(cls._id == identifier)
|
|
115
|
+
|
|
116
|
+
if kwargs:
|
|
117
|
+
query = query.filter_by(**kwargs)
|
|
118
|
+
|
|
119
|
+
if actor:
|
|
120
|
+
query = cls.apply_access_predicate(query, actor, access)
|
|
121
|
+
|
|
113
122
|
if hasattr(cls, "is_deleted"):
|
|
114
123
|
query = query.where(cls.is_deleted == False)
|
|
115
124
|
if found := db_session.execute(query).scalar():
|
|
116
125
|
return found
|
|
117
126
|
raise NoResultFound(f"{cls.__name__} with id {identifier} not found")
|
|
118
127
|
|
|
119
|
-
def create(self, db_session: "Session") -> Type["SqlalchemyBase"]:
|
|
120
|
-
|
|
128
|
+
def create(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
|
|
129
|
+
if actor:
|
|
130
|
+
self._set_created_and_updated_by_fields(actor.id)
|
|
121
131
|
|
|
122
132
|
with db_session as session:
|
|
123
133
|
session.add(self)
|
|
@@ -125,11 +135,17 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
125
135
|
session.refresh(self)
|
|
126
136
|
return self
|
|
127
137
|
|
|
128
|
-
def delete(self, db_session: "Session") -> Type["SqlalchemyBase"]:
|
|
138
|
+
def delete(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
|
|
139
|
+
if actor:
|
|
140
|
+
self._set_created_and_updated_by_fields(actor.id)
|
|
141
|
+
|
|
129
142
|
self.is_deleted = True
|
|
130
143
|
return self.update(db_session)
|
|
131
144
|
|
|
132
|
-
def update(self, db_session: "Session") -> Type["SqlalchemyBase"]:
|
|
145
|
+
def update(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
|
|
146
|
+
if actor:
|
|
147
|
+
self._set_created_and_updated_by_fields(actor.id)
|
|
148
|
+
|
|
133
149
|
with db_session as session:
|
|
134
150
|
session.add(self)
|
|
135
151
|
session.commit()
|
|
@@ -137,39 +153,28 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
137
153
|
return self
|
|
138
154
|
|
|
139
155
|
@classmethod
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# access:
|
|
163
|
-
# what mode of access should the query restrict to? This will be used with granular permissions,
|
|
164
|
-
# but because of how it will impact every query we want to be explicitly calling access ahead of time.
|
|
165
|
-
# Returns:
|
|
166
|
-
# the sqlalchemy select statement restricted to the given access.
|
|
167
|
-
# """
|
|
168
|
-
# del access # entrypoint for row-level permissions. Defaults to "same org as the actor, all permissions" at the moment
|
|
169
|
-
# org_uid = getattr(actor, "_organization_id", getattr(actor.organization, "_id", None))
|
|
170
|
-
# if not org_uid:
|
|
171
|
-
# raise ValueError("object %s has no organization accessor", actor)
|
|
172
|
-
# return query.where(cls._organization_id == org_uid, cls.is_deleted == False)
|
|
156
|
+
def apply_access_predicate(
|
|
157
|
+
cls,
|
|
158
|
+
query: "Select",
|
|
159
|
+
actor: "User",
|
|
160
|
+
access: List[Literal["read", "write", "admin"]],
|
|
161
|
+
) -> "Select":
|
|
162
|
+
"""applies a WHERE clause restricting results to the given actor and access level
|
|
163
|
+
Args:
|
|
164
|
+
query: The initial sqlalchemy select statement
|
|
165
|
+
actor: The user acting on the query. **Note**: this is called 'actor' to identify the
|
|
166
|
+
person or system acting. Users can act on users, making naming very sticky otherwise.
|
|
167
|
+
access:
|
|
168
|
+
what mode of access should the query restrict to? This will be used with granular permissions,
|
|
169
|
+
but because of how it will impact every query we want to be explicitly calling access ahead of time.
|
|
170
|
+
Returns:
|
|
171
|
+
the sqlalchemy select statement restricted to the given access.
|
|
172
|
+
"""
|
|
173
|
+
del access # entrypoint for row-level permissions. Defaults to "same org as the actor, all permissions" at the moment
|
|
174
|
+
org_id = getattr(actor, "organization_id", None)
|
|
175
|
+
if not org_id:
|
|
176
|
+
raise ValueError(f"object {actor} has no organization accessor")
|
|
177
|
+
return query.where(cls._organization_id == cls.get_uid_from_identifier(org_id, indifferent=True), cls.is_deleted == False)
|
|
173
178
|
|
|
174
179
|
@property
|
|
175
180
|
def __pydantic_model__(self) -> Type["BaseModel"]:
|
|
@@ -183,21 +188,3 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
183
188
|
"""Deprecated accessor for to_pydantic"""
|
|
184
189
|
logger.warning("to_record is deprecated, use to_pydantic instead.")
|
|
185
190
|
return self.to_pydantic()
|
|
186
|
-
|
|
187
|
-
def _infer_organization(self, db_session: "Session") -> None:
|
|
188
|
-
"""🪄 MAGIC ALERT! 🪄
|
|
189
|
-
Because so much of the original API is centered around user scopes,
|
|
190
|
-
this allows us to continue with that scope and then infer the org from the creating user.
|
|
191
|
-
|
|
192
|
-
IF a created_by_id is set, we will use that to infer the organization and magic set it at create time!
|
|
193
|
-
If not do nothing to the object. Mutates in place.
|
|
194
|
-
"""
|
|
195
|
-
if self.created_by_id and hasattr(self, "_organization_id"):
|
|
196
|
-
try:
|
|
197
|
-
from letta.orm.user import User # to avoid circular import
|
|
198
|
-
|
|
199
|
-
created_by = User.read(db_session, self.created_by_id)
|
|
200
|
-
except NoResultFound:
|
|
201
|
-
logger.warning(f"User {self.created_by_id} not found, unable to infer organization.")
|
|
202
|
-
return
|
|
203
|
-
self._organization_id = created_by._organization_id
|
letta/orm/tool.py
CHANGED
|
@@ -5,18 +5,15 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
5
5
|
|
|
6
6
|
# TODO everything in functions should live in this model
|
|
7
7
|
from letta.orm.enums import ToolSourceType
|
|
8
|
-
from letta.orm.mixins import OrganizationMixin
|
|
8
|
+
from letta.orm.mixins import OrganizationMixin
|
|
9
9
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
10
10
|
from letta.schemas.tool import Tool as PydanticTool
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
13
|
from letta.orm.organization import Organization
|
|
16
|
-
from letta.orm.user import User
|
|
17
14
|
|
|
18
15
|
|
|
19
|
-
class Tool(SqlalchemyBase, OrganizationMixin
|
|
16
|
+
class Tool(SqlalchemyBase, OrganizationMixin):
|
|
20
17
|
"""Represents an available tool that the LLM can invoke.
|
|
21
18
|
|
|
22
19
|
NOTE: polymorphic inheritance makes more sense here as a TODO. We want a superset of tools
|
|
@@ -29,10 +26,7 @@ class Tool(SqlalchemyBase, OrganizationMixin, UserMixin):
|
|
|
29
26
|
|
|
30
27
|
# Add unique constraint on (name, _organization_id)
|
|
31
28
|
# An organization should not have multiple tools with the same name
|
|
32
|
-
__table_args__ = (
|
|
33
|
-
UniqueConstraint("name", "_organization_id", name="uix_name_organization"),
|
|
34
|
-
UniqueConstraint("name", "_user_id", name="uix_name_user"),
|
|
35
|
-
)
|
|
29
|
+
__table_args__ = (UniqueConstraint("name", "_organization_id", name="uix_name_organization"),)
|
|
36
30
|
|
|
37
31
|
name: Mapped[str] = mapped_column(doc="The display name of the tool.")
|
|
38
32
|
description: Mapped[Optional[str]] = mapped_column(nullable=True, doc="The description of the tool.")
|
|
@@ -48,7 +42,4 @@ class Tool(SqlalchemyBase, OrganizationMixin, UserMixin):
|
|
|
48
42
|
# This was an intentional decision by Sarah
|
|
49
43
|
|
|
50
44
|
# relationships
|
|
51
|
-
# TODO: Possibly add in user in the future
|
|
52
|
-
# This will require some more thought and justification to add this in.
|
|
53
|
-
user: Mapped["User"] = relationship("User", back_populates="tools", lazy="selectin")
|
|
54
45
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="tools", lazy="selectin")
|
letta/orm/user.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
4
4
|
|
|
@@ -8,7 +8,6 @@ from letta.schemas.user import User as PydanticUser
|
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from letta.orm.organization import Organization
|
|
11
|
-
from letta.orm.tool import Tool
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class User(SqlalchemyBase, OrganizationMixin):
|
|
@@ -21,7 +20,6 @@ class User(SqlalchemyBase, OrganizationMixin):
|
|
|
21
20
|
|
|
22
21
|
# relationships
|
|
23
22
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="users")
|
|
24
|
-
tools: Mapped[List["Tool"]] = relationship("Tool", back_populates="user", cascade="all, delete-orphan")
|
|
25
23
|
|
|
26
24
|
# TODO: Add this back later potentially
|
|
27
25
|
# agents: Mapped[List["Agent"]] = relationship(
|
letta/providers.py
CHANGED
|
@@ -313,7 +313,7 @@ class GroqProvider(OpenAIProvider):
|
|
|
313
313
|
continue
|
|
314
314
|
configs.append(
|
|
315
315
|
LLMConfig(
|
|
316
|
-
model=model["id"], model_endpoint_type="
|
|
316
|
+
model=model["id"], model_endpoint_type="groq", model_endpoint=self.base_url, context_window=model["context_window"]
|
|
317
317
|
)
|
|
318
318
|
)
|
|
319
319
|
return configs
|
letta/schemas/tool.py
CHANGED
|
@@ -11,7 +11,6 @@ from letta.functions.schema_generator import generate_schema_from_args_schema
|
|
|
11
11
|
from letta.schemas.letta_base import LettaBase
|
|
12
12
|
from letta.schemas.openai.chat_completions import ToolCall
|
|
13
13
|
from letta.services.organization_manager import OrganizationManager
|
|
14
|
-
from letta.services.user_manager import UserManager
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class BaseTool(LettaBase):
|
|
@@ -35,7 +34,6 @@ class Tool(BaseTool):
|
|
|
35
34
|
description: Optional[str] = Field(None, description="The description of the tool.")
|
|
36
35
|
source_type: Optional[str] = Field(None, description="The type of the source code.")
|
|
37
36
|
module: Optional[str] = Field(None, description="The module of the function.")
|
|
38
|
-
user_id: str = Field(..., description="The unique identifier of the user associated with the tool.")
|
|
39
37
|
organization_id: str = Field(..., description="The unique identifier of the organization associated with the tool.")
|
|
40
38
|
name: str = Field(..., description="The name of the function.")
|
|
41
39
|
tags: List[str] = Field(..., description="Metadata tags.")
|
|
@@ -44,6 +42,10 @@ class Tool(BaseTool):
|
|
|
44
42
|
source_code: str = Field(..., description="The source code of the function.")
|
|
45
43
|
json_schema: Dict = Field(default_factory=dict, description="The JSON schema of the function.")
|
|
46
44
|
|
|
45
|
+
# metadata fields
|
|
46
|
+
created_by_id: str = Field(..., description="The id of the user that made this Tool.")
|
|
47
|
+
last_updated_by_id: str = Field(..., description="The id of the user that made this Tool.")
|
|
48
|
+
|
|
47
49
|
def to_dict(self):
|
|
48
50
|
"""
|
|
49
51
|
Convert tool into OpenAI representation.
|
|
@@ -58,11 +60,6 @@ class Tool(BaseTool):
|
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
class ToolCreate(LettaBase):
|
|
61
|
-
user_id: str = Field(UserManager.DEFAULT_USER_ID, description="The user that this tool belongs to. Defaults to the default user ID.")
|
|
62
|
-
organization_id: str = Field(
|
|
63
|
-
OrganizationManager.DEFAULT_ORG_ID,
|
|
64
|
-
description="The organization that this tool belongs to. Defaults to the default organization ID.",
|
|
65
|
-
)
|
|
66
63
|
name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
|
|
67
64
|
description: Optional[str] = Field(None, description="The description of the tool.")
|
|
68
65
|
tags: List[str] = Field([], description="Metadata tags.")
|
|
@@ -75,9 +72,7 @@ class ToolCreate(LettaBase):
|
|
|
75
72
|
terminal: Optional[bool] = Field(None, description="Whether the tool is a terminal tool (allow requesting heartbeats).")
|
|
76
73
|
|
|
77
74
|
@classmethod
|
|
78
|
-
def from_composio(
|
|
79
|
-
cls, action: "ActionType", user_id: str = UserManager.DEFAULT_USER_ID, organization_id: str = OrganizationManager.DEFAULT_ORG_ID
|
|
80
|
-
) -> "ToolCreate":
|
|
75
|
+
def from_composio(cls, action: "ActionType", organization_id: str = OrganizationManager.DEFAULT_ORG_ID) -> "ToolCreate":
|
|
81
76
|
"""
|
|
82
77
|
Class method to create an instance of Letta-compatible Composio Tool.
|
|
83
78
|
Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio
|
|
@@ -106,8 +101,6 @@ class ToolCreate(LettaBase):
|
|
|
106
101
|
json_schema = generate_schema_from_args_schema(composio_tool.args_schema, name=wrapper_func_name, description=description)
|
|
107
102
|
|
|
108
103
|
return cls(
|
|
109
|
-
user_id=user_id,
|
|
110
|
-
organization_id=organization_id,
|
|
111
104
|
name=wrapper_func_name,
|
|
112
105
|
description=description,
|
|
113
106
|
source_type=source_type,
|
|
@@ -121,7 +114,6 @@ class ToolCreate(LettaBase):
|
|
|
121
114
|
cls,
|
|
122
115
|
langchain_tool: "LangChainBaseTool",
|
|
123
116
|
additional_imports_module_attr_map: dict[str, str] = None,
|
|
124
|
-
user_id: str = UserManager.DEFAULT_USER_ID,
|
|
125
117
|
organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
|
|
126
118
|
) -> "ToolCreate":
|
|
127
119
|
"""
|
|
@@ -142,8 +134,6 @@ class ToolCreate(LettaBase):
|
|
|
142
134
|
json_schema = generate_schema_from_args_schema(langchain_tool.args_schema, name=wrapper_func_name, description=description)
|
|
143
135
|
|
|
144
136
|
return cls(
|
|
145
|
-
user_id=user_id,
|
|
146
|
-
organization_id=organization_id,
|
|
147
137
|
name=wrapper_func_name,
|
|
148
138
|
description=description,
|
|
149
139
|
source_type=source_type,
|
|
@@ -157,7 +147,6 @@ class ToolCreate(LettaBase):
|
|
|
157
147
|
cls,
|
|
158
148
|
crewai_tool: "CrewAIBaseTool",
|
|
159
149
|
additional_imports_module_attr_map: dict[str, str] = None,
|
|
160
|
-
user_id: str = UserManager.DEFAULT_USER_ID,
|
|
161
150
|
organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
|
|
162
151
|
) -> "ToolCreate":
|
|
163
152
|
"""
|
|
@@ -176,8 +165,6 @@ class ToolCreate(LettaBase):
|
|
|
176
165
|
json_schema = generate_schema_from_args_schema(crewai_tool.args_schema, name=wrapper_func_name, description=description)
|
|
177
166
|
|
|
178
167
|
return cls(
|
|
179
|
-
user_id=user_id,
|
|
180
|
-
organization_id=organization_id,
|
|
181
168
|
name=wrapper_func_name,
|
|
182
169
|
description=description,
|
|
183
170
|
source_type=source_type,
|
|
@@ -84,7 +84,7 @@ def create_agent(
|
|
|
84
84
|
blocks = agent.memory.get_blocks()
|
|
85
85
|
agent.memory = BasicBlockMemory(blocks=blocks)
|
|
86
86
|
|
|
87
|
-
return server.create_agent(agent,
|
|
87
|
+
return server.create_agent(agent, actor=actor)
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
@router.patch("/{agent_id}", response_model=AgentState, operation_id="update_agent")
|
|
@@ -96,9 +96,7 @@ def update_agent(
|
|
|
96
96
|
):
|
|
97
97
|
"""Update an exsiting agent"""
|
|
98
98
|
actor = server.get_user_or_default(user_id=user_id)
|
|
99
|
-
|
|
100
|
-
update_agent.id = agent_id
|
|
101
|
-
return server.update_agent(update_agent, user_id=actor.id)
|
|
99
|
+
return server.update_agent(update_agent, actor=actor)
|
|
102
100
|
|
|
103
101
|
|
|
104
102
|
@router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="get_tools_from_agent")
|
|
@@ -26,11 +26,13 @@ def delete_tool(
|
|
|
26
26
|
def get_tool(
|
|
27
27
|
tool_id: str,
|
|
28
28
|
server: SyncServer = Depends(get_letta_server),
|
|
29
|
+
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
29
30
|
):
|
|
30
31
|
"""
|
|
31
32
|
Get a tool by ID
|
|
32
33
|
"""
|
|
33
|
-
|
|
34
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
35
|
+
tool = server.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
|
|
34
36
|
if tool is None:
|
|
35
37
|
# return 404 error
|
|
36
38
|
raise HTTPException(status_code=404, detail=f"Tool with id {tool_id} not found.")
|
|
@@ -49,7 +51,7 @@ def get_tool_id(
|
|
|
49
51
|
actor = server.get_user_or_default(user_id=user_id)
|
|
50
52
|
|
|
51
53
|
try:
|
|
52
|
-
tool = server.tool_manager.
|
|
54
|
+
tool = server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
53
55
|
return tool.id
|
|
54
56
|
except NoResultFound:
|
|
55
57
|
raise HTTPException(status_code=404, detail=f"Tool with name {tool_name} and organization id {actor.organization_id} not found.")
|
|
@@ -67,7 +69,7 @@ def list_tools(
|
|
|
67
69
|
"""
|
|
68
70
|
try:
|
|
69
71
|
actor = server.get_user_or_default(user_id=user_id)
|
|
70
|
-
return server.tool_manager.
|
|
72
|
+
return server.tool_manager.list_tools(actor=actor, cursor=cursor, limit=limit)
|
|
71
73
|
except Exception as e:
|
|
72
74
|
# Log or print the full exception here for debugging
|
|
73
75
|
print(f"Error occurred: {e}")
|
|
@@ -85,13 +87,9 @@ def create_tool(
|
|
|
85
87
|
"""
|
|
86
88
|
# Derive user and org id from actor
|
|
87
89
|
actor = server.get_user_or_default(user_id=user_id)
|
|
88
|
-
request.organization_id = actor.organization_id
|
|
89
|
-
request.user_id = actor.id
|
|
90
90
|
|
|
91
91
|
# Send request to create the tool
|
|
92
|
-
return server.tool_manager.create_or_update_tool(
|
|
93
|
-
tool_create=request,
|
|
94
|
-
)
|
|
92
|
+
return server.tool_manager.create_or_update_tool(tool_create=request, actor=actor)
|
|
95
93
|
|
|
96
94
|
|
|
97
95
|
@router.patch("/{tool_id}", response_model=Tool, operation_id="update_tool")
|
|
@@ -104,4 +102,5 @@ def update_tool(
|
|
|
104
102
|
"""
|
|
105
103
|
Update an existing tool
|
|
106
104
|
"""
|
|
107
|
-
|
|
105
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
106
|
+
return server.tool_manager.update_tool_by_id(tool_id, actor.id, request)
|
letta/server/server.py
CHANGED
|
@@ -37,11 +37,13 @@ from letta.log import get_logger
|
|
|
37
37
|
from letta.memory import get_memory_functions
|
|
38
38
|
from letta.metadata import Base, MetadataStore
|
|
39
39
|
from letta.o1_agent import O1Agent
|
|
40
|
+
from letta.orm.errors import NoResultFound
|
|
40
41
|
from letta.prompts import gpt_system
|
|
41
42
|
from letta.providers import (
|
|
42
43
|
AnthropicProvider,
|
|
43
44
|
AzureProvider,
|
|
44
45
|
GoogleAIProvider,
|
|
46
|
+
GroqProvider,
|
|
45
47
|
LettaProvider,
|
|
46
48
|
OllamaProvider,
|
|
47
49
|
OpenAIProvider,
|
|
@@ -73,6 +75,7 @@ from letta.schemas.memory import (
|
|
|
73
75
|
RecallMemorySummary,
|
|
74
76
|
)
|
|
75
77
|
from letta.schemas.message import Message, MessageCreate, MessageRole, UpdateMessage
|
|
78
|
+
from letta.schemas.organization import Organization
|
|
76
79
|
from letta.schemas.passage import Passage
|
|
77
80
|
from letta.schemas.source import Source, SourceCreate, SourceUpdate
|
|
78
81
|
from letta.schemas.tool import Tool, ToolCreate
|
|
@@ -251,12 +254,12 @@ class SyncServer(Server):
|
|
|
251
254
|
self.default_org = self.organization_manager.create_default_organization()
|
|
252
255
|
self.default_user = self.user_manager.create_default_user()
|
|
253
256
|
self.add_default_blocks(self.default_user.id)
|
|
254
|
-
self.tool_manager.add_default_tools(module_name="base",
|
|
257
|
+
self.tool_manager.add_default_tools(module_name="base", actor=self.default_user)
|
|
255
258
|
|
|
256
259
|
# If there is a default org/user
|
|
257
260
|
# This logic may have to change in the future
|
|
258
261
|
if settings.load_default_external_tools:
|
|
259
|
-
self.add_default_external_tools(
|
|
262
|
+
self.add_default_external_tools(actor=self.default_user)
|
|
260
263
|
|
|
261
264
|
# collect providers (always has Letta as a default)
|
|
262
265
|
self._enabled_providers: List[Provider] = [LettaProvider()]
|
|
@@ -296,6 +299,8 @@ class SyncServer(Server):
|
|
|
296
299
|
api_version=model_settings.azure_api_version,
|
|
297
300
|
)
|
|
298
301
|
)
|
|
302
|
+
if model_settings.groq_api_key:
|
|
303
|
+
self._enabled_providers.append(GroqProvider(api_key=model_settings.groq_api_key))
|
|
299
304
|
if model_settings.vllm_api_base:
|
|
300
305
|
# vLLM exposes both a /chat/completions and a /completions endpoint
|
|
301
306
|
self._enabled_providers.append(
|
|
@@ -345,10 +350,10 @@ class SyncServer(Server):
|
|
|
345
350
|
}
|
|
346
351
|
)
|
|
347
352
|
|
|
348
|
-
def _load_agent(self,
|
|
353
|
+
def _load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
349
354
|
"""Loads a saved agent into memory (if it doesn't exist, throw an error)"""
|
|
350
|
-
assert isinstance(user_id, str), user_id
|
|
351
355
|
assert isinstance(agent_id, str), agent_id
|
|
356
|
+
user_id = actor.id
|
|
352
357
|
|
|
353
358
|
# If an interface isn't specified, use the default
|
|
354
359
|
if interface is None:
|
|
@@ -365,7 +370,7 @@ class SyncServer(Server):
|
|
|
365
370
|
logger.debug(f"Creating an agent object")
|
|
366
371
|
tool_objs = []
|
|
367
372
|
for name in agent_state.tools:
|
|
368
|
-
tool_obj = self.tool_manager.
|
|
373
|
+
tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
|
|
369
374
|
if not tool_obj:
|
|
370
375
|
logger.exception(f"Tool {name} does not exist for user {user_id}")
|
|
371
376
|
raise ValueError(f"Tool {name} does not exist for user {user_id}")
|
|
@@ -396,13 +401,14 @@ class SyncServer(Server):
|
|
|
396
401
|
if not agent_state:
|
|
397
402
|
raise ValueError(f"Agent does not exist")
|
|
398
403
|
user_id = agent_state.user_id
|
|
404
|
+
actor = self.user_manager.get_user_by_id(user_id)
|
|
399
405
|
|
|
400
406
|
logger.debug(f"Checking for agent user_id={user_id} agent_id={agent_id}")
|
|
401
407
|
# TODO: consider disabling loading cached agents due to potential concurrency issues
|
|
402
408
|
letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
|
|
403
409
|
if not letta_agent:
|
|
404
410
|
logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
|
|
405
|
-
letta_agent = self._load_agent(
|
|
411
|
+
letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
|
|
406
412
|
return letta_agent
|
|
407
413
|
|
|
408
414
|
def _step(
|
|
@@ -759,11 +765,12 @@ class SyncServer(Server):
|
|
|
759
765
|
def create_agent(
|
|
760
766
|
self,
|
|
761
767
|
request: CreateAgent,
|
|
762
|
-
|
|
768
|
+
actor: User,
|
|
763
769
|
# interface
|
|
764
770
|
interface: Union[AgentInterface, None] = None,
|
|
765
771
|
) -> AgentState:
|
|
766
772
|
"""Create a new agent using a config"""
|
|
773
|
+
user_id = actor.id
|
|
767
774
|
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
768
775
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
769
776
|
|
|
@@ -801,7 +808,7 @@ class SyncServer(Server):
|
|
|
801
808
|
tool_objs = []
|
|
802
809
|
if request.tools:
|
|
803
810
|
for tool_name in request.tools:
|
|
804
|
-
tool_obj = self.tool_manager.
|
|
811
|
+
tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
805
812
|
tool_objs.append(tool_obj)
|
|
806
813
|
|
|
807
814
|
assert request.memory is not None
|
|
@@ -822,9 +829,8 @@ class SyncServer(Server):
|
|
|
822
829
|
source_type=source_type,
|
|
823
830
|
tags=tags,
|
|
824
831
|
json_schema=json_schema,
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
832
|
+
),
|
|
833
|
+
actor=actor,
|
|
828
834
|
)
|
|
829
835
|
tool_objs.append(tool)
|
|
830
836
|
if not request.tools:
|
|
@@ -887,11 +893,14 @@ class SyncServer(Server):
|
|
|
887
893
|
def update_agent(
|
|
888
894
|
self,
|
|
889
895
|
request: UpdateAgentState,
|
|
890
|
-
|
|
896
|
+
actor: User,
|
|
891
897
|
):
|
|
892
898
|
"""Update the agents core memory block, return the new state"""
|
|
893
|
-
|
|
894
|
-
|
|
899
|
+
try:
|
|
900
|
+
self.user_manager.get_user_by_id(user_id=actor.id)
|
|
901
|
+
except Exception:
|
|
902
|
+
raise ValueError(f"User user_id={actor.id} does not exist")
|
|
903
|
+
|
|
895
904
|
if self.ms.get_agent(agent_id=request.id) is None:
|
|
896
905
|
raise ValueError(f"Agent agent_id={request.id} does not exist")
|
|
897
906
|
|
|
@@ -902,7 +911,7 @@ class SyncServer(Server):
|
|
|
902
911
|
if request.memory:
|
|
903
912
|
assert isinstance(request.memory, Memory), type(request.memory)
|
|
904
913
|
new_memory_contents = request.memory.to_flat_dict()
|
|
905
|
-
_ = self.update_agent_core_memory(user_id=
|
|
914
|
+
_ = self.update_agent_core_memory(user_id=actor.id, agent_id=request.id, new_memory_contents=new_memory_contents)
|
|
906
915
|
|
|
907
916
|
# update the system prompt
|
|
908
917
|
if request.system:
|
|
@@ -922,7 +931,7 @@ class SyncServer(Server):
|
|
|
922
931
|
# (1) get tools + make sure they exist
|
|
923
932
|
tool_objs = []
|
|
924
933
|
for tool_name in request.tools:
|
|
925
|
-
tool_obj = self.tool_manager.
|
|
934
|
+
tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
926
935
|
assert tool_obj, f"Tool {tool_name} does not exist"
|
|
927
936
|
tool_objs.append(tool_obj)
|
|
928
937
|
|
|
@@ -968,8 +977,11 @@ class SyncServer(Server):
|
|
|
968
977
|
user_id: str,
|
|
969
978
|
):
|
|
970
979
|
"""Add tools from an existing agent"""
|
|
971
|
-
|
|
980
|
+
try:
|
|
981
|
+
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
982
|
+
except NoResultFound:
|
|
972
983
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
984
|
+
|
|
973
985
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
974
986
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
975
987
|
|
|
@@ -978,12 +990,12 @@ class SyncServer(Server):
|
|
|
978
990
|
|
|
979
991
|
# Get all the tool objects from the request
|
|
980
992
|
tool_objs = []
|
|
981
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool_id)
|
|
993
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=user)
|
|
982
994
|
assert tool_obj, f"Tool with id={tool_id} does not exist"
|
|
983
995
|
tool_objs.append(tool_obj)
|
|
984
996
|
|
|
985
997
|
for tool in letta_agent.tools:
|
|
986
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id)
|
|
998
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
|
|
987
999
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
988
1000
|
|
|
989
1001
|
# If it's not the already added tool
|
|
@@ -1007,8 +1019,11 @@ class SyncServer(Server):
|
|
|
1007
1019
|
user_id: str,
|
|
1008
1020
|
):
|
|
1009
1021
|
"""Remove tools from an existing agent"""
|
|
1010
|
-
|
|
1022
|
+
try:
|
|
1023
|
+
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1024
|
+
except NoResultFound:
|
|
1011
1025
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1026
|
+
|
|
1012
1027
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1013
1028
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1014
1029
|
|
|
@@ -1018,7 +1033,7 @@ class SyncServer(Server):
|
|
|
1018
1033
|
# Get all the tool_objs
|
|
1019
1034
|
tool_objs = []
|
|
1020
1035
|
for tool in letta_agent.tools:
|
|
1021
|
-
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id)
|
|
1036
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
|
|
1022
1037
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1023
1038
|
|
|
1024
1039
|
# If it's not the tool we want to remove
|
|
@@ -1733,7 +1748,7 @@ class SyncServer(Server):
|
|
|
1733
1748
|
|
|
1734
1749
|
return sources_with_metadata
|
|
1735
1750
|
|
|
1736
|
-
def add_default_external_tools(self,
|
|
1751
|
+
def add_default_external_tools(self, actor: User) -> bool:
|
|
1737
1752
|
"""Add default langchain tools. Return true if successful, false otherwise."""
|
|
1738
1753
|
success = True
|
|
1739
1754
|
tool_creates = ToolCreate.load_default_langchain_tools() + ToolCreate.load_default_crewai_tools()
|
|
@@ -1741,7 +1756,7 @@ class SyncServer(Server):
|
|
|
1741
1756
|
tool_creates += ToolCreate.load_default_composio_tools()
|
|
1742
1757
|
for tool_create in tool_creates:
|
|
1743
1758
|
try:
|
|
1744
|
-
self.tool_manager.create_or_update_tool(tool_create)
|
|
1759
|
+
self.tool_manager.create_or_update_tool(tool_create, actor=actor)
|
|
1745
1760
|
except Exception as e:
|
|
1746
1761
|
warnings.warn(f"An error occurred while creating tool {tool_create}: {e}")
|
|
1747
1762
|
warnings.warn(traceback.format_exc())
|
|
@@ -1843,6 +1858,16 @@ class SyncServer(Server):
|
|
|
1843
1858
|
except ValueError:
|
|
1844
1859
|
raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
|
|
1845
1860
|
|
|
1861
|
+
def get_organization_or_default(self, org_id: Optional[str]) -> Organization:
|
|
1862
|
+
"""Get the organization object for org_id if it exists, otherwise return the default organization object"""
|
|
1863
|
+
if org_id is None:
|
|
1864
|
+
org_id = self.organization_manager.DEFAULT_ORG_ID
|
|
1865
|
+
|
|
1866
|
+
try:
|
|
1867
|
+
return self.organization_manager.get_organization_by_id(org_id=org_id)
|
|
1868
|
+
except ValueError:
|
|
1869
|
+
raise HTTPException(status_code=404, detail=f"Organization with id {org_id} not found")
|
|
1870
|
+
|
|
1846
1871
|
def list_llm_models(self) -> List[LLMConfig]:
|
|
1847
1872
|
"""List available models"""
|
|
1848
1873
|
|
letta/server/startup.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
echo "Starting MEMGPT server..."
|
|
3
3
|
if [ "$MEMGPT_ENVIRONMENT" = "DEVELOPMENT" ] ; then
|
|
4
4
|
echo "Starting in development mode!"
|
|
5
|
-
uvicorn letta.server.rest_api.app:app --reload --reload-dir /letta --host 0.0.0.0 --port
|
|
5
|
+
uvicorn letta.server.rest_api.app:app --reload --reload-dir /letta --host 0.0.0.0 --port 8283
|
|
6
6
|
else
|
|
7
|
-
uvicorn letta.server.rest_api.app:app --host 0.0.0.0 --port
|
|
7
|
+
uvicorn letta.server.rest_api.app:app --host 0.0.0.0 --port 8283
|
|
8
8
|
fi
|
letta/services/tool_manager.py
CHANGED
|
@@ -9,9 +9,9 @@ from letta.functions.functions import derive_openai_json_schema, load_function_s
|
|
|
9
9
|
from letta.orm.errors import NoResultFound
|
|
10
10
|
from letta.orm.organization import Organization as OrganizationModel
|
|
11
11
|
from letta.orm.tool import Tool as ToolModel
|
|
12
|
-
from letta.orm.user import User as UserModel
|
|
13
12
|
from letta.schemas.tool import Tool as PydanticTool
|
|
14
13
|
from letta.schemas.tool import ToolCreate, ToolUpdate
|
|
14
|
+
from letta.schemas.user import User as PydanticUser
|
|
15
15
|
from letta.utils import enforce_types
|
|
16
16
|
|
|
17
17
|
|
|
@@ -25,7 +25,7 @@ class ToolManager:
|
|
|
25
25
|
self.session_maker = db_context
|
|
26
26
|
|
|
27
27
|
@enforce_types
|
|
28
|
-
def create_or_update_tool(self, tool_create: ToolCreate) -> PydanticTool:
|
|
28
|
+
def create_or_update_tool(self, tool_create: ToolCreate, actor: PydanticUser) -> PydanticTool:
|
|
29
29
|
"""Create a new tool based on the ToolCreate schema."""
|
|
30
30
|
# Derive json_schema
|
|
31
31
|
derived_json_schema = tool_create.json_schema or derive_openai_json_schema(tool_create)
|
|
@@ -34,105 +34,72 @@ class ToolManager:
|
|
|
34
34
|
try:
|
|
35
35
|
# NOTE: We use the organization id here
|
|
36
36
|
# This is important, because even if it's a different user, adding the same tool to the org should not happen
|
|
37
|
-
tool = self.
|
|
37
|
+
tool = self.get_tool_by_name(tool_name=derived_name, actor=actor)
|
|
38
38
|
# Put to dict and remove fields that should not be reset
|
|
39
|
-
update_data = tool_create.model_dump(exclude={"
|
|
39
|
+
update_data = tool_create.model_dump(exclude={"module", "terminal"}, exclude_unset=True)
|
|
40
40
|
# Remove redundant update fields
|
|
41
41
|
update_data = {key: value for key, value in update_data.items() if getattr(tool, key) != value}
|
|
42
42
|
|
|
43
43
|
# If there's anything to update
|
|
44
44
|
if update_data:
|
|
45
|
-
self.update_tool_by_id(tool.id, ToolUpdate(**update_data))
|
|
45
|
+
self.update_tool_by_id(tool.id, ToolUpdate(**update_data), actor)
|
|
46
46
|
else:
|
|
47
47
|
warnings.warn(
|
|
48
|
-
f"`create_or_update_tool` was called with user_id={
|
|
48
|
+
f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={tool_create.name}, but found existing tool with nothing to update."
|
|
49
49
|
)
|
|
50
50
|
except NoResultFound:
|
|
51
51
|
tool_create.json_schema = derived_json_schema
|
|
52
52
|
tool_create.name = derived_name
|
|
53
|
-
tool = self.create_tool(tool_create)
|
|
53
|
+
tool = self.create_tool(tool_create, actor=actor)
|
|
54
54
|
|
|
55
55
|
return tool
|
|
56
56
|
|
|
57
57
|
@enforce_types
|
|
58
|
-
def create_tool(self, tool_create: ToolCreate) -> PydanticTool:
|
|
58
|
+
def create_tool(self, tool_create: ToolCreate, actor: PydanticUser) -> PydanticTool:
|
|
59
59
|
"""Create a new tool based on the ToolCreate schema."""
|
|
60
60
|
# Create the tool
|
|
61
61
|
with self.session_maker() as session:
|
|
62
|
-
# Include all fields except
|
|
62
|
+
# Include all fields except `terminal` (which is not part of ToolModel) at the moment
|
|
63
63
|
create_data = tool_create.model_dump(exclude={"terminal"})
|
|
64
|
-
tool = ToolModel(**create_data) # Unpack everything directly into ToolModel
|
|
65
|
-
tool.create(session)
|
|
64
|
+
tool = ToolModel(**create_data, organization_id=actor.organization_id) # Unpack everything directly into ToolModel
|
|
65
|
+
tool.create(session, actor=actor)
|
|
66
66
|
|
|
67
67
|
return tool.to_pydantic()
|
|
68
68
|
|
|
69
69
|
@enforce_types
|
|
70
|
-
def get_tool_by_id(self, tool_id: str) -> PydanticTool:
|
|
70
|
+
def get_tool_by_id(self, tool_id: str, actor: PydanticUser) -> PydanticTool:
|
|
71
71
|
"""Fetch a tool by its ID."""
|
|
72
72
|
with self.session_maker() as session:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return tool.to_pydantic()
|
|
78
|
-
except NoResultFound:
|
|
79
|
-
raise ValueError(f"Tool with id {tool_id} not found.")
|
|
80
|
-
|
|
81
|
-
@enforce_types
|
|
82
|
-
def get_tool_by_name_and_user_id(self, tool_name: str, user_id: str) -> PydanticTool:
|
|
83
|
-
"""Retrieve a tool by its name and organization_id."""
|
|
84
|
-
with self.session_maker() as session:
|
|
85
|
-
# Use the list method to apply filters
|
|
86
|
-
results = ToolModel.list(db_session=session, name=tool_name, _user_id=UserModel.get_uid_from_identifier(user_id))
|
|
87
|
-
|
|
88
|
-
# Ensure only one result is returned (since there is a unique constraint)
|
|
89
|
-
if not results:
|
|
90
|
-
raise NoResultFound(f"Tool with name {tool_name} and user_id {user_id} not found.")
|
|
91
|
-
|
|
92
|
-
if len(results) > 1:
|
|
93
|
-
raise RuntimeError(
|
|
94
|
-
f"Multiple tools with name {tool_name} and user_id {user_id} were found. This is a serious error, and means that our table does not have uniqueness constraints properly set up. Please reach out to the letta development team if you see this error."
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
# Return the single result
|
|
98
|
-
return results[0]
|
|
73
|
+
# Retrieve tool by id using the Tool model's read method
|
|
74
|
+
tool = ToolModel.read(db_session=session, identifier=tool_id, actor=actor)
|
|
75
|
+
# Convert the SQLAlchemy Tool object to PydanticTool
|
|
76
|
+
return tool.to_pydantic()
|
|
99
77
|
|
|
100
78
|
@enforce_types
|
|
101
|
-
def
|
|
102
|
-
"""Retrieve a tool by its name and
|
|
79
|
+
def get_tool_by_name(self, tool_name: str, actor: PydanticUser):
|
|
80
|
+
"""Retrieve a tool by its name and a user. We derive the organization from the user, and retrieve that tool."""
|
|
103
81
|
with self.session_maker() as session:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
db_session=session, name=tool_name, _organization_id=OrganizationModel.get_uid_from_identifier(organization_id)
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Ensure only one result is returned (since there is a unique constraint)
|
|
110
|
-
if not results:
|
|
111
|
-
raise NoResultFound(f"Tool with name {tool_name} and organization_id {organization_id} not found.")
|
|
112
|
-
|
|
113
|
-
if len(results) > 1:
|
|
114
|
-
raise RuntimeError(
|
|
115
|
-
f"Multiple tools with name {tool_name} and organization_id {organization_id} were found. This is a serious error, and means that our table does not have uniqueness constraints properly set up. Please reach out to the letta development team if you see this error."
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# Return the single result
|
|
119
|
-
return results[0]
|
|
82
|
+
tool = ToolModel.read(db_session=session, name=tool_name, actor=actor)
|
|
83
|
+
return tool.to_pydantic()
|
|
120
84
|
|
|
121
85
|
@enforce_types
|
|
122
|
-
def
|
|
86
|
+
def list_tools(self, actor: PydanticUser, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticTool]:
|
|
123
87
|
"""List all tools with optional pagination using cursor and limit."""
|
|
124
88
|
with self.session_maker() as session:
|
|
125
89
|
tools = ToolModel.list(
|
|
126
|
-
db_session=session,
|
|
90
|
+
db_session=session,
|
|
91
|
+
cursor=cursor,
|
|
92
|
+
limit=limit,
|
|
93
|
+
_organization_id=OrganizationModel.get_uid_from_identifier(actor.organization_id),
|
|
127
94
|
)
|
|
128
95
|
return [tool.to_pydantic() for tool in tools]
|
|
129
96
|
|
|
130
97
|
@enforce_types
|
|
131
|
-
def update_tool_by_id(self, tool_id: str, tool_update: ToolUpdate) -> None:
|
|
98
|
+
def update_tool_by_id(self, tool_id: str, tool_update: ToolUpdate, actor: PydanticUser) -> None:
|
|
132
99
|
"""Update a tool by its ID with the given ToolUpdate object."""
|
|
133
100
|
with self.session_maker() as session:
|
|
134
101
|
# Fetch the tool by ID
|
|
135
|
-
tool = ToolModel.read(db_session=session, identifier=tool_id)
|
|
102
|
+
tool = ToolModel.read(db_session=session, identifier=tool_id, actor=actor)
|
|
136
103
|
|
|
137
104
|
# Update tool attributes with only the fields that were explicitly set
|
|
138
105
|
update_data = tool_update.model_dump(exclude_unset=True, exclude_none=True)
|
|
@@ -140,20 +107,20 @@ class ToolManager:
|
|
|
140
107
|
setattr(tool, key, value)
|
|
141
108
|
|
|
142
109
|
# Save the updated tool to the database
|
|
143
|
-
tool.update(db_session=session)
|
|
110
|
+
tool.update(db_session=session, actor=actor)
|
|
144
111
|
|
|
145
112
|
@enforce_types
|
|
146
|
-
def delete_tool_by_id(self, tool_id: str) -> None:
|
|
113
|
+
def delete_tool_by_id(self, tool_id: str, actor: PydanticUser) -> None:
|
|
147
114
|
"""Delete a tool by its ID."""
|
|
148
115
|
with self.session_maker() as session:
|
|
149
116
|
try:
|
|
150
117
|
tool = ToolModel.read(db_session=session, identifier=tool_id)
|
|
151
|
-
tool.delete(db_session=session)
|
|
118
|
+
tool.delete(db_session=session, actor=actor)
|
|
152
119
|
except NoResultFound:
|
|
153
120
|
raise ValueError(f"Tool with id {tool_id} not found.")
|
|
154
121
|
|
|
155
122
|
@enforce_types
|
|
156
|
-
def add_default_tools(self,
|
|
123
|
+
def add_default_tools(self, actor: PydanticUser, module_name="base"):
|
|
157
124
|
"""Add default tools in {module_name}.py"""
|
|
158
125
|
full_module_name = f"letta.functions.function_sets.{module_name}"
|
|
159
126
|
try:
|
|
@@ -187,7 +154,6 @@ class ToolManager:
|
|
|
187
154
|
module=schema["module"],
|
|
188
155
|
source_code=source_code,
|
|
189
156
|
json_schema=schema["json_schema"],
|
|
190
|
-
organization_id=org_id,
|
|
191
|
-
user_id=user_id,
|
|
192
157
|
),
|
|
158
|
+
actor=actor,
|
|
193
159
|
)
|
letta/services/user_manager.py
CHANGED
|
@@ -85,11 +85,8 @@ class UserManager:
|
|
|
85
85
|
def get_user_by_id(self, user_id: str) -> PydanticUser:
|
|
86
86
|
"""Fetch a user by ID."""
|
|
87
87
|
with self.session_maker() as session:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return user.to_pydantic()
|
|
91
|
-
except NoResultFound:
|
|
92
|
-
raise ValueError(f"User with id {user_id} not found.")
|
|
88
|
+
user = UserModel.read(db_session=session, identifier=user_id)
|
|
89
|
+
return user.to_pydantic()
|
|
93
90
|
|
|
94
91
|
@enforce_types
|
|
95
92
|
def get_default_user(self) -> PydanticUser:
|
|
@@ -9,11 +9,11 @@ letta/agent_store/qdrant.py,sha256=6_33V-FEDpT9LG5zmr6-3y9slw1YFLswxpahiyMkvHA,7
|
|
|
9
9
|
letta/agent_store/storage.py,sha256=4gKvMRYBGm9cwyaDOzljxDKgqr4MxGXcC4yGhAdKcAA,6693
|
|
10
10
|
letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
|
|
11
11
|
letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
|
|
12
|
-
letta/cli/cli.py,sha256=
|
|
12
|
+
letta/cli/cli.py,sha256=i6wFBaX8-WwZ6nl_T0FFptfozlnYEBM7RcShwBl-qfw,16106
|
|
13
13
|
letta/cli/cli_config.py,sha256=ynsezKawQ0l95rymlv-AJ3QjbqiyaXZyuzsDfBYwMwg,8537
|
|
14
14
|
letta/cli/cli_load.py,sha256=x4L8s15GwIW13xrhKYFWHo_y-IVGtoPDHWWKcHDRP10,4587
|
|
15
15
|
letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
letta/client/client.py,sha256=
|
|
16
|
+
letta/client/client.py,sha256=3tZfSKez9uCCpcItjtUJ0CMpRwoTSA-CGvX7I4pNBG4,92340
|
|
17
17
|
letta/client/streaming.py,sha256=bfWlUu7z7EoPfKxBqIarYxGKyrL7Pj79BlliToqcCgI,4592
|
|
18
18
|
letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
|
|
19
19
|
letta/config.py,sha256=eK-ip06ELHNYriInkgfidDvJxQ2tD1u49I-VLXB87nE,18929
|
|
@@ -40,7 +40,7 @@ letta/llm_api/azure_openai_constants.py,sha256=oXtKrgBFHf744gyt5l1thILXgyi8NDNUr
|
|
|
40
40
|
letta/llm_api/cohere.py,sha256=vDRd-SUGp1t_JUIdwC3RkIhwMl0OY7n-tAU9uPORYkY,14826
|
|
41
41
|
letta/llm_api/google_ai.py,sha256=3xZ074nSOCC22c15yerA5ngWzh0ex4wxeI-6faNbHPE,17708
|
|
42
42
|
letta/llm_api/helpers.py,sha256=sGCmNA1U_7-AhRFgvT668jdp_xyzSliKQYbTvRR6O7c,9812
|
|
43
|
-
letta/llm_api/llm_api_tools.py,sha256=
|
|
43
|
+
letta/llm_api/llm_api_tools.py,sha256=RRczEZ3h_fxIJLXbw1UT5YZwdsr1gZD6HRPVA9qopmE,14787
|
|
44
44
|
letta/llm_api/mistral.py,sha256=fHdfD9ug-rQIk2qn8tRKay1U6w9maF11ryhKi91FfXM,1593
|
|
45
45
|
letta/llm_api/openai.py,sha256=0hwAZhpjrmubYy549hvjzhw6zK_aBhSVuIQN0OsTq-w,23705
|
|
46
46
|
letta/local_llm/README.md,sha256=hFJyw5B0TU2jrh9nb0zGZMgdH-Ei1dSRfhvPQG_NSoU,168
|
|
@@ -89,14 +89,14 @@ letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
89
89
|
letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
|
|
90
90
|
letta/orm/__all__.py,sha256=2gh2MZTkA3Hw67VWVKK3JIStJOqTeLdpCvYSVYNeEDA,692
|
|
91
91
|
letta/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
-
letta/orm/base.py,sha256=
|
|
92
|
+
letta/orm/base.py,sha256=uK56wXg4Hv5jGuBsThSgmlFdl0e2u4VGiTX4RiRqTK4,2671
|
|
93
93
|
letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
|
|
94
94
|
letta/orm/errors.py,sha256=somsGtotFlb3SDM6tKdZ5TDGwEEP3ppx47ICAvNMnkg,225
|
|
95
95
|
letta/orm/mixins.py,sha256=hZJFYnmSGklryuAOypvVhMgXyU7pgatGEun-p-2f_Fc,2457
|
|
96
96
|
letta/orm/organization.py,sha256=ccE0ShFWqWPdtXiCRkZCSeIERbtwU-a7AF99N3S6IRM,1468
|
|
97
|
-
letta/orm/sqlalchemy_base.py,sha256=
|
|
98
|
-
letta/orm/tool.py,sha256=
|
|
99
|
-
letta/orm/user.py,sha256=
|
|
97
|
+
letta/orm/sqlalchemy_base.py,sha256=LuV-or0T8kdg2M0H7Y8KEIcaeKD1DkARzA-LgAGR4qU,7721
|
|
98
|
+
letta/orm/tool.py,sha256=mVOgN2TdszQ5SM6cHqY9Rl6405ccB7mXrq_DHisA1WU,2179
|
|
99
|
+
letta/orm/user.py,sha256=69FU1rSXhlRJMaAUPYebxgFPn8UCQRU8TYUkjXLrALQ,1136
|
|
100
100
|
letta/persistence_manager.py,sha256=LlLgEDpSafCPAiyKmuq0NvVAnfBkZo6TWbGIKYQjQBs,5200
|
|
101
101
|
letta/personas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
102
|
letta/personas/examples/anna_pa.txt,sha256=zgiNdSNhy1HQy58cF_6RFPzcg2i37F9v38YuL1CW40A,1849
|
|
@@ -120,7 +120,7 @@ letta/prompts/system/memgpt_gpt35_extralong.txt,sha256=FheNhYoIzNz6qnJKhVquZVSMj
|
|
|
120
120
|
letta/prompts/system/memgpt_intuitive_knowledge.txt,sha256=sA7c3urYqREVnSBI81nTGImXAekqC0Fxc7RojFqud1g,2966
|
|
121
121
|
letta/prompts/system/memgpt_modified_chat.txt,sha256=HOaPVurEftD8KsuwsclDgE2afIfklMjxhuSO96q1-6I,4656
|
|
122
122
|
letta/prompts/system/memgpt_modified_o1.txt,sha256=AxxYVjYLZwpZ6yfifh1SuPtwlJGWTcTVzw53QbkN-Ao,5492
|
|
123
|
-
letta/providers.py,sha256=
|
|
123
|
+
letta/providers.py,sha256=nWvTvVsZH1EE2aHAwihJvCIDJpgfeWAOUE_YK1x5Zj4,19635
|
|
124
124
|
letta/pytest.ini,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
125
125
|
letta/schemas/agent.py,sha256=e69lAKJQYtx92w8tM9sdLdv1hDqZ_0V_qiUiQyI-uks,7138
|
|
126
126
|
letta/schemas/api_key.py,sha256=u07yzzMn-hBAHZIIKbWY16KsgiFjSNR8lAghpMUo3_4,682
|
|
@@ -145,7 +145,7 @@ letta/schemas/openai/openai.py,sha256=Hilo5BiLAGabzxCwnwfzK5QrWqwYD8epaEKFa4Pwnd
|
|
|
145
145
|
letta/schemas/organization.py,sha256=0MOUbaiDGTXkzTjaALLI2wj4E1bL5V_0jjI6jEgFKlA,635
|
|
146
146
|
letta/schemas/passage.py,sha256=eYQMxD_XjHAi72jmqcGBU4wM4VZtSU0XK8uhQxxN3Ug,3563
|
|
147
147
|
letta/schemas/source.py,sha256=hB4Ai6Nj8dFdbxv5_Qaf4uN_cmdGmnzgc-4QnHXcV3o,2562
|
|
148
|
-
letta/schemas/tool.py,sha256=
|
|
148
|
+
letta/schemas/tool.py,sha256=itC9jIwrCPeE-w70CbkaJ7l8ueA5C9Heq_bGNmKHUcc,9598
|
|
149
149
|
letta/schemas/usage.py,sha256=lvn1ooHwLEdv6gwQpw5PBUbcwn_gwdT6HA-fCiix6sY,817
|
|
150
150
|
letta/schemas/user.py,sha256=toiA53UKB1hpH6k2xHyQNKVeDUoSB3RmLfxSwmKPAz4,1523
|
|
151
151
|
letta/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -169,19 +169,19 @@ letta/server/rest_api/routers/openai/assistants/threads.py,sha256=WXVGBaBvSNPB7Z
|
|
|
169
169
|
letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
170
170
|
letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=-uye6cm4SnoQGwxhr1N1FrSXOlnO2Hvbfj6k8JSc45k,4918
|
|
171
171
|
letta/server/rest_api/routers/v1/__init__.py,sha256=sqlVZa-u9DJwdRsp0_8YUGrac9DHguIB4wETlEDRylA,666
|
|
172
|
-
letta/server/rest_api/routers/v1/agents.py,sha256=
|
|
172
|
+
letta/server/rest_api/routers/v1/agents.py,sha256=EoUWyhiWYYxhtwZUg9agyDeCpixLqzsP9YXIk_zX7MM,25355
|
|
173
173
|
letta/server/rest_api/routers/v1/blocks.py,sha256=0WekE_yBD2U3jYgPxI0DCFjACWavCAlvm_Ybw5SZBnw,2583
|
|
174
174
|
letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
|
|
175
175
|
letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
|
|
176
176
|
letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFlJaaO4uL95VY,877
|
|
177
177
|
letta/server/rest_api/routers/v1/organizations.py,sha256=3XlHPUc6ZdMOVZNdarwDM8ZxYisoHunSZQ0ozzX5orU,2037
|
|
178
178
|
letta/server/rest_api/routers/v1/sources.py,sha256=eY_pk9jRL2Y9yIZdsTjH6EuKsfH1neaTU15MKNL0dvw,8749
|
|
179
|
-
letta/server/rest_api/routers/v1/tools.py,sha256=
|
|
179
|
+
letta/server/rest_api/routers/v1/tools.py,sha256=aVZcVxL68Oq8N6p3p_MsGrNxmnK3-UWAIHdVztFw_Nk,3794
|
|
180
180
|
letta/server/rest_api/routers/v1/users.py,sha256=bxQ-YdevjDqmgNDfbSPAG_4KEVvPNBHD_-Lp1MdeMec,3374
|
|
181
181
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
182
182
|
letta/server/rest_api/utils.py,sha256=Fc2ZGKzLaBa2sEtSTVjJ8D5M0xIwsWC0CVAOIJaD3rY,2176
|
|
183
|
-
letta/server/server.py,sha256=
|
|
184
|
-
letta/server/startup.sh,sha256=
|
|
183
|
+
letta/server/server.py,sha256=HeIucHAaf6CpV0izHTnrMfQwDX-Lbs7j9DHYogZpLAc,80255
|
|
184
|
+
letta/server/startup.sh,sha256=o2dY9wc5Ew1j_10U6dK2FYevAj79udZ-T969j5rG-a0,311
|
|
185
185
|
letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
|
|
186
186
|
letta/server/static_files/assets/index-d6b3669a.js,sha256=i1nHReU0RPnj-a5W0nNPV4Y9bQ0FOW0ztjMz8a2AE-Y,1821560
|
|
187
187
|
letta/server/static_files/favicon.ico,sha256=DezhLdFSbM8o81wCOZcV3riq7tFUOGQD4h6-vr-HuU0,342
|
|
@@ -195,15 +195,15 @@ letta/server/ws_api/protocol.py,sha256=M_-gM5iuDBwa1cuN2IGNCG5GxMJwU2d3XW93XALv9
|
|
|
195
195
|
letta/server/ws_api/server.py,sha256=C2Kv48PCwl46DQFb0ZP30s86KJLQ6dZk2AhWQEZn9pY,6004
|
|
196
196
|
letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
197
197
|
letta/services/organization_manager.py,sha256=wW4wOmKaqhHC7oTGWU38OO-dFtmRI0JVmq_k8gI1b0A,3658
|
|
198
|
-
letta/services/tool_manager.py,sha256=
|
|
199
|
-
letta/services/user_manager.py,sha256=
|
|
198
|
+
letta/services/tool_manager.py,sha256=RAY7mNGuFoVfWfxe4mOmmPp53Ghm1Wy1UtLFuUwKPhA,7082
|
|
199
|
+
letta/services/user_manager.py,sha256=5wjmnhLoxsc9FCKD0IPotfV24i_iCqmifOkhIBvshtc,4404
|
|
200
200
|
letta/settings.py,sha256=yiYNmnYKj_BdTm0cBEIvQKYGU-lCmFntqsyVfRUy3_k,3411
|
|
201
201
|
letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
|
|
202
202
|
letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
|
|
203
203
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
204
204
|
letta/utils.py,sha256=SXLEYhyp3gHyIjrxNIKNZZ5ittKo3KOj6zxgC_Trex0,31012
|
|
205
|
-
letta_nightly-0.5.1.
|
|
206
|
-
letta_nightly-0.5.1.
|
|
207
|
-
letta_nightly-0.5.1.
|
|
208
|
-
letta_nightly-0.5.1.
|
|
209
|
-
letta_nightly-0.5.1.
|
|
205
|
+
letta_nightly-0.5.1.dev20241029104141.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
206
|
+
letta_nightly-0.5.1.dev20241029104141.dist-info/METADATA,sha256=_xR7OpWIFvzC-jEsKBRLUsZ1cXbNOYvxH1ZlSB2U9EY,10660
|
|
207
|
+
letta_nightly-0.5.1.dev20241029104141.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
208
|
+
letta_nightly-0.5.1.dev20241029104141.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
209
|
+
letta_nightly-0.5.1.dev20241029104141.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|