letta-nightly 0.8.17.dev20250723104501__py3-none-any.whl → 0.9.0.dev20250724104456__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/__init__.py +5 -3
- letta/agent.py +3 -2
- letta/agents/base_agent.py +4 -1
- letta/agents/voice_agent.py +1 -0
- letta/constants.py +4 -2
- letta/functions/schema_generator.py +2 -1
- letta/groups/dynamic_multi_agent.py +1 -0
- letta/helpers/converters.py +13 -5
- letta/helpers/json_helpers.py +6 -1
- letta/llm_api/anthropic.py +2 -2
- letta/llm_api/aws_bedrock.py +24 -94
- letta/llm_api/deepseek.py +1 -1
- letta/llm_api/google_ai_client.py +0 -38
- letta/llm_api/google_constants.py +6 -3
- letta/llm_api/helpers.py +1 -1
- letta/llm_api/llm_api_tools.py +4 -7
- letta/llm_api/mistral.py +12 -37
- letta/llm_api/openai.py +17 -17
- letta/llm_api/sample_response_jsons/aws_bedrock.json +38 -0
- letta/llm_api/sample_response_jsons/lmstudio_embedding_list.json +15 -0
- letta/llm_api/sample_response_jsons/lmstudio_model_list.json +15 -0
- letta/local_llm/constants.py +2 -23
- letta/local_llm/json_parser.py +11 -1
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +9 -9
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +7 -8
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +6 -6
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +3 -3
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +1 -1
- letta/local_llm/ollama/api.py +2 -2
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +33 -2
- letta/orm/files_agents.py +13 -10
- letta/orm/mixins.py +8 -0
- letta/orm/prompt.py +13 -0
- letta/orm/sqlite_functions.py +61 -17
- letta/otel/db_pool_monitoring.py +13 -12
- letta/schemas/agent.py +69 -4
- letta/schemas/agent_file.py +2 -0
- letta/schemas/block.py +11 -0
- letta/schemas/embedding_config.py +15 -3
- letta/schemas/enums.py +2 -0
- letta/schemas/file.py +1 -1
- letta/schemas/folder.py +74 -0
- letta/schemas/memory.py +12 -6
- letta/schemas/prompt.py +9 -0
- letta/schemas/providers/__init__.py +47 -0
- letta/schemas/providers/anthropic.py +78 -0
- letta/schemas/providers/azure.py +80 -0
- letta/schemas/providers/base.py +201 -0
- letta/schemas/providers/bedrock.py +78 -0
- letta/schemas/providers/cerebras.py +79 -0
- letta/schemas/providers/cohere.py +18 -0
- letta/schemas/providers/deepseek.py +63 -0
- letta/schemas/providers/google_gemini.py +102 -0
- letta/schemas/providers/google_vertex.py +54 -0
- letta/schemas/providers/groq.py +35 -0
- letta/schemas/providers/letta.py +39 -0
- letta/schemas/providers/lmstudio.py +97 -0
- letta/schemas/providers/mistral.py +41 -0
- letta/schemas/providers/ollama.py +151 -0
- letta/schemas/providers/openai.py +241 -0
- letta/schemas/providers/together.py +85 -0
- letta/schemas/providers/vllm.py +57 -0
- letta/schemas/providers/xai.py +66 -0
- letta/server/db.py +0 -5
- letta/server/rest_api/app.py +4 -3
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +152 -4
- letta/server/rest_api/routers/v1/folders.py +490 -0
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/sources.py +21 -26
- letta/server/rest_api/routers/v1/tools.py +90 -15
- letta/server/server.py +50 -95
- letta/services/agent_manager.py +420 -81
- letta/services/agent_serialization_manager.py +707 -0
- letta/services/block_manager.py +132 -11
- letta/services/file_manager.py +104 -29
- letta/services/file_processor/embedder/pinecone_embedder.py +8 -2
- letta/services/file_processor/file_processor.py +75 -24
- letta/services/file_processor/parser/markitdown_parser.py +95 -0
- letta/services/files_agents_manager.py +57 -17
- letta/services/group_manager.py +7 -0
- letta/services/helpers/agent_manager_helper.py +25 -15
- letta/services/provider_manager.py +2 -2
- letta/services/source_manager.py +35 -16
- letta/services/tool_executor/files_tool_executor.py +12 -5
- letta/services/tool_manager.py +12 -0
- letta/services/tool_sandbox/e2b_sandbox.py +52 -48
- letta/settings.py +9 -6
- letta/streaming_utils.py +2 -1
- letta/utils.py +34 -1
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/METADATA +9 -8
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/RECORD +96 -68
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.17.dev20250723104501.dist-info → letta_nightly-0.9.0.dev20250724104456.dist-info}/entry_points.txt +0 -0
letta/orm/sqlite_functions.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import base64
|
2
1
|
import sqlite3
|
3
2
|
from typing import Optional, Union
|
4
3
|
|
@@ -7,11 +6,15 @@ from sqlalchemy import event
|
|
7
6
|
from sqlalchemy.engine import Engine
|
8
7
|
|
9
8
|
from letta.constants import MAX_EMBEDDING_DIM
|
9
|
+
from letta.settings import DatabaseChoice, settings
|
10
|
+
|
11
|
+
if settings.database_engine == DatabaseChoice.SQLITE:
|
12
|
+
import sqlite_vec
|
10
13
|
|
11
14
|
|
12
15
|
def adapt_array(arr):
|
13
16
|
"""
|
14
|
-
Converts numpy array to binary for SQLite storage
|
17
|
+
Converts numpy array to binary for SQLite storage using sqlite-vec
|
15
18
|
"""
|
16
19
|
if arr is None:
|
17
20
|
return None
|
@@ -21,15 +24,14 @@ def adapt_array(arr):
|
|
21
24
|
elif not isinstance(arr, np.ndarray):
|
22
25
|
raise ValueError(f"Unsupported type: {type(arr)}")
|
23
26
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
return sqlite3.Binary(base64_data)
|
27
|
+
# Ensure float32 for compatibility
|
28
|
+
arr = arr.astype(np.float32)
|
29
|
+
return sqlite_vec.serialize_float32(arr.tolist())
|
28
30
|
|
29
31
|
|
30
32
|
def convert_array(text):
|
31
33
|
"""
|
32
|
-
Converts binary back to numpy array
|
34
|
+
Converts binary back to numpy array using sqlite-vec format
|
33
35
|
"""
|
34
36
|
if text is None:
|
35
37
|
return None
|
@@ -41,13 +43,11 @@ def convert_array(text):
|
|
41
43
|
# Handle both bytes and sqlite3.Binary
|
42
44
|
binary_data = bytes(text) if isinstance(text, sqlite3.Binary) else text
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
except Exception:
|
50
|
-
return None
|
46
|
+
# Use sqlite-vec native format
|
47
|
+
if len(binary_data) % 4 == 0: # Must be divisible by 4 for float32
|
48
|
+
return np.frombuffer(binary_data, dtype=np.float32)
|
49
|
+
else:
|
50
|
+
raise ValueError(f"Invalid sqlite-vec binary data length: {len(binary_data)}")
|
51
51
|
|
52
52
|
|
53
53
|
def verify_embedding_dimension(embedding: np.ndarray, expected_dim: int = MAX_EMBEDDING_DIM) -> bool:
|
@@ -131,11 +131,55 @@ def cosine_distance(embedding1, embedding2, expected_dim=MAX_EMBEDDING_DIM):
|
|
131
131
|
return distance
|
132
132
|
|
133
133
|
|
134
|
+
# Note: sqlite-vec provides native SQL functions for vector operations
|
135
|
+
# We don't need custom Python distance functions since sqlite-vec handles this at the SQL level
|
136
|
+
|
137
|
+
|
134
138
|
@event.listens_for(Engine, "connect")
|
135
139
|
def register_functions(dbapi_connection, connection_record):
|
136
|
-
"""Register SQLite functions"""
|
137
|
-
|
138
|
-
|
140
|
+
"""Register SQLite functions and enable sqlite-vec extension"""
|
141
|
+
# Check for both sync SQLite connections and async aiosqlite connections
|
142
|
+
is_sqlite_connection = isinstance(dbapi_connection, sqlite3.Connection)
|
143
|
+
is_aiosqlite_connection = hasattr(dbapi_connection, "_connection") and str(type(dbapi_connection)).find("aiosqlite") != -1
|
144
|
+
|
145
|
+
if is_sqlite_connection or is_aiosqlite_connection:
|
146
|
+
# Get the actual SQLite connection for async connections
|
147
|
+
actual_connection = dbapi_connection._connection if is_aiosqlite_connection else dbapi_connection
|
148
|
+
|
149
|
+
# Enable sqlite-vec extension
|
150
|
+
try:
|
151
|
+
if is_aiosqlite_connection:
|
152
|
+
# For aiosqlite connections, we cannot use async operations in sync event handlers
|
153
|
+
# The extension will need to be loaded per-connection when actually used
|
154
|
+
print("Detected aiosqlite connection - sqlite-vec will be loaded per-query")
|
155
|
+
else:
|
156
|
+
# For sync connections
|
157
|
+
dbapi_connection.enable_load_extension(True)
|
158
|
+
sqlite_vec.load(dbapi_connection)
|
159
|
+
dbapi_connection.enable_load_extension(False)
|
160
|
+
print("Successfully loaded sqlite-vec extension (sync)")
|
161
|
+
except Exception as e:
|
162
|
+
raise RuntimeError(f"Failed to load sqlite-vec extension: {e}")
|
163
|
+
|
164
|
+
# Register custom cosine_distance function for backward compatibility
|
165
|
+
try:
|
166
|
+
if is_aiosqlite_connection:
|
167
|
+
# Try to register function on the actual connection, even though it might be async
|
168
|
+
# This may require the function to be registered per-connection
|
169
|
+
print("Attempting function registration for aiosqlite connection")
|
170
|
+
# For async connections, we need to register the function differently
|
171
|
+
# We'll use the sync-style registration on the underlying connection
|
172
|
+
raw_conn = getattr(actual_connection, "_connection", actual_connection)
|
173
|
+
if hasattr(raw_conn, "create_function"):
|
174
|
+
raw_conn.create_function("cosine_distance", 2, cosine_distance)
|
175
|
+
print("Successfully registered cosine_distance for aiosqlite")
|
176
|
+
else:
|
177
|
+
dbapi_connection.create_function("cosine_distance", 2, cosine_distance)
|
178
|
+
print("Successfully registered cosine_distance for sync connection")
|
179
|
+
except Exception as e:
|
180
|
+
raise RuntimeError(f"Failed to register cosine_distance function: {e}")
|
181
|
+
else:
|
182
|
+
print(f"Warning: Not a SQLite connection, but instead {type(dbapi_connection)}: skipping function registration")
|
139
183
|
|
140
184
|
|
141
185
|
# Register adapters and converters for numpy arrays
|
letta/otel/db_pool_monitoring.py
CHANGED
@@ -89,18 +89,18 @@ class DatabasePoolMonitor:
|
|
89
89
|
try:
|
90
90
|
from letta.otel.metric_registry import MetricRegistry
|
91
91
|
|
92
|
-
# Record current pool statistics
|
93
|
-
pool_stats = self._get_pool_stats(pool)
|
94
92
|
attrs = {
|
95
93
|
"engine_name": engine_name,
|
96
94
|
**get_ctx_attributes(),
|
97
95
|
}
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
MetricRegistry().
|
96
|
+
# Record current pool statistics
|
97
|
+
if isinstance(pool, QueuePool):
|
98
|
+
pool_stats = self._get_pool_stats(pool)
|
99
|
+
MetricRegistry().db_pool_connections_checked_out_gauge.set(pool_stats["checked_out"], attributes=attrs)
|
100
|
+
MetricRegistry().db_pool_connections_available_gauge.set(pool_stats["available"], attributes=attrs)
|
101
|
+
MetricRegistry().db_pool_connections_total_gauge.set(pool_stats["total"], attributes=attrs)
|
102
|
+
if pool_stats["overflow"] is not None:
|
103
|
+
MetricRegistry().db_pool_connections_overflow_gauge.set(pool_stats["overflow"], attributes=attrs)
|
104
104
|
|
105
105
|
# Record checkout event
|
106
106
|
attrs["event"] = "checkout"
|
@@ -137,15 +137,16 @@ class DatabasePoolMonitor:
|
|
137
137
|
try:
|
138
138
|
from letta.otel.metric_registry import MetricRegistry
|
139
139
|
|
140
|
-
# Record current pool statistics after checkin
|
141
|
-
pool_stats = self._get_pool_stats(pool)
|
142
140
|
attrs = {
|
143
141
|
"engine_name": engine_name,
|
144
142
|
**get_ctx_attributes(),
|
145
143
|
}
|
146
144
|
|
147
|
-
|
148
|
-
|
145
|
+
# Record current pool statistics after checkin
|
146
|
+
if isinstance(pool, QueuePool):
|
147
|
+
pool_stats = self._get_pool_stats(pool)
|
148
|
+
MetricRegistry().db_pool_connections_checked_out_gauge.set(pool_stats["checked_out"], attributes=attrs)
|
149
|
+
MetricRegistry().db_pool_connections_available_gauge.set(pool_stats["available"], attributes=attrs)
|
149
150
|
|
150
151
|
# Record checkin event
|
151
152
|
attrs["event"] = "checkin"
|
letta/schemas/agent.py
CHANGED
@@ -19,7 +19,7 @@ from letta.schemas.response_format import ResponseFormatUnion
|
|
19
19
|
from letta.schemas.source import Source
|
20
20
|
from letta.schemas.tool import Tool
|
21
21
|
from letta.schemas.tool_rule import ToolRule
|
22
|
-
from letta.utils import create_random_username
|
22
|
+
from letta.utils import calculate_file_defaults_based_on_context_window, create_random_username
|
23
23
|
|
24
24
|
|
25
25
|
class AgentType(str, Enum):
|
@@ -112,6 +112,16 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
|
|
112
112
|
# timezone
|
113
113
|
timezone: Optional[str] = Field(None, description="The timezone of the agent (IANA format).")
|
114
114
|
|
115
|
+
# file related controls
|
116
|
+
max_files_open: Optional[int] = Field(
|
117
|
+
None,
|
118
|
+
description="Maximum number of files that can be open at once for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
119
|
+
)
|
120
|
+
per_file_view_window_char_limit: Optional[int] = Field(
|
121
|
+
None,
|
122
|
+
description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
123
|
+
)
|
124
|
+
|
115
125
|
def get_agent_env_vars_as_dict(self) -> Dict[str, str]:
|
116
126
|
# Get environment variables for this agent specifically
|
117
127
|
per_agent_env_vars = {}
|
@@ -119,6 +129,27 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
|
|
119
129
|
per_agent_env_vars[agent_env_var_obj.key] = agent_env_var_obj.value
|
120
130
|
return per_agent_env_vars
|
121
131
|
|
132
|
+
@model_validator(mode="after")
|
133
|
+
def set_file_defaults_based_on_context_window(self) -> "AgentState":
|
134
|
+
"""Set reasonable defaults for file-related fields based on the model's context window size."""
|
135
|
+
# Only set defaults if not explicitly provided
|
136
|
+
if self.max_files_open is not None and self.per_file_view_window_char_limit is not None:
|
137
|
+
return self
|
138
|
+
|
139
|
+
# Get context window size from llm_config
|
140
|
+
context_window = self.llm_config.context_window if self.llm_config and self.llm_config.context_window else None
|
141
|
+
|
142
|
+
# Calculate defaults using the helper function
|
143
|
+
default_max_files, default_char_limit = calculate_file_defaults_based_on_context_window(context_window)
|
144
|
+
|
145
|
+
# Apply defaults only if not set
|
146
|
+
if self.max_files_open is None:
|
147
|
+
self.max_files_open = default_max_files
|
148
|
+
if self.per_file_view_window_char_limit is None:
|
149
|
+
self.per_file_view_window_char_limit = default_char_limit
|
150
|
+
|
151
|
+
return self
|
152
|
+
|
122
153
|
|
123
154
|
class CreateAgent(BaseModel, validate_assignment=True): #
|
124
155
|
# all optional as server can generate defaults
|
@@ -197,6 +228,14 @@ class CreateAgent(BaseModel, validate_assignment=True): #
|
|
197
228
|
enable_sleeptime: Optional[bool] = Field(None, description="If set to True, memory management will move to a background agent thread.")
|
198
229
|
response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the agent.")
|
199
230
|
timezone: Optional[str] = Field(None, description="The timezone of the agent (IANA format).")
|
231
|
+
max_files_open: Optional[int] = Field(
|
232
|
+
None,
|
233
|
+
description="Maximum number of files that can be open at once for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
234
|
+
)
|
235
|
+
per_file_view_window_char_limit: Optional[int] = Field(
|
236
|
+
None,
|
237
|
+
description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
238
|
+
)
|
200
239
|
|
201
240
|
@field_validator("name")
|
202
241
|
@classmethod
|
@@ -291,6 +330,14 @@ class UpdateAgent(BaseModel):
|
|
291
330
|
last_run_completion: Optional[datetime] = Field(None, description="The timestamp when the agent last completed a run.")
|
292
331
|
last_run_duration_ms: Optional[int] = Field(None, description="The duration in milliseconds of the agent's last run.")
|
293
332
|
timezone: Optional[str] = Field(None, description="The timezone of the agent (IANA format).")
|
333
|
+
max_files_open: Optional[int] = Field(
|
334
|
+
None,
|
335
|
+
description="Maximum number of files that can be open at once for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
336
|
+
)
|
337
|
+
per_file_view_window_char_limit: Optional[int] = Field(
|
338
|
+
None,
|
339
|
+
description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.",
|
340
|
+
)
|
294
341
|
|
295
342
|
class Config:
|
296
343
|
extra = "ignore" # Ignores extra fields
|
@@ -313,6 +360,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
313
360
|
return (
|
314
361
|
"{% if sources %}"
|
315
362
|
"<directories>\n"
|
363
|
+
"{% if max_files_open %}"
|
364
|
+
"<file_limits>\n"
|
365
|
+
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
|
366
|
+
"- max_files_open={{ max_files_open }}\n"
|
367
|
+
"</file_limits>\n"
|
368
|
+
"{% endif %}"
|
316
369
|
"{% for source in sources %}"
|
317
370
|
f'<directory name="{{{{ source.name }}}}">\n'
|
318
371
|
"{% if source.description %}"
|
@@ -323,7 +376,7 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
323
376
|
"{% endif %}"
|
324
377
|
"{% if file_blocks %}"
|
325
378
|
"{% for block in file_blocks %}"
|
326
|
-
"{% if block.
|
379
|
+
"{% if block.source_id and block.source_id == source.id %}"
|
327
380
|
f"<file status=\"{{{{ '{FileStatus.open.value}' if block.value else '{FileStatus.closed.value}' }}}}\">\n"
|
328
381
|
"<{{ block.label }}>\n"
|
329
382
|
"<description>\n"
|
@@ -380,6 +433,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
380
433
|
"{% endif %}"
|
381
434
|
"\n\n{% if sources %}"
|
382
435
|
"<directories>\n"
|
436
|
+
"{% if max_files_open %}"
|
437
|
+
"<file_limits>\n"
|
438
|
+
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
|
439
|
+
"- max_files_open={{ max_files_open }}\n"
|
440
|
+
"</file_limits>\n"
|
441
|
+
"{% endif %}"
|
383
442
|
"{% for source in sources %}"
|
384
443
|
f'<directory name="{{{{ source.name }}}}">\n'
|
385
444
|
"{% if source.description %}"
|
@@ -390,7 +449,7 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
390
449
|
"{% endif %}"
|
391
450
|
"{% if file_blocks %}"
|
392
451
|
"{% for block in file_blocks %}"
|
393
|
-
"{% if block.
|
452
|
+
"{% if block.source_id and block.source_id == source.id %}"
|
394
453
|
f"<file status=\"{{{{ '{FileStatus.open.value}' if block.value else '{FileStatus.closed.value}' }}}}\" name=\"{{{{ block.label }}}}\">\n"
|
395
454
|
"{% if block.description %}"
|
396
455
|
"<description>\n"
|
@@ -446,6 +505,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
446
505
|
"{% endif %}"
|
447
506
|
"\n\n{% if sources %}"
|
448
507
|
"<directories>\n"
|
508
|
+
"{% if max_files_open %}"
|
509
|
+
"<file_limits>\n"
|
510
|
+
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
|
511
|
+
"- max_files_open={{ max_files_open }}\n"
|
512
|
+
"</file_limits>\n"
|
513
|
+
"{% endif %}"
|
449
514
|
"{% for source in sources %}"
|
450
515
|
f'<directory name="{{{{ source.name }}}}">\n'
|
451
516
|
"{% if source.description %}"
|
@@ -456,7 +521,7 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
456
521
|
"{% endif %}"
|
457
522
|
"{% if file_blocks %}"
|
458
523
|
"{% for block in file_blocks %}"
|
459
|
-
"{% if block.
|
524
|
+
"{% if block.source_id and block.source_id == source.id %}"
|
460
525
|
f"<file status=\"{{{{ '{FileStatus.open.value}' if block.value else '{FileStatus.closed.value}' }}}}\" name=\"{{{{ block.label }}}}\">\n"
|
461
526
|
"{% if block.description %}"
|
462
527
|
"<description>\n"
|
letta/schemas/agent_file.py
CHANGED
@@ -145,6 +145,8 @@ class AgentSchema(CreateAgent):
|
|
145
145
|
enable_sleeptime=False, # TODO: Need to figure out how to patch this
|
146
146
|
response_format=agent_state.response_format,
|
147
147
|
timezone=agent_state.timezone or "UTC",
|
148
|
+
max_files_open=agent_state.max_files_open,
|
149
|
+
per_file_view_window_char_limit=agent_state.per_file_view_window_char_limit,
|
148
150
|
)
|
149
151
|
|
150
152
|
messages = await message_manager.list_messages_for_agent_async(
|
letta/schemas/block.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
from datetime import datetime
|
1
2
|
from typing import Optional
|
2
3
|
|
3
4
|
from pydantic import Field, model_validator
|
@@ -79,6 +80,16 @@ class Block(BaseBlock):
|
|
79
80
|
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that last updated this Block.")
|
80
81
|
|
81
82
|
|
83
|
+
class FileBlock(Block):
|
84
|
+
file_id: str = Field(..., description="Unique identifier of the file.")
|
85
|
+
source_id: str = Field(..., description="Unique identifier of the source.")
|
86
|
+
is_open: bool = Field(..., description="True if the agent currently has the file open.")
|
87
|
+
last_accessed_at: Optional[datetime] = Field(
|
88
|
+
default_factory=datetime.utcnow,
|
89
|
+
description="UTC timestamp of the agent’s most recent access to this file. Any operations from the open, close, or search tools will update this field.",
|
90
|
+
)
|
91
|
+
|
92
|
+
|
82
93
|
class Human(Block):
|
83
94
|
"""Human block of the LLM context"""
|
84
95
|
|
@@ -2,6 +2,8 @@ from typing import Literal, Optional
|
|
2
2
|
|
3
3
|
from pydantic import BaseModel, Field
|
4
4
|
|
5
|
+
from letta.constants import DEFAULT_EMBEDDING_CHUNK_SIZE
|
6
|
+
|
5
7
|
|
6
8
|
class EmbeddingConfig(BaseModel):
|
7
9
|
"""
|
@@ -40,6 +42,7 @@ class EmbeddingConfig(BaseModel):
|
|
40
42
|
"hugging-face",
|
41
43
|
"mistral",
|
42
44
|
"together", # completions endpoint
|
45
|
+
"pinecone",
|
43
46
|
] = Field(..., description="The endpoint type for the model.")
|
44
47
|
embedding_endpoint: Optional[str] = Field(None, description="The endpoint for the model (`None` if local).")
|
45
48
|
embedding_model: str = Field(..., description="The model for the embedding.")
|
@@ -62,7 +65,7 @@ class EmbeddingConfig(BaseModel):
|
|
62
65
|
embedding_endpoint_type="openai",
|
63
66
|
embedding_endpoint="https://api.openai.com/v1",
|
64
67
|
embedding_dim=1536,
|
65
|
-
embedding_chunk_size=
|
68
|
+
embedding_chunk_size=DEFAULT_EMBEDDING_CHUNK_SIZE,
|
66
69
|
)
|
67
70
|
if (model_name == "text-embedding-3-small" and provider == "openai") or (not model_name and provider == "openai"):
|
68
71
|
return cls(
|
@@ -70,16 +73,25 @@ class EmbeddingConfig(BaseModel):
|
|
70
73
|
embedding_endpoint_type="openai",
|
71
74
|
embedding_endpoint="https://api.openai.com/v1",
|
72
75
|
embedding_dim=2000,
|
73
|
-
embedding_chunk_size=
|
76
|
+
embedding_chunk_size=DEFAULT_EMBEDDING_CHUNK_SIZE,
|
74
77
|
)
|
75
78
|
elif model_name == "letta":
|
76
79
|
return cls(
|
77
80
|
embedding_endpoint="https://embeddings.memgpt.ai",
|
78
81
|
embedding_model="BAAI/bge-large-en-v1.5",
|
79
82
|
embedding_dim=1024,
|
80
|
-
embedding_chunk_size=
|
83
|
+
embedding_chunk_size=DEFAULT_EMBEDDING_CHUNK_SIZE,
|
81
84
|
embedding_endpoint_type="hugging-face",
|
82
85
|
)
|
86
|
+
elif provider == "pinecone":
|
87
|
+
# default config for pinecone with empty endpoint
|
88
|
+
return cls(
|
89
|
+
embedding_endpoint=None,
|
90
|
+
embedding_model="llama-text-embed-v2",
|
91
|
+
embedding_dim=1536, # assuming default openai dimension
|
92
|
+
embedding_chunk_size=DEFAULT_EMBEDDING_CHUNK_SIZE,
|
93
|
+
embedding_endpoint_type="pinecone",
|
94
|
+
)
|
83
95
|
else:
|
84
96
|
raise ValueError(f"Model {model_name} not supported.")
|
85
97
|
|
letta/schemas/enums.py
CHANGED
@@ -8,6 +8,7 @@ class ProviderType(str, Enum):
|
|
8
8
|
openai = "openai"
|
9
9
|
letta = "letta"
|
10
10
|
deepseek = "deepseek"
|
11
|
+
cerebras = "cerebras"
|
11
12
|
lmstudio_openai = "lmstudio_openai"
|
12
13
|
xai = "xai"
|
13
14
|
mistral = "mistral"
|
@@ -17,6 +18,7 @@ class ProviderType(str, Enum):
|
|
17
18
|
azure = "azure"
|
18
19
|
vllm = "vllm"
|
19
20
|
bedrock = "bedrock"
|
21
|
+
cohere = "cohere"
|
20
22
|
|
21
23
|
|
22
24
|
class ProviderCategory(str, Enum):
|
letta/schemas/file.py
CHANGED
@@ -67,7 +67,7 @@ class FileAgentBase(LettaBase):
|
|
67
67
|
# Core file-agent association fields
|
68
68
|
agent_id: str = Field(..., description="Unique identifier of the agent.")
|
69
69
|
file_id: str = Field(..., description="Unique identifier of the file.")
|
70
|
-
source_id: str = Field(..., description="Unique identifier of the source
|
70
|
+
source_id: str = Field(..., description="Unique identifier of the source.")
|
71
71
|
file_name: str = Field(..., description="Name of the file.")
|
72
72
|
is_open: bool = Field(True, description="True if the agent currently has the file open.")
|
73
73
|
visible_content: Optional[str] = Field(
|
letta/schemas/folder.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from pydantic import Field
|
5
|
+
|
6
|
+
from letta.schemas.embedding_config import EmbeddingConfig
|
7
|
+
from letta.schemas.letta_base import LettaBase
|
8
|
+
|
9
|
+
|
10
|
+
class BaseFolder(LettaBase):
|
11
|
+
"""
|
12
|
+
Shared attributes across all folder schemas.
|
13
|
+
"""
|
14
|
+
|
15
|
+
__id_prefix__ = "source" # TODO: change to "folder"
|
16
|
+
|
17
|
+
# Core folder fields
|
18
|
+
name: str = Field(..., description="The name of the folder.")
|
19
|
+
description: Optional[str] = Field(None, description="The description of the folder.")
|
20
|
+
instructions: Optional[str] = Field(None, description="Instructions for how to use the folder.")
|
21
|
+
metadata: Optional[dict] = Field(None, description="Metadata associated with the folder.")
|
22
|
+
|
23
|
+
|
24
|
+
class Folder(BaseFolder):
|
25
|
+
"""
|
26
|
+
Representation of a folder, which is a collection of files and passages.
|
27
|
+
|
28
|
+
Parameters:
|
29
|
+
id (str): The ID of the folder
|
30
|
+
name (str): The name of the folder.
|
31
|
+
embedding_config (EmbeddingConfig): The embedding configuration used by the folder.
|
32
|
+
user_id (str): The ID of the user that created the folder.
|
33
|
+
metadata (dict): Metadata associated with the folder.
|
34
|
+
description (str): The description of the folder.
|
35
|
+
"""
|
36
|
+
|
37
|
+
id: str = BaseFolder.generate_id_field()
|
38
|
+
embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the folder.")
|
39
|
+
organization_id: Optional[str] = Field(None, description="The ID of the organization that created the folder.")
|
40
|
+
metadata: Optional[dict] = Field(None, validation_alias="metadata_", description="Metadata associated with the folder.")
|
41
|
+
|
42
|
+
# metadata fields
|
43
|
+
created_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
44
|
+
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
45
|
+
created_at: Optional[datetime] = Field(None, description="The timestamp when the folder was created.")
|
46
|
+
updated_at: Optional[datetime] = Field(None, description="The timestamp when the folder was last updated.")
|
47
|
+
|
48
|
+
|
49
|
+
class FolderCreate(BaseFolder):
|
50
|
+
"""
|
51
|
+
Schema for creating a new Folder.
|
52
|
+
"""
|
53
|
+
|
54
|
+
# TODO: @matt, make this required after shub makes the FE changes
|
55
|
+
embedding: Optional[str] = Field(None, description="The handle for the embedding config used by the folder.")
|
56
|
+
embedding_chunk_size: Optional[int] = Field(None, description="The chunk size of the embedding.")
|
57
|
+
|
58
|
+
# TODO: remove (legacy config)
|
59
|
+
embedding_config: Optional[EmbeddingConfig] = Field(None, description="(Legacy) The embedding configuration used by the folder.")
|
60
|
+
|
61
|
+
|
62
|
+
class FolderUpdate(BaseFolder):
|
63
|
+
"""
|
64
|
+
Schema for updating an existing Folder.
|
65
|
+
"""
|
66
|
+
|
67
|
+
# Override base fields to make them optional for updates
|
68
|
+
name: Optional[str] = Field(None, description="The name of the folder.")
|
69
|
+
description: Optional[str] = Field(None, description="The description of the folder.")
|
70
|
+
instructions: Optional[str] = Field(None, description="Instructions for how to use the folder.")
|
71
|
+
metadata: Optional[dict] = Field(None, description="Metadata associated with the folder.")
|
72
|
+
|
73
|
+
# Additional update-specific fields
|
74
|
+
embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the folder.")
|
letta/schemas/memory.py
CHANGED
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|
11
11
|
from openai.types.beta.function_tool import FunctionTool as OpenAITool
|
12
12
|
|
13
13
|
from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT
|
14
|
-
from letta.schemas.block import Block
|
14
|
+
from letta.schemas.block import Block, FileBlock
|
15
15
|
from letta.schemas.message import Message
|
16
16
|
|
17
17
|
|
@@ -66,8 +66,8 @@ class Memory(BaseModel, validate_assignment=True):
|
|
66
66
|
|
67
67
|
# Memory.block contains the list of memory blocks in the core memory
|
68
68
|
blocks: List[Block] = Field(..., description="Memory blocks contained in the agent's in-context memory")
|
69
|
-
file_blocks: List[
|
70
|
-
default_factory=list, description="
|
69
|
+
file_blocks: List[FileBlock] = Field(
|
70
|
+
default_factory=list, description="Special blocks representing the agent's in-context memory of an attached file"
|
71
71
|
)
|
72
72
|
|
73
73
|
@field_validator("file_blocks")
|
@@ -124,7 +124,7 @@ class Memory(BaseModel, validate_assignment=True):
|
|
124
124
|
Template(prompt_template)
|
125
125
|
|
126
126
|
# Validate compatibility with current memory structure
|
127
|
-
Template(prompt_template).render(blocks=self.blocks, file_blocks=self.file_blocks, sources=[])
|
127
|
+
Template(prompt_template).render(blocks=self.blocks, file_blocks=self.file_blocks, sources=[], max_files_open=None)
|
128
128
|
|
129
129
|
# If we get here, the template is valid and compatible
|
130
130
|
self.prompt_template = prompt_template
|
@@ -133,11 +133,17 @@ class Memory(BaseModel, validate_assignment=True):
|
|
133
133
|
except Exception as e:
|
134
134
|
raise ValueError(f"Prompt template is not compatible with current memory structure: {str(e)}")
|
135
135
|
|
136
|
-
def compile(self, tool_usage_rules=None, sources=None) -> str:
|
136
|
+
def compile(self, tool_usage_rules=None, sources=None, max_files_open=None) -> str:
|
137
137
|
"""Generate a string representation of the memory in-context using the Jinja2 template"""
|
138
138
|
try:
|
139
139
|
template = Template(self.prompt_template)
|
140
|
-
return template.render(
|
140
|
+
return template.render(
|
141
|
+
blocks=self.blocks,
|
142
|
+
file_blocks=self.file_blocks,
|
143
|
+
tool_usage_rules=tool_usage_rules,
|
144
|
+
sources=sources,
|
145
|
+
max_files_open=max_files_open,
|
146
|
+
)
|
141
147
|
except TemplateSyntaxError as e:
|
142
148
|
raise ValueError(f"Invalid Jinja2 template syntax: {str(e)}")
|
143
149
|
except Exception as e:
|
letta/schemas/prompt.py
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
|
3
|
+
from letta.schemas.letta_base import OrmMetadataBase
|
4
|
+
|
5
|
+
|
6
|
+
class Prompt(OrmMetadataBase):
|
7
|
+
id: str = Field(..., description="The id of the agent. Assigned by the database.")
|
8
|
+
project_id: str | None = Field(None, description="The associated project id.")
|
9
|
+
prompt: str = Field(..., description="The string contents of the prompt.")
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Provider base classes and utilities
|
2
|
+
# Provider implementations
|
3
|
+
from .anthropic import AnthropicProvider
|
4
|
+
from .azure import AzureProvider
|
5
|
+
from .base import Provider, ProviderBase, ProviderCheck, ProviderCreate, ProviderUpdate
|
6
|
+
from .bedrock import BedrockProvider
|
7
|
+
from .cerebras import CerebrasProvider
|
8
|
+
from .cohere import CohereProvider
|
9
|
+
from .deepseek import DeepSeekProvider
|
10
|
+
from .google_gemini import GoogleAIProvider
|
11
|
+
from .google_vertex import GoogleVertexProvider
|
12
|
+
from .groq import GroqProvider
|
13
|
+
from .letta import LettaProvider
|
14
|
+
from .lmstudio import LMStudioOpenAIProvider
|
15
|
+
from .mistral import MistralProvider
|
16
|
+
from .ollama import OllamaProvider
|
17
|
+
from .openai import OpenAIProvider
|
18
|
+
from .together import TogetherProvider
|
19
|
+
from .vllm import VLLMProvider
|
20
|
+
from .xai import XAIProvider
|
21
|
+
|
22
|
+
__all__ = [
|
23
|
+
# Base classes
|
24
|
+
"Provider",
|
25
|
+
"ProviderBase",
|
26
|
+
"ProviderCreate",
|
27
|
+
"ProviderUpdate",
|
28
|
+
"ProviderCheck",
|
29
|
+
# Provider implementations
|
30
|
+
"AnthropicProvider",
|
31
|
+
"AzureProvider",
|
32
|
+
"BedrockProvider",
|
33
|
+
"CerebrasProvider", # NEW
|
34
|
+
"CohereProvider",
|
35
|
+
"DeepSeekProvider",
|
36
|
+
"GoogleAIProvider",
|
37
|
+
"GoogleVertexProvider",
|
38
|
+
"GroqProvider",
|
39
|
+
"LettaProvider",
|
40
|
+
"LMStudioOpenAIProvider",
|
41
|
+
"MistralProvider",
|
42
|
+
"OllamaProvider",
|
43
|
+
"OpenAIProvider",
|
44
|
+
"TogetherProvider",
|
45
|
+
"VLLMProvider", # Replaces ChatCompletions and Completions
|
46
|
+
"XAIProvider",
|
47
|
+
]
|