letta-nightly 0.5.2.dev20241118104226__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 +13 -7
- letta/agent_store/db.py +4 -3
- letta/cli/cli.py +2 -1
- letta/client/client.py +33 -30
- letta/constants.py +4 -1
- letta/functions/functions.py +2 -1
- letta/llm_api/llm_api_tools.py +38 -0
- letta/llm_api/openai.py +0 -1
- letta/local_llm/utils.py +12 -2
- 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/providers.py +141 -3
- letta/schemas/block.py +31 -26
- letta/schemas/letta_base.py +1 -1
- letta/schemas/llm_config.py +1 -0
- letta/schemas/openai/chat_completion_response.py +1 -0
- letta/server/rest_api/routers/v1/blocks.py +18 -22
- letta/server/rest_api/routers/v1/sources.py +9 -3
- letta/server/server.py +20 -85
- letta/services/block_manager.py +103 -0
- letta/services/tool_manager.py +4 -0
- letta/settings.py +3 -0
- letta/utils.py +39 -0
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/METADATA +2 -1
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/RECORD +32 -30
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.2.dev20241118104226.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.
|
|
@@ -1584,7 +1591,8 @@ class Agent(BaseAgent):
|
|
|
1584
1591
|
|
|
1585
1592
|
def count_tokens(self) -> int:
|
|
1586
1593
|
"""Count the tokens in the current context window"""
|
|
1587
|
-
|
|
1594
|
+
context_window_breakdown = self.get_context_window()
|
|
1595
|
+
return context_window_breakdown.context_window_size_current
|
|
1588
1596
|
|
|
1589
1597
|
|
|
1590
1598
|
def save_agent(agent: Agent, ms: MetadataStore):
|
|
@@ -1597,7 +1605,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1597
1605
|
|
|
1598
1606
|
# NOTE: we're saving agent memory before persisting the agent to ensure
|
|
1599
1607
|
# that allocated block_ids for each memory block are present in the agent model
|
|
1600
|
-
save_agent_memory(agent=agent
|
|
1608
|
+
save_agent_memory(agent=agent)
|
|
1601
1609
|
|
|
1602
1610
|
if ms.get_agent(agent_id=agent.agent_state.id):
|
|
1603
1611
|
ms.update_agent(agent_state)
|
|
@@ -1608,7 +1616,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1608
1616
|
assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
|
|
1609
1617
|
|
|
1610
1618
|
|
|
1611
|
-
def save_agent_memory(agent: Agent
|
|
1619
|
+
def save_agent_memory(agent: Agent):
|
|
1612
1620
|
"""
|
|
1613
1621
|
Save agent memory to metadata store. Memory is a collection of blocks and each block is persisted to the block table.
|
|
1614
1622
|
|
|
@@ -1617,14 +1625,12 @@ def save_agent_memory(agent: Agent, ms: MetadataStore):
|
|
|
1617
1625
|
|
|
1618
1626
|
for block_dict in agent.memory.to_dict()["memory"].values():
|
|
1619
1627
|
# TODO: block creation should happen in one place to enforce these sort of constraints consistently.
|
|
1620
|
-
if block_dict.get("user_id", None) is None:
|
|
1621
|
-
block_dict["user_id"] = agent.agent_state.user_id
|
|
1622
1628
|
block = Block(**block_dict)
|
|
1623
1629
|
# FIXME: should we expect for block values to be None? If not, we need to figure out why that is
|
|
1624
1630
|
# the case in some tests, if so we should relax the DB constraint.
|
|
1625
1631
|
if block.value is None:
|
|
1626
1632
|
block.value = ""
|
|
1627
|
-
|
|
1633
|
+
BlockManager().create_or_update_block(block, actor=agent.user)
|
|
1628
1634
|
|
|
1629
1635
|
|
|
1630
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
|
@@ -19,7 +19,7 @@ IN_CONTEXT_MEMORY_KEYWORD = "CORE_MEMORY"
|
|
|
19
19
|
TOOL_CALL_ID_MAX_LEN = 29
|
|
20
20
|
|
|
21
21
|
# minimum context window size
|
|
22
|
-
MIN_CONTEXT_WINDOW =
|
|
22
|
+
MIN_CONTEXT_WINDOW = 4096
|
|
23
23
|
|
|
24
24
|
# embeddings
|
|
25
25
|
MAX_EMBEDDING_DIM = 4096 # maximum supported embeding size - do NOT change or else DBs will need to be reset
|
|
@@ -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/functions/functions.py
CHANGED
|
@@ -3,7 +3,7 @@ import inspect
|
|
|
3
3
|
import os
|
|
4
4
|
from textwrap import dedent # remove indentation
|
|
5
5
|
from types import ModuleType
|
|
6
|
-
from typing import Optional
|
|
6
|
+
from typing import Optional, List
|
|
7
7
|
|
|
8
8
|
from letta.constants import CLI_WARNING_PREFIX
|
|
9
9
|
from letta.functions.schema_generator import generate_schema
|
|
@@ -15,6 +15,7 @@ def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> d
|
|
|
15
15
|
# Define a custom environment with necessary imports
|
|
16
16
|
env = {
|
|
17
17
|
"Optional": Optional, # Add any other required imports here
|
|
18
|
+
"List": List
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
env.update(globals())
|
letta/llm_api/llm_api_tools.py
CHANGED
|
@@ -25,6 +25,7 @@ from letta.local_llm.constants import (
|
|
|
25
25
|
INNER_THOUGHTS_KWARG,
|
|
26
26
|
INNER_THOUGHTS_KWARG_DESCRIPTION,
|
|
27
27
|
)
|
|
28
|
+
from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
|
|
28
29
|
from letta.schemas.llm_config import LLMConfig
|
|
29
30
|
from letta.schemas.message import Message
|
|
30
31
|
from letta.schemas.openai.chat_completion_request import (
|
|
@@ -33,6 +34,7 @@ from letta.schemas.openai.chat_completion_request import (
|
|
|
33
34
|
cast_message_to_subtype,
|
|
34
35
|
)
|
|
35
36
|
from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
|
|
37
|
+
from letta.settings import ModelSettings
|
|
36
38
|
from letta.streaming_interface import (
|
|
37
39
|
AgentChunkStreamingInterface,
|
|
38
40
|
AgentRefreshStreamingInterface,
|
|
@@ -122,10 +124,19 @@ def create(
|
|
|
122
124
|
"""Return response to chat completion with backoff"""
|
|
123
125
|
from letta.utils import printd
|
|
124
126
|
|
|
127
|
+
# Count the tokens first, if there's an overflow exit early by throwing an error up the stack
|
|
128
|
+
# NOTE: we want to include a specific substring in the error message to trigger summarization
|
|
129
|
+
messages_oai_format = [m.to_openai_dict() for m in messages]
|
|
130
|
+
prompt_tokens = num_tokens_from_messages(messages=messages_oai_format, model=llm_config.model)
|
|
131
|
+
function_tokens = num_tokens_from_functions(functions=functions, model=llm_config.model) if functions else 0
|
|
132
|
+
if prompt_tokens + function_tokens > llm_config.context_window:
|
|
133
|
+
raise Exception(f"Request exceeds maximum context length ({prompt_tokens + function_tokens} > {llm_config.context_window} tokens)")
|
|
134
|
+
|
|
125
135
|
if not model_settings:
|
|
126
136
|
from letta.settings import model_settings
|
|
127
137
|
|
|
128
138
|
model_settings = model_settings
|
|
139
|
+
assert isinstance(model_settings, ModelSettings)
|
|
129
140
|
|
|
130
141
|
printd(f"Using model {llm_config.model_endpoint_type}, endpoint: {llm_config.model_endpoint}")
|
|
131
142
|
|
|
@@ -326,6 +337,33 @@ def create(
|
|
|
326
337
|
|
|
327
338
|
return response
|
|
328
339
|
|
|
340
|
+
elif llm_config.model_endpoint_type == "together":
|
|
341
|
+
"""TogetherAI endpoint that goes via /completions instead of /chat/completions"""
|
|
342
|
+
|
|
343
|
+
if stream:
|
|
344
|
+
raise NotImplementedError(f"Streaming not yet implemented for TogetherAI (via the /completions endpoint).")
|
|
345
|
+
|
|
346
|
+
if model_settings.together_api_key is None and llm_config.model_endpoint == "https://api.together.ai/v1/completions":
|
|
347
|
+
raise ValueError(f"TogetherAI key is missing from letta config file")
|
|
348
|
+
|
|
349
|
+
return get_chat_completion(
|
|
350
|
+
model=llm_config.model,
|
|
351
|
+
messages=messages,
|
|
352
|
+
functions=functions,
|
|
353
|
+
functions_python=functions_python,
|
|
354
|
+
function_call=function_call,
|
|
355
|
+
context_window=llm_config.context_window,
|
|
356
|
+
endpoint=llm_config.model_endpoint,
|
|
357
|
+
endpoint_type="vllm", # NOTE: use the vLLM path through /completions
|
|
358
|
+
wrapper=llm_config.model_wrapper,
|
|
359
|
+
user=str(user_id),
|
|
360
|
+
# hint
|
|
361
|
+
first_message=first_message,
|
|
362
|
+
# auth-related
|
|
363
|
+
auth_type="bearer_token", # NOTE: Together expects bearer token auth
|
|
364
|
+
auth_key=model_settings.together_api_key,
|
|
365
|
+
)
|
|
366
|
+
|
|
329
367
|
# local model
|
|
330
368
|
else:
|
|
331
369
|
if stream:
|
letta/llm_api/openai.py
CHANGED
letta/local_llm/utils.py
CHANGED
|
@@ -94,7 +94,10 @@ def num_tokens_from_functions(functions: List[dict], model: str = "gpt-4"):
|
|
|
94
94
|
num_tokens = 0
|
|
95
95
|
for function in functions:
|
|
96
96
|
function_tokens = len(encoding.encode(function["name"]))
|
|
97
|
-
|
|
97
|
+
if function["description"]:
|
|
98
|
+
function_tokens += len(encoding.encode(function["description"]))
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError(f"Function {function['name']} has no description, function: {function}")
|
|
98
101
|
|
|
99
102
|
if "parameters" in function:
|
|
100
103
|
parameters = function["parameters"]
|
|
@@ -184,6 +187,7 @@ def num_tokens_from_messages(messages: List[dict], model: str = "gpt-4") -> int:
|
|
|
184
187
|
https://community.openai.com/t/how-to-calculate-the-tokens-when-using-function-call/266573/11
|
|
185
188
|
"""
|
|
186
189
|
try:
|
|
190
|
+
# Attempt to search for the encoding based on the model string
|
|
187
191
|
encoding = tiktoken.encoding_for_model(model)
|
|
188
192
|
except KeyError:
|
|
189
193
|
# print("Warning: model not found. Using cl100k_base encoding.")
|
|
@@ -228,7 +232,13 @@ def num_tokens_from_messages(messages: List[dict], model: str = "gpt-4") -> int:
|
|
|
228
232
|
# num_tokens += len(encoding.encode(value["arguments"]))
|
|
229
233
|
|
|
230
234
|
else:
|
|
231
|
-
|
|
235
|
+
if value is None:
|
|
236
|
+
# raise ValueError(f"Message has null value: {key} with value: {value} - message={message}")
|
|
237
|
+
warnings.warn(f"Message has null value: {key} with value: {value} - message={message}")
|
|
238
|
+
else:
|
|
239
|
+
if not isinstance(value, str):
|
|
240
|
+
raise ValueError(f"Message has non-string value: {key} with value: {value} - message={message}")
|
|
241
|
+
num_tokens += len(encoding.encode(value))
|
|
232
242
|
|
|
233
243
|
if key == "name":
|
|
234
244
|
num_tokens += tokens_per_name
|