letta-nightly 0.5.2.dev20241119104253__py3-none-any.whl → 0.5.3.dev20241120010849__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/__init__.py +1 -1
- letta/agent.py +11 -6
- letta/agent_store/db.py +4 -3
- letta/cli/cli.py +2 -1
- letta/client/client.py +33 -30
- letta/constants.py +3 -0
- letta/metadata.py +1 -155
- letta/o1_agent.py +3 -1
- letta/orm/__init__.py +1 -0
- letta/orm/block.py +44 -0
- letta/orm/organization.py +1 -0
- letta/schemas/block.py +31 -26
- letta/schemas/letta_base.py +1 -1
- letta/server/rest_api/routers/v1/blocks.py +18 -22
- letta/server/rest_api/routers/v1/sources.py +9 -3
- letta/server/server.py +7 -84
- letta/services/block_manager.py +103 -0
- letta/utils.py +39 -0
- {letta_nightly-0.5.2.dev20241119104253.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/METADATA +2 -1
- {letta_nightly-0.5.2.dev20241119104253.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/RECORD +23 -21
- {letta_nightly-0.5.2.dev20241119104253.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.2.dev20241119104253.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.2.dev20241119104253.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
letta/agent.py
CHANGED
|
@@ -27,6 +27,7 @@ from letta.llm_api.llm_api_tools import create
|
|
|
27
27
|
from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
|
|
28
28
|
from letta.memory import ArchivalMemory, RecallMemory, summarize_messages
|
|
29
29
|
from letta.metadata import MetadataStore
|
|
30
|
+
from letta.orm import User
|
|
30
31
|
from letta.persistence_manager import LocalStateManager
|
|
31
32
|
from letta.schemas.agent import AgentState, AgentStepResponse
|
|
32
33
|
from letta.schemas.block import Block
|
|
@@ -46,6 +47,7 @@ from letta.schemas.passage import Passage
|
|
|
46
47
|
from letta.schemas.tool import Tool
|
|
47
48
|
from letta.schemas.tool_rule import TerminalToolRule
|
|
48
49
|
from letta.schemas.usage import LettaUsageStatistics
|
|
50
|
+
from letta.services.block_manager import BlockManager
|
|
49
51
|
from letta.services.source_manager import SourceManager
|
|
50
52
|
from letta.services.user_manager import UserManager
|
|
51
53
|
from letta.streaming_interface import StreamingRefreshCLIInterface
|
|
@@ -234,6 +236,7 @@ class Agent(BaseAgent):
|
|
|
234
236
|
# agents can be created from providing agent_state
|
|
235
237
|
agent_state: AgentState,
|
|
236
238
|
tools: List[Tool],
|
|
239
|
+
user: User,
|
|
237
240
|
# memory: Memory,
|
|
238
241
|
# extras
|
|
239
242
|
messages_total: Optional[int] = None, # TODO remove?
|
|
@@ -245,6 +248,8 @@ class Agent(BaseAgent):
|
|
|
245
248
|
self.agent_state = agent_state
|
|
246
249
|
assert isinstance(self.agent_state.memory, Memory), f"Memory object is not of type Memory: {type(self.agent_state.memory)}"
|
|
247
250
|
|
|
251
|
+
self.user = user
|
|
252
|
+
|
|
248
253
|
# link tools
|
|
249
254
|
self.link_tools(tools)
|
|
250
255
|
|
|
@@ -1221,7 +1226,9 @@ class Agent(BaseAgent):
|
|
|
1221
1226
|
# future if we expect templates to change often.
|
|
1222
1227
|
continue
|
|
1223
1228
|
block_id = block.get("id")
|
|
1224
|
-
|
|
1229
|
+
|
|
1230
|
+
# TODO: This is really hacky and we should probably figure out how to
|
|
1231
|
+
db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
|
|
1225
1232
|
if db_block is None:
|
|
1226
1233
|
# this case covers if someone has deleted a shared block by interacting
|
|
1227
1234
|
# with some other agent.
|
|
@@ -1598,7 +1605,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1598
1605
|
|
|
1599
1606
|
# NOTE: we're saving agent memory before persisting the agent to ensure
|
|
1600
1607
|
# that allocated block_ids for each memory block are present in the agent model
|
|
1601
|
-
save_agent_memory(agent=agent
|
|
1608
|
+
save_agent_memory(agent=agent)
|
|
1602
1609
|
|
|
1603
1610
|
if ms.get_agent(agent_id=agent.agent_state.id):
|
|
1604
1611
|
ms.update_agent(agent_state)
|
|
@@ -1609,7 +1616,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1609
1616
|
assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
|
|
1610
1617
|
|
|
1611
1618
|
|
|
1612
|
-
def save_agent_memory(agent: Agent
|
|
1619
|
+
def save_agent_memory(agent: Agent):
|
|
1613
1620
|
"""
|
|
1614
1621
|
Save agent memory to metadata store. Memory is a collection of blocks and each block is persisted to the block table.
|
|
1615
1622
|
|
|
@@ -1618,14 +1625,12 @@ def save_agent_memory(agent: Agent, ms: MetadataStore):
|
|
|
1618
1625
|
|
|
1619
1626
|
for block_dict in agent.memory.to_dict()["memory"].values():
|
|
1620
1627
|
# TODO: block creation should happen in one place to enforce these sort of constraints consistently.
|
|
1621
|
-
if block_dict.get("user_id", None) is None:
|
|
1622
|
-
block_dict["user_id"] = agent.agent_state.user_id
|
|
1623
1628
|
block = Block(**block_dict)
|
|
1624
1629
|
# FIXME: should we expect for block values to be None? If not, we need to figure out why that is
|
|
1625
1630
|
# the case in some tests, if so we should relax the DB constraint.
|
|
1626
1631
|
if block.value is None:
|
|
1627
1632
|
block.value = ""
|
|
1628
|
-
|
|
1633
|
+
BlockManager().create_or_update_block(block, actor=agent.user)
|
|
1629
1634
|
|
|
1630
1635
|
|
|
1631
1636
|
def strip_name_field_from_user_message(user_message_text: str) -> Tuple[str, Optional[str]]:
|
letta/agent_store/db.py
CHANGED
|
@@ -380,9 +380,10 @@ class PostgresStorageConnector(SQLStorageConnector):
|
|
|
380
380
|
else:
|
|
381
381
|
raise ValueError(f"Table type {table_type} not implemented")
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
if settings.pg_uri:
|
|
384
|
+
for c in self.db_model.__table__.columns:
|
|
385
|
+
if c.name == "embedding":
|
|
386
|
+
assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
|
|
386
387
|
|
|
387
388
|
from letta.server.server import db_context
|
|
388
389
|
|
letta/cli/cli.py
CHANGED
|
@@ -220,7 +220,7 @@ def run(
|
|
|
220
220
|
|
|
221
221
|
# create agent
|
|
222
222
|
tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.tools]
|
|
223
|
-
letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools)
|
|
223
|
+
letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools, user=client.user)
|
|
224
224
|
|
|
225
225
|
else: # create new agent
|
|
226
226
|
# create new agent config: override defaults with args if provided
|
|
@@ -320,6 +320,7 @@ def run(
|
|
|
320
320
|
tools=tools,
|
|
321
321
|
# gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
|
|
322
322
|
first_message_verify_mono=True if (model is not None and "gpt-4" in model) else False,
|
|
323
|
+
user=client.user,
|
|
323
324
|
)
|
|
324
325
|
save_agent(agent=letta_agent, ms=ms)
|
|
325
326
|
typer.secho(f"🎉 Created new agent '{letta_agent.agent_state.name}' (id={letta_agent.agent_state.id})", fg=typer.colors.GREEN)
|
letta/client/client.py
CHANGED
|
@@ -12,12 +12,10 @@ from letta.memory import get_memory_functions
|
|
|
12
12
|
from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
|
|
13
13
|
from letta.schemas.block import (
|
|
14
14
|
Block,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
CreatePersona,
|
|
15
|
+
BlockCreate,
|
|
16
|
+
BlockUpdate,
|
|
18
17
|
Human,
|
|
19
18
|
Persona,
|
|
20
|
-
UpdateBlock,
|
|
21
19
|
UpdateHuman,
|
|
22
20
|
UpdatePersona,
|
|
23
21
|
)
|
|
@@ -883,8 +881,8 @@ class RESTClient(AbstractClient):
|
|
|
883
881
|
else:
|
|
884
882
|
return [Block(**block) for block in response.json()]
|
|
885
883
|
|
|
886
|
-
def create_block(self, label: str,
|
|
887
|
-
request =
|
|
884
|
+
def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
|
|
885
|
+
request = BlockCreate(label=label, value=value, template=is_template, template_name=template_name)
|
|
888
886
|
response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
|
|
889
887
|
if response.status_code != 200:
|
|
890
888
|
raise ValueError(f"Failed to create block: {response.text}")
|
|
@@ -896,7 +894,7 @@ class RESTClient(AbstractClient):
|
|
|
896
894
|
return Block(**response.json())
|
|
897
895
|
|
|
898
896
|
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
|
|
899
|
-
request =
|
|
897
|
+
request = BlockUpdate(id=block_id, template_name=name, value=text)
|
|
900
898
|
response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
|
|
901
899
|
if response.status_code != 200:
|
|
902
900
|
raise ValueError(f"Failed to update block: {response.text}")
|
|
@@ -950,7 +948,7 @@ class RESTClient(AbstractClient):
|
|
|
950
948
|
Returns:
|
|
951
949
|
human (Human): Human block
|
|
952
950
|
"""
|
|
953
|
-
return self.create_block(label="human", template_name=name,
|
|
951
|
+
return self.create_block(label="human", template_name=name, value=text, is_template=True)
|
|
954
952
|
|
|
955
953
|
def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
|
|
956
954
|
"""
|
|
@@ -990,7 +988,7 @@ class RESTClient(AbstractClient):
|
|
|
990
988
|
Returns:
|
|
991
989
|
persona (Persona): Persona block
|
|
992
990
|
"""
|
|
993
|
-
return self.create_block(label="persona", template_name=name,
|
|
991
|
+
return self.create_block(label="persona", template_name=name, value=text, is_template=True)
|
|
994
992
|
|
|
995
993
|
def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
|
|
996
994
|
"""
|
|
@@ -2125,8 +2123,7 @@ class LocalClient(AbstractClient):
|
|
|
2125
2123
|
# humans / personas
|
|
2126
2124
|
|
|
2127
2125
|
def get_block_id(self, name: str, label: str) -> str:
|
|
2128
|
-
|
|
2129
|
-
block = self.server.get_blocks(name=name, label=label, user_id=self.user_id, template=True)
|
|
2126
|
+
block = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label=label, is_template=True)
|
|
2130
2127
|
if not block:
|
|
2131
2128
|
return None
|
|
2132
2129
|
return block[0].id
|
|
@@ -2142,7 +2139,7 @@ class LocalClient(AbstractClient):
|
|
|
2142
2139
|
Returns:
|
|
2143
2140
|
human (Human): Human block
|
|
2144
2141
|
"""
|
|
2145
|
-
return self.server.
|
|
2142
|
+
return self.server.block_manager.create_or_update_block(Human(template_name=name, value=text), actor=self.user)
|
|
2146
2143
|
|
|
2147
2144
|
def create_persona(self, name: str, text: str):
|
|
2148
2145
|
"""
|
|
@@ -2155,7 +2152,7 @@ class LocalClient(AbstractClient):
|
|
|
2155
2152
|
Returns:
|
|
2156
2153
|
persona (Persona): Persona block
|
|
2157
2154
|
"""
|
|
2158
|
-
return self.server.
|
|
2155
|
+
return self.server.block_manager.create_or_update_block(Persona(template_name=name, value=text), actor=self.user)
|
|
2159
2156
|
|
|
2160
2157
|
def list_humans(self):
|
|
2161
2158
|
"""
|
|
@@ -2164,7 +2161,7 @@ class LocalClient(AbstractClient):
|
|
|
2164
2161
|
Returns:
|
|
2165
2162
|
humans (List[Human]): List of human blocks
|
|
2166
2163
|
"""
|
|
2167
|
-
return self.server.get_blocks(label="human",
|
|
2164
|
+
return self.server.block_manager.get_blocks(actor=self.user, label="human", is_template=True)
|
|
2168
2165
|
|
|
2169
2166
|
def list_personas(self) -> List[Persona]:
|
|
2170
2167
|
"""
|
|
@@ -2173,7 +2170,7 @@ class LocalClient(AbstractClient):
|
|
|
2173
2170
|
Returns:
|
|
2174
2171
|
personas (List[Persona]): List of persona blocks
|
|
2175
2172
|
"""
|
|
2176
|
-
return self.server.get_blocks(label="persona",
|
|
2173
|
+
return self.server.block_manager.get_blocks(actor=self.user, label="persona", is_template=True)
|
|
2177
2174
|
|
|
2178
2175
|
def update_human(self, human_id: str, text: str):
|
|
2179
2176
|
"""
|
|
@@ -2186,7 +2183,9 @@ class LocalClient(AbstractClient):
|
|
|
2186
2183
|
Returns:
|
|
2187
2184
|
human (Human): Updated human block
|
|
2188
2185
|
"""
|
|
2189
|
-
return self.server.update_block(
|
|
2186
|
+
return self.server.block_manager.update_block(
|
|
2187
|
+
block_id=human_id, block_update=UpdateHuman(value=text, is_template=True), actor=self.user
|
|
2188
|
+
)
|
|
2190
2189
|
|
|
2191
2190
|
def update_persona(self, persona_id: str, text: str):
|
|
2192
2191
|
"""
|
|
@@ -2199,7 +2198,9 @@ class LocalClient(AbstractClient):
|
|
|
2199
2198
|
Returns:
|
|
2200
2199
|
persona (Persona): Updated persona block
|
|
2201
2200
|
"""
|
|
2202
|
-
return self.server.update_block(
|
|
2201
|
+
return self.server.block_manager.update_block(
|
|
2202
|
+
block_id=persona_id, block_update=UpdatePersona(value=text, is_template=True), actor=self.user
|
|
2203
|
+
)
|
|
2203
2204
|
|
|
2204
2205
|
def get_persona(self, id: str) -> Persona:
|
|
2205
2206
|
"""
|
|
@@ -2212,7 +2213,7 @@ class LocalClient(AbstractClient):
|
|
|
2212
2213
|
persona (Persona): Persona block
|
|
2213
2214
|
"""
|
|
2214
2215
|
assert id, f"Persona ID must be provided"
|
|
2215
|
-
return Persona(**self.server.
|
|
2216
|
+
return Persona(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
|
|
2216
2217
|
|
|
2217
2218
|
def get_human(self, id: str) -> Human:
|
|
2218
2219
|
"""
|
|
@@ -2225,7 +2226,7 @@ class LocalClient(AbstractClient):
|
|
|
2225
2226
|
human (Human): Human block
|
|
2226
2227
|
"""
|
|
2227
2228
|
assert id, f"Human ID must be provided"
|
|
2228
|
-
return Human(**self.server.
|
|
2229
|
+
return Human(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
|
|
2229
2230
|
|
|
2230
2231
|
def get_persona_id(self, name: str) -> str:
|
|
2231
2232
|
"""
|
|
@@ -2237,7 +2238,7 @@ class LocalClient(AbstractClient):
|
|
|
2237
2238
|
Returns:
|
|
2238
2239
|
id (str): ID of the persona block
|
|
2239
2240
|
"""
|
|
2240
|
-
persona = self.server.get_blocks(
|
|
2241
|
+
persona = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="persona", is_template=True)
|
|
2241
2242
|
if not persona:
|
|
2242
2243
|
return None
|
|
2243
2244
|
return persona[0].id
|
|
@@ -2252,7 +2253,7 @@ class LocalClient(AbstractClient):
|
|
|
2252
2253
|
Returns:
|
|
2253
2254
|
id (str): ID of the human block
|
|
2254
2255
|
"""
|
|
2255
|
-
human = self.server.get_blocks(
|
|
2256
|
+
human = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="human", is_template=True)
|
|
2256
2257
|
if not human:
|
|
2257
2258
|
return None
|
|
2258
2259
|
return human[0].id
|
|
@@ -2264,7 +2265,7 @@ class LocalClient(AbstractClient):
|
|
|
2264
2265
|
Args:
|
|
2265
2266
|
id (str): ID of the persona block
|
|
2266
2267
|
"""
|
|
2267
|
-
self.
|
|
2268
|
+
self.delete_block(id)
|
|
2268
2269
|
|
|
2269
2270
|
def delete_human(self, id: str):
|
|
2270
2271
|
"""
|
|
@@ -2273,7 +2274,7 @@ class LocalClient(AbstractClient):
|
|
|
2273
2274
|
Args:
|
|
2274
2275
|
id (str): ID of the human block
|
|
2275
2276
|
"""
|
|
2276
|
-
self.
|
|
2277
|
+
self.delete_block(id)
|
|
2277
2278
|
|
|
2278
2279
|
# tools
|
|
2279
2280
|
def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
@@ -2661,9 +2662,9 @@ class LocalClient(AbstractClient):
|
|
|
2661
2662
|
Returns:
|
|
2662
2663
|
blocks (List[Block]): List of blocks
|
|
2663
2664
|
"""
|
|
2664
|
-
return self.server.get_blocks(label=label,
|
|
2665
|
+
return self.server.block_manager.get_blocks(actor=self.user, label=label, is_template=templates_only)
|
|
2665
2666
|
|
|
2666
|
-
def create_block(self, label: str,
|
|
2667
|
+
def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
|
|
2667
2668
|
"""
|
|
2668
2669
|
Create a block
|
|
2669
2670
|
|
|
@@ -2675,8 +2676,8 @@ class LocalClient(AbstractClient):
|
|
|
2675
2676
|
Returns:
|
|
2676
2677
|
block (Block): Created block
|
|
2677
2678
|
"""
|
|
2678
|
-
return self.server.
|
|
2679
|
-
|
|
2679
|
+
return self.server.block_manager.create_or_update_block(
|
|
2680
|
+
Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
|
|
2680
2681
|
)
|
|
2681
2682
|
|
|
2682
2683
|
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
|
|
@@ -2691,7 +2692,9 @@ class LocalClient(AbstractClient):
|
|
|
2691
2692
|
Returns:
|
|
2692
2693
|
block (Block): Updated block
|
|
2693
2694
|
"""
|
|
2694
|
-
return self.server.update_block(
|
|
2695
|
+
return self.server.block_manager.update_block(
|
|
2696
|
+
block_id=block_id, block_update=BlockUpdate(template_name=name, value=text), actor=self.user
|
|
2697
|
+
)
|
|
2695
2698
|
|
|
2696
2699
|
def get_block(self, block_id: str) -> Block:
|
|
2697
2700
|
"""
|
|
@@ -2703,7 +2706,7 @@ class LocalClient(AbstractClient):
|
|
|
2703
2706
|
Returns:
|
|
2704
2707
|
block (Block): Block
|
|
2705
2708
|
"""
|
|
2706
|
-
return self.server.
|
|
2709
|
+
return self.server.block_manager.get_block_by_id(block_id, actor=self.user)
|
|
2707
2710
|
|
|
2708
2711
|
def delete_block(self, id: str) -> Block:
|
|
2709
2712
|
"""
|
|
@@ -2715,7 +2718,7 @@ class LocalClient(AbstractClient):
|
|
|
2715
2718
|
Returns:
|
|
2716
2719
|
block (Block): Deleted block
|
|
2717
2720
|
"""
|
|
2718
|
-
return self.server.delete_block(id)
|
|
2721
|
+
return self.server.block_manager.delete_block(id, actor=self.user)
|
|
2719
2722
|
|
|
2720
2723
|
def set_default_llm_config(self, llm_config: LLMConfig):
|
|
2721
2724
|
"""
|
letta/constants.py
CHANGED
|
@@ -158,3 +158,6 @@ RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5
|
|
|
158
158
|
# TODO Is this config or constant?
|
|
159
159
|
CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 2000
|
|
160
160
|
CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 2000
|
|
161
|
+
|
|
162
|
+
MAX_FILENAME_LENGTH = 255
|
|
163
|
+
RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"}
|
letta/metadata.py
CHANGED
|
@@ -4,23 +4,13 @@ import os
|
|
|
4
4
|
import secrets
|
|
5
5
|
from typing import List, Optional
|
|
6
6
|
|
|
7
|
-
from sqlalchemy import
|
|
8
|
-
BIGINT,
|
|
9
|
-
JSON,
|
|
10
|
-
Boolean,
|
|
11
|
-
Column,
|
|
12
|
-
DateTime,
|
|
13
|
-
Index,
|
|
14
|
-
String,
|
|
15
|
-
TypeDecorator,
|
|
16
|
-
)
|
|
7
|
+
from sqlalchemy import JSON, Column, DateTime, Index, String, TypeDecorator
|
|
17
8
|
from sqlalchemy.sql import func
|
|
18
9
|
|
|
19
10
|
from letta.config import LettaConfig
|
|
20
11
|
from letta.orm.base import Base
|
|
21
12
|
from letta.schemas.agent import AgentState
|
|
22
13
|
from letta.schemas.api_key import APIKey
|
|
23
|
-
from letta.schemas.block import Block, Human, Persona
|
|
24
14
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
25
15
|
from letta.schemas.enums import JobStatus
|
|
26
16
|
from letta.schemas.job import Job
|
|
@@ -269,63 +259,6 @@ class AgentSourceMappingModel(Base):
|
|
|
269
259
|
return f"<AgentSourceMapping(user_id='{self.user_id}', agent_id='{self.agent_id}', source_id='{self.source_id}')>"
|
|
270
260
|
|
|
271
261
|
|
|
272
|
-
class BlockModel(Base):
|
|
273
|
-
__tablename__ = "block"
|
|
274
|
-
__table_args__ = {"extend_existing": True}
|
|
275
|
-
|
|
276
|
-
id = Column(String, primary_key=True, nullable=False)
|
|
277
|
-
value = Column(String, nullable=False)
|
|
278
|
-
limit = Column(BIGINT)
|
|
279
|
-
template_name = Column(String, nullable=True, default=None)
|
|
280
|
-
template = Column(Boolean, default=False) # True: listed as possible human/persona
|
|
281
|
-
label = Column(String, nullable=False)
|
|
282
|
-
metadata_ = Column(JSON)
|
|
283
|
-
description = Column(String)
|
|
284
|
-
user_id = Column(String)
|
|
285
|
-
Index(__tablename__ + "_idx_user", user_id),
|
|
286
|
-
|
|
287
|
-
def __repr__(self) -> str:
|
|
288
|
-
return f"<Block(id='{self.id}', template_name='{self.template_name}', template='{self.template_name}', label='{self.label}', user_id='{self.user_id}')>"
|
|
289
|
-
|
|
290
|
-
def to_record(self) -> Block:
|
|
291
|
-
if self.label == "persona":
|
|
292
|
-
return Persona(
|
|
293
|
-
id=self.id,
|
|
294
|
-
value=self.value,
|
|
295
|
-
limit=self.limit,
|
|
296
|
-
template_name=self.template_name,
|
|
297
|
-
template=self.template,
|
|
298
|
-
label=self.label,
|
|
299
|
-
metadata_=self.metadata_,
|
|
300
|
-
description=self.description,
|
|
301
|
-
user_id=self.user_id,
|
|
302
|
-
)
|
|
303
|
-
elif self.label == "human":
|
|
304
|
-
return Human(
|
|
305
|
-
id=self.id,
|
|
306
|
-
value=self.value,
|
|
307
|
-
limit=self.limit,
|
|
308
|
-
template_name=self.template_name,
|
|
309
|
-
template=self.template,
|
|
310
|
-
label=self.label,
|
|
311
|
-
metadata_=self.metadata_,
|
|
312
|
-
description=self.description,
|
|
313
|
-
user_id=self.user_id,
|
|
314
|
-
)
|
|
315
|
-
else:
|
|
316
|
-
return Block(
|
|
317
|
-
id=self.id,
|
|
318
|
-
value=self.value,
|
|
319
|
-
limit=self.limit,
|
|
320
|
-
template_name=self.template_name,
|
|
321
|
-
template=self.template,
|
|
322
|
-
label=self.label,
|
|
323
|
-
metadata_=self.metadata_,
|
|
324
|
-
description=self.description,
|
|
325
|
-
user_id=self.user_id,
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
|
|
329
262
|
class JobModel(Base):
|
|
330
263
|
__tablename__ = "jobs"
|
|
331
264
|
__table_args__ = {"extend_existing": True}
|
|
@@ -425,27 +358,6 @@ class MetadataStore:
|
|
|
425
358
|
session.add(AgentModel(**fields))
|
|
426
359
|
session.commit()
|
|
427
360
|
|
|
428
|
-
@enforce_types
|
|
429
|
-
def create_block(self, block: Block):
|
|
430
|
-
with self.session_maker() as session:
|
|
431
|
-
# TODO: fix?
|
|
432
|
-
# we are only validating that more than one template block
|
|
433
|
-
# with a given name doesn't exist.
|
|
434
|
-
if (
|
|
435
|
-
session.query(BlockModel)
|
|
436
|
-
.filter(BlockModel.template_name == block.template_name)
|
|
437
|
-
.filter(BlockModel.user_id == block.user_id)
|
|
438
|
-
.filter(BlockModel.template == True)
|
|
439
|
-
.filter(BlockModel.label == block.label)
|
|
440
|
-
.count()
|
|
441
|
-
> 0
|
|
442
|
-
):
|
|
443
|
-
|
|
444
|
-
raise ValueError(f"Block with name {block.template_name} already exists")
|
|
445
|
-
|
|
446
|
-
session.add(BlockModel(**vars(block)))
|
|
447
|
-
session.commit()
|
|
448
|
-
|
|
449
361
|
@enforce_types
|
|
450
362
|
def update_agent(self, agent: AgentState):
|
|
451
363
|
with self.session_maker() as session:
|
|
@@ -457,28 +369,6 @@ class MetadataStore:
|
|
|
457
369
|
session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
|
|
458
370
|
session.commit()
|
|
459
371
|
|
|
460
|
-
@enforce_types
|
|
461
|
-
def update_block(self, block: Block):
|
|
462
|
-
with self.session_maker() as session:
|
|
463
|
-
session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
|
|
464
|
-
session.commit()
|
|
465
|
-
|
|
466
|
-
@enforce_types
|
|
467
|
-
def update_or_create_block(self, block: Block):
|
|
468
|
-
with self.session_maker() as session:
|
|
469
|
-
existing_block = session.query(BlockModel).filter(BlockModel.id == block.id).first()
|
|
470
|
-
if existing_block:
|
|
471
|
-
session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
|
|
472
|
-
else:
|
|
473
|
-
session.add(BlockModel(**vars(block)))
|
|
474
|
-
session.commit()
|
|
475
|
-
|
|
476
|
-
@enforce_types
|
|
477
|
-
def delete_block(self, block_id: str):
|
|
478
|
-
with self.session_maker() as session:
|
|
479
|
-
session.query(BlockModel).filter(BlockModel.id == block_id).delete()
|
|
480
|
-
session.commit()
|
|
481
|
-
|
|
482
372
|
@enforce_types
|
|
483
373
|
def delete_agent(self, agent_id: str):
|
|
484
374
|
with self.session_maker() as session:
|
|
@@ -513,50 +403,6 @@ class MetadataStore:
|
|
|
513
403
|
assert len(results) == 1, f"Expected 1 result, got {len(results)}" # should only be one result
|
|
514
404
|
return results[0].to_record()
|
|
515
405
|
|
|
516
|
-
@enforce_types
|
|
517
|
-
def get_block(self, block_id: str) -> Optional[Block]:
|
|
518
|
-
with self.session_maker() as session:
|
|
519
|
-
results = session.query(BlockModel).filter(BlockModel.id == block_id).all()
|
|
520
|
-
if len(results) == 0:
|
|
521
|
-
return None
|
|
522
|
-
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
|
523
|
-
return results[0].to_record()
|
|
524
|
-
|
|
525
|
-
@enforce_types
|
|
526
|
-
def get_blocks(
|
|
527
|
-
self,
|
|
528
|
-
user_id: Optional[str],
|
|
529
|
-
label: Optional[str] = None,
|
|
530
|
-
template: Optional[bool] = None,
|
|
531
|
-
template_name: Optional[str] = None,
|
|
532
|
-
id: Optional[str] = None,
|
|
533
|
-
) -> Optional[List[Block]]:
|
|
534
|
-
"""List available blocks"""
|
|
535
|
-
with self.session_maker() as session:
|
|
536
|
-
query = session.query(BlockModel)
|
|
537
|
-
|
|
538
|
-
if user_id:
|
|
539
|
-
query = query.filter(BlockModel.user_id == user_id)
|
|
540
|
-
|
|
541
|
-
if label:
|
|
542
|
-
query = query.filter(BlockModel.label == label)
|
|
543
|
-
|
|
544
|
-
if template_name:
|
|
545
|
-
query = query.filter(BlockModel.template_name == template_name)
|
|
546
|
-
|
|
547
|
-
if id:
|
|
548
|
-
query = query.filter(BlockModel.id == id)
|
|
549
|
-
|
|
550
|
-
if template:
|
|
551
|
-
query = query.filter(BlockModel.template == template)
|
|
552
|
-
|
|
553
|
-
results = query.all()
|
|
554
|
-
|
|
555
|
-
if len(results) == 0:
|
|
556
|
-
return None
|
|
557
|
-
|
|
558
|
-
return [r.to_record() for r in results]
|
|
559
|
-
|
|
560
406
|
# agent source metadata
|
|
561
407
|
@enforce_types
|
|
562
408
|
def attach_source(self, user_id: str, agent_id: str, source_id: str):
|
letta/o1_agent.py
CHANGED
|
@@ -8,6 +8,7 @@ from letta.schemas.message import Message
|
|
|
8
8
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
9
9
|
from letta.schemas.tool import Tool
|
|
10
10
|
from letta.schemas.usage import LettaUsageStatistics
|
|
11
|
+
from letta.schemas.user import User
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def send_thinking_message(self: "Agent", message: str) -> Optional[str]:
|
|
@@ -43,11 +44,12 @@ class O1Agent(Agent):
|
|
|
43
44
|
self,
|
|
44
45
|
interface: AgentInterface,
|
|
45
46
|
agent_state: AgentState,
|
|
47
|
+
user: User,
|
|
46
48
|
tools: List[Tool] = [],
|
|
47
49
|
max_thinking_steps: int = 10,
|
|
48
50
|
first_message_verify_mono: bool = False,
|
|
49
51
|
):
|
|
50
|
-
super().__init__(interface, agent_state, tools)
|
|
52
|
+
super().__init__(interface, agent_state, tools, user)
|
|
51
53
|
self.max_thinking_steps = max_thinking_steps
|
|
52
54
|
self.tools = tools
|
|
53
55
|
self.first_message_verify_mono = first_message_verify_mono
|
letta/orm/__init__.py
CHANGED
letta/orm/block.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, Type
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import JSON, BigInteger, Integer
|
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
+
|
|
6
|
+
from letta.orm.mixins import OrganizationMixin
|
|
7
|
+
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
8
|
+
from letta.schemas.block import Block as PydanticBlock
|
|
9
|
+
from letta.schemas.block import Human, Persona
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from letta.orm.organization import Organization
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Block(OrganizationMixin, SqlalchemyBase):
|
|
16
|
+
"""Blocks are sections of the LLM context, representing a specific part of the total Memory"""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "block"
|
|
19
|
+
__pydantic_model__ = PydanticBlock
|
|
20
|
+
|
|
21
|
+
template_name: Mapped[Optional[str]] = mapped_column(
|
|
22
|
+
nullable=True, doc="the unique name that identifies a block in a human-readable way"
|
|
23
|
+
)
|
|
24
|
+
description: Mapped[Optional[str]] = mapped_column(nullable=True, doc="a description of the block for context")
|
|
25
|
+
label: Mapped[str] = mapped_column(doc="the type of memory block in use, ie 'human', 'persona', 'system'")
|
|
26
|
+
is_template: Mapped[bool] = mapped_column(
|
|
27
|
+
doc="whether the block is a template (e.g. saved human/persona options as baselines for other templates)", default=False
|
|
28
|
+
)
|
|
29
|
+
value: Mapped[str] = mapped_column(doc="Text content of the block for the respective section of core memory.")
|
|
30
|
+
limit: Mapped[BigInteger] = mapped_column(Integer, default=2000, doc="Character limit of the block.")
|
|
31
|
+
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, default={}, doc="arbitrary information related to the block.")
|
|
32
|
+
|
|
33
|
+
# relationships
|
|
34
|
+
organization: Mapped[Optional["Organization"]] = relationship("Organization")
|
|
35
|
+
|
|
36
|
+
def to_pydantic(self) -> Type:
|
|
37
|
+
match self.label:
|
|
38
|
+
case "human":
|
|
39
|
+
Schema = Human
|
|
40
|
+
case "persona":
|
|
41
|
+
Schema = Persona
|
|
42
|
+
case _:
|
|
43
|
+
Schema = PydanticBlock
|
|
44
|
+
return Schema.model_validate(self)
|
letta/orm/organization.py
CHANGED
|
@@ -23,6 +23,7 @@ class Organization(SqlalchemyBase):
|
|
|
23
23
|
# relationships
|
|
24
24
|
users: Mapped[List["User"]] = relationship("User", back_populates="organization", cascade="all, delete-orphan")
|
|
25
25
|
tools: Mapped[List["Tool"]] = relationship("Tool", back_populates="organization", cascade="all, delete-orphan")
|
|
26
|
+
blocks: Mapped[List["Block"]] = relationship("Block", back_populates="organization", cascade="all, delete-orphan")
|
|
26
27
|
sources: Mapped[List["Source"]] = relationship("Source", back_populates="organization", cascade="all, delete-orphan")
|
|
27
28
|
agents_tags: Mapped[List["AgentsTags"]] = relationship("AgentsTags", back_populates="organization", cascade="all, delete-orphan")
|
|
28
29
|
files: Mapped[List["FileMetadata"]] = relationship("FileMetadata", back_populates="organization", cascade="all, delete-orphan")
|