letta-nightly 0.8.0.dev20250606104326__py3-none-any.whl → 0.8.2.dev20250606215616__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/__init__.py +1 -1
- letta/agent.py +1 -1
- letta/agents/letta_agent.py +49 -29
- letta/agents/letta_agent_batch.py +1 -2
- letta/agents/voice_agent.py +19 -13
- letta/agents/voice_sleeptime_agent.py +11 -3
- letta/constants.py +18 -0
- letta/data_sources/__init__.py +0 -0
- letta/data_sources/redis_client.py +282 -0
- letta/errors.py +0 -4
- letta/functions/function_sets/files.py +58 -0
- letta/functions/schema_generator.py +18 -1
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/helpers/datetime_helpers.py +47 -3
- letta/helpers/decorators.py +69 -0
- letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
- letta/interfaces/anthropic_streaming_interface.py +43 -24
- letta/interfaces/openai_streaming_interface.py +21 -19
- letta/llm_api/anthropic.py +1 -1
- letta/llm_api/anthropic_client.py +22 -14
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/helpers.py +36 -30
- letta/llm_api/llm_api_tools.py +1 -1
- letta/llm_api/llm_client_base.py +29 -1
- letta/llm_api/openai.py +1 -1
- letta/llm_api/openai_client.py +6 -8
- letta/local_llm/chat_completion_proxy.py +1 -1
- letta/memory.py +1 -1
- letta/orm/enums.py +1 -0
- letta/orm/file.py +80 -3
- letta/orm/files_agents.py +13 -0
- letta/orm/sqlalchemy_base.py +34 -11
- letta/otel/__init__.py +0 -0
- letta/otel/context.py +25 -0
- letta/otel/events.py +0 -0
- letta/otel/metric_registry.py +122 -0
- letta/otel/metrics.py +66 -0
- letta/otel/resource.py +26 -0
- letta/{tracing.py → otel/tracing.py} +55 -78
- letta/plugins/README.md +22 -0
- letta/plugins/__init__.py +0 -0
- letta/plugins/defaults.py +11 -0
- letta/plugins/plugins.py +72 -0
- letta/schemas/enums.py +8 -0
- letta/schemas/file.py +12 -0
- letta/schemas/tool.py +4 -0
- letta/server/db.py +7 -7
- letta/server/rest_api/app.py +8 -6
- letta/server/rest_api/routers/v1/agents.py +37 -36
- letta/server/rest_api/routers/v1/groups.py +3 -3
- letta/server/rest_api/routers/v1/sources.py +26 -3
- letta/server/rest_api/utils.py +9 -6
- letta/server/server.py +18 -12
- letta/services/agent_manager.py +185 -193
- letta/services/block_manager.py +1 -1
- letta/services/context_window_calculator/token_counter.py +3 -2
- letta/services/file_processor/chunker/line_chunker.py +34 -0
- letta/services/file_processor/file_processor.py +40 -11
- letta/services/file_processor/parser/mistral_parser.py +11 -1
- letta/services/files_agents_manager.py +96 -7
- letta/services/group_manager.py +6 -6
- letta/services/helpers/agent_manager_helper.py +373 -3
- letta/services/identity_manager.py +1 -1
- letta/services/job_manager.py +1 -1
- letta/services/llm_batch_manager.py +1 -1
- letta/services/message_manager.py +1 -1
- letta/services/organization_manager.py +1 -1
- letta/services/passage_manager.py +1 -1
- letta/services/per_agent_lock_manager.py +1 -1
- letta/services/provider_manager.py +1 -1
- letta/services/sandbox_config_manager.py +1 -1
- letta/services/source_manager.py +178 -19
- letta/services/step_manager.py +2 -2
- letta/services/summarizer/summarizer.py +1 -1
- letta/services/telemetry_manager.py +1 -1
- letta/services/tool_executor/builtin_tool_executor.py +117 -0
- letta/services/tool_executor/composio_tool_executor.py +53 -0
- letta/services/tool_executor/core_tool_executor.py +474 -0
- letta/services/tool_executor/files_tool_executor.py +131 -0
- letta/services/tool_executor/mcp_tool_executor.py +45 -0
- letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
- letta/services/tool_executor/tool_execution_manager.py +34 -14
- letta/services/tool_executor/tool_execution_sandbox.py +1 -1
- letta/services/tool_executor/tool_executor.py +3 -802
- letta/services/tool_executor/tool_executor_base.py +43 -0
- letta/services/tool_manager.py +55 -59
- letta/services/tool_sandbox/e2b_sandbox.py +1 -1
- letta/services/tool_sandbox/local_sandbox.py +6 -3
- letta/services/user_manager.py +6 -3
- letta/settings.py +21 -1
- letta/utils.py +7 -2
- {letta_nightly-0.8.0.dev20250606104326.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/METADATA +4 -2
- {letta_nightly-0.8.0.dev20250606104326.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/RECORD +96 -74
- {letta_nightly-0.8.0.dev20250606104326.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/LICENSE +0 -0
- {letta_nightly-0.8.0.dev20250606104326.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/WHEEL +0 -0
- {letta_nightly-0.8.0.dev20250606104326.dist-info → letta_nightly-0.8.2.dev20250606215616.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,5 @@
|
|
1
1
|
import inspect
|
2
2
|
import re
|
3
|
-
import sys
|
4
3
|
import time
|
5
4
|
from functools import wraps
|
6
5
|
from typing import Any, Dict, List, Optional
|
@@ -11,15 +10,18 @@ from fastapi.responses import JSONResponse
|
|
11
10
|
from opentelemetry import trace
|
12
11
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
13
12
|
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
14
|
-
from opentelemetry.sdk.resources import Resource
|
15
13
|
from opentelemetry.sdk.trace import TracerProvider
|
16
14
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
17
15
|
from opentelemetry.trace import Status, StatusCode
|
18
16
|
|
19
|
-
from letta import
|
17
|
+
from letta.log import get_logger
|
18
|
+
from letta.otel.resource import get_resource, is_pytest_environment
|
19
|
+
from letta.settings import settings
|
20
20
|
|
21
|
+
logger = get_logger(__name__) # TODO: set up logger config for this
|
21
22
|
tracer = trace.get_tracer(__name__)
|
22
23
|
_is_tracing_initialized = False
|
24
|
+
|
23
25
|
_excluded_v1_endpoints_regex: List[str] = [
|
24
26
|
# "^GET /v1/agents/(?P<agent_id>[^/]+)/messages$",
|
25
27
|
# "^GET /v1/agents/(?P<agent_id>[^/]+)/context$",
|
@@ -30,11 +32,7 @@ _excluded_v1_endpoints_regex: List[str] = [
|
|
30
32
|
]
|
31
33
|
|
32
34
|
|
33
|
-
def
|
34
|
-
return "pytest" in sys.modules
|
35
|
-
|
36
|
-
|
37
|
-
async def trace_request_middleware(request: Request, call_next):
|
35
|
+
async def _trace_request_middleware(request: Request, call_next):
|
38
36
|
if not _is_tracing_initialized:
|
39
37
|
return await call_next(request)
|
40
38
|
initial_span_name = f"{request.method} {request.url.path}"
|
@@ -56,7 +54,7 @@ async def trace_request_middleware(request: Request, call_next):
|
|
56
54
|
raise
|
57
55
|
|
58
56
|
|
59
|
-
async def
|
57
|
+
async def _update_trace_attributes(request: Request):
|
60
58
|
"""Dependency to update trace attributes after FastAPI has processed the request"""
|
61
59
|
if not _is_tracing_initialized:
|
62
60
|
return
|
@@ -78,35 +76,19 @@ async def update_trace_attributes(request: Request):
|
|
78
76
|
for key, value in request.path_params.items():
|
79
77
|
span.set_attribute(f"http.{key}", value)
|
80
78
|
|
81
|
-
# Add
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
span.set_attribute("project.id", project_id)
|
95
|
-
|
96
|
-
# Add agent_id if available
|
97
|
-
agent_id = request.headers.get("x-agent-id")
|
98
|
-
if agent_id:
|
99
|
-
span.set_attribute("agent.id", agent_id)
|
100
|
-
|
101
|
-
# Add template_id if available
|
102
|
-
template_id = request.headers.get("x-template-id")
|
103
|
-
if template_id:
|
104
|
-
span.set_attribute("template.id", template_id)
|
105
|
-
|
106
|
-
# Add base_template_id if available
|
107
|
-
base_template_id = request.headers.get("x-base-template-id")
|
108
|
-
if base_template_id:
|
109
|
-
span.set_attribute("base_template.id", base_template_id)
|
79
|
+
# Add the following headers to span if available
|
80
|
+
header_attributes = {
|
81
|
+
"user_id": "user.id",
|
82
|
+
"x-organization-id": "organization.id",
|
83
|
+
"x-project-id": "project.id",
|
84
|
+
"x-agent-id": "agent.id",
|
85
|
+
"x-template-id": "template.id",
|
86
|
+
"x-base-template-id": "base_template.id",
|
87
|
+
}
|
88
|
+
for header_key, span_key in header_attributes.items():
|
89
|
+
header_value = request.headers.get(header_key)
|
90
|
+
if header_value:
|
91
|
+
span.set_attribute(span_key, header_value)
|
110
92
|
|
111
93
|
# Add request body if available
|
112
94
|
try:
|
@@ -117,7 +99,7 @@ async def update_trace_attributes(request: Request):
|
|
117
99
|
pass
|
118
100
|
|
119
101
|
|
120
|
-
async def
|
102
|
+
async def _trace_error_handler(_request: Request, exc: Exception) -> JSONResponse:
|
121
103
|
status_code = getattr(exc, "status_code", 500)
|
122
104
|
error_msg = str(exc)
|
123
105
|
|
@@ -142,49 +124,44 @@ def setup_tracing(
|
|
142
124
|
) -> None:
|
143
125
|
if is_pytest_environment():
|
144
126
|
return
|
127
|
+
assert endpoint
|
145
128
|
|
146
129
|
global _is_tracing_initialized
|
147
130
|
|
148
|
-
|
149
|
-
|
131
|
+
tracer_provider = TracerProvider(resource=get_resource(service_name))
|
132
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint)))
|
133
|
+
_is_tracing_initialized = True
|
134
|
+
trace.set_tracer_provider(tracer_provider)
|
150
135
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
full_path = ((next(iter(route.methods)) + " ") if route.methods else "") + "/v1" + route.path
|
181
|
-
if not any(re.match(regex, full_path) for regex in _excluded_v1_endpoints_regex):
|
182
|
-
route.dependencies.append(Depends(update_trace_attributes))
|
183
|
-
|
184
|
-
# Register exception handlers
|
185
|
-
app.exception_handler(HTTPException)(trace_error_handler)
|
186
|
-
app.exception_handler(RequestValidationError)(trace_error_handler)
|
187
|
-
app.exception_handler(Exception)(trace_error_handler)
|
136
|
+
# Instrumentors (e.g., RequestsInstrumentor)
|
137
|
+
def requests_callback(span: trace.Span, _: Any, response: Any) -> None:
|
138
|
+
if hasattr(response, "status_code"):
|
139
|
+
span.set_status(Status(StatusCode.OK if response.status_code < 400 else StatusCode.ERROR))
|
140
|
+
|
141
|
+
RequestsInstrumentor().instrument(response_hook=requests_callback)
|
142
|
+
|
143
|
+
if settings.sqlalchemy_tracing:
|
144
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
145
|
+
|
146
|
+
SQLAlchemyInstrumentor().instrument()
|
147
|
+
|
148
|
+
if app:
|
149
|
+
# Add middleware first
|
150
|
+
app.middleware("http")(_trace_request_middleware)
|
151
|
+
|
152
|
+
# Add dependency to v1 routes
|
153
|
+
from letta.server.rest_api.routers.v1 import ROUTERS as V1_ROUTES
|
154
|
+
|
155
|
+
for router in V1_ROUTES:
|
156
|
+
for route in router.routes:
|
157
|
+
full_path = ((next(iter(route.methods)) + " ") if route.methods else "") + "/v1" + route.path
|
158
|
+
if not any(re.match(regex, full_path) for regex in _excluded_v1_endpoints_regex):
|
159
|
+
route.dependencies.append(Depends(_update_trace_attributes))
|
160
|
+
|
161
|
+
# Register exception handlers for tracing
|
162
|
+
app.exception_handler(HTTPException)(_trace_error_handler)
|
163
|
+
app.exception_handler(RequestValidationError)(_trace_error_handler)
|
164
|
+
app.exception_handler(Exception)(_trace_error_handler)
|
188
165
|
|
189
166
|
|
190
167
|
def trace_method(func):
|
letta/plugins/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
### Plugins
|
2
|
+
|
3
|
+
Plugins enable plug and play for various components.
|
4
|
+
|
5
|
+
Plugin configurations can be set in `letta.settings.settings`.
|
6
|
+
|
7
|
+
The plugins will take a delimited list of consisting of individual plugin configs:
|
8
|
+
|
9
|
+
`<plugin_name>.<config_name>=<class_or_function>`
|
10
|
+
|
11
|
+
joined by `;`
|
12
|
+
|
13
|
+
In the default configuration, the top level keys have values `plugin_name`,
|
14
|
+
the `config_name` is nested under and the `class_or_function` is defined
|
15
|
+
after in format `<module_path>:<name>`.
|
16
|
+
|
17
|
+
```
|
18
|
+
DEFAULT_PLUGINS = {
|
19
|
+
"experimental_check": {
|
20
|
+
"default": "letta.plugins.defaults:is_experimental_enabled",
|
21
|
+
...
|
22
|
+
```
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from letta.settings import settings
|
2
|
+
|
3
|
+
|
4
|
+
def is_experimental_enabled(feature_name: str, **kwargs) -> bool:
|
5
|
+
if feature_name in ("async_agent_loop", "summarize"):
|
6
|
+
if not (kwargs.get("eligibility", False) and settings.use_experimental):
|
7
|
+
return False
|
8
|
+
return True
|
9
|
+
|
10
|
+
# Err on safety here, disabling experimental if not handled here.
|
11
|
+
return False
|
letta/plugins/plugins.py
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
import importlib
|
2
|
+
from typing import Protocol, runtime_checkable
|
3
|
+
|
4
|
+
from letta.settings import settings
|
5
|
+
|
6
|
+
|
7
|
+
@runtime_checkable
|
8
|
+
class SummarizerProtocol(Protocol):
|
9
|
+
"""What a summarizer must implement"""
|
10
|
+
|
11
|
+
async def summarize(self, text: str) -> str: ...
|
12
|
+
def get_name(self) -> str: ...
|
13
|
+
|
14
|
+
|
15
|
+
# Currently this supports one of each plugin type. This can be expanded in the future.
|
16
|
+
DEFAULT_PLUGINS = {
|
17
|
+
"experimental_check": {
|
18
|
+
"protocol": None,
|
19
|
+
"target": "letta.plugins.defaults:is_experimental_enabled",
|
20
|
+
},
|
21
|
+
"summarizer": {
|
22
|
+
"protocol": SummarizerProtocol,
|
23
|
+
"target": "letta.services.summarizer.summarizer:Summarizer",
|
24
|
+
},
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
def get_plugin(plugin_type: str):
|
29
|
+
"""Get a plugin instance"""
|
30
|
+
plugin_register = dict(DEFAULT_PLUGINS, **settings.plugin_register_dict)
|
31
|
+
if plugin_type in plugin_register:
|
32
|
+
impl_path = plugin_register[plugin_type]["target"]
|
33
|
+
module_path, name = impl_path.split(":")
|
34
|
+
module = importlib.import_module(module_path)
|
35
|
+
plugin = getattr(module, name)
|
36
|
+
if type(plugin).__name__ == "function":
|
37
|
+
return plugin
|
38
|
+
elif type(plugin).__name__ == "class":
|
39
|
+
if plugin_register["protocol"] and not isinstance(plugin, type(plugin_register["protocol"])):
|
40
|
+
raise TypeError(f'{plugin} does not implement {type(plugin_register["protocol"]).__name__}')
|
41
|
+
return plugin()
|
42
|
+
raise TypeError("Unknown plugin type")
|
43
|
+
|
44
|
+
|
45
|
+
_experimental_checker = None
|
46
|
+
_summarizer = None
|
47
|
+
|
48
|
+
|
49
|
+
# TODO handle coroutines
|
50
|
+
# Convenience functions
|
51
|
+
def get_experimental_checker():
|
52
|
+
global _experimental_checker
|
53
|
+
if _experimental_checker is None:
|
54
|
+
_experimental_checker = get_plugin("experimental_check")
|
55
|
+
return _experimental_checker
|
56
|
+
|
57
|
+
|
58
|
+
def get_summarizer():
|
59
|
+
global _summarizer
|
60
|
+
if _summarizer is None:
|
61
|
+
_summarizer = get_plugin("summarizer")
|
62
|
+
return _summarizer
|
63
|
+
|
64
|
+
|
65
|
+
def reset_experimental_checker():
|
66
|
+
global _experimental_checker
|
67
|
+
_experimental_checker = None
|
68
|
+
|
69
|
+
|
70
|
+
def reset_summarizer():
|
71
|
+
global _summarizer
|
72
|
+
_summarizer = None
|
letta/schemas/enums.py
CHANGED
@@ -87,3 +87,11 @@ class ToolRuleType(str, Enum):
|
|
87
87
|
constrain_child_tools = "constrain_child_tools"
|
88
88
|
max_count_per_step = "max_count_per_step"
|
89
89
|
parent_last_tool = "parent_last_tool"
|
90
|
+
|
91
|
+
|
92
|
+
class FileProcessingStatus(str, Enum):
|
93
|
+
PENDING = "pending"
|
94
|
+
PARSING = "parsing"
|
95
|
+
EMBEDDING = "embedding"
|
96
|
+
COMPLETED = "completed"
|
97
|
+
ERROR = "error"
|
letta/schemas/file.py
CHANGED
@@ -4,6 +4,7 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
from pydantic import Field
|
6
6
|
|
7
|
+
from letta.schemas.enums import FileProcessingStatus
|
7
8
|
from letta.schemas.letta_base import LettaBase
|
8
9
|
|
9
10
|
|
@@ -34,12 +35,22 @@ class FileMetadata(FileMetadataBase):
|
|
34
35
|
file_size: Optional[int] = Field(None, description="The size of the file in bytes.")
|
35
36
|
file_creation_date: Optional[str] = Field(None, description="The creation date of the file.")
|
36
37
|
file_last_modified_date: Optional[str] = Field(None, description="The last modified date of the file.")
|
38
|
+
processing_status: FileProcessingStatus = Field(
|
39
|
+
default=FileProcessingStatus.PENDING,
|
40
|
+
description="The current processing status of the file (e.g. pending, parsing, embedding, completed, error).",
|
41
|
+
)
|
42
|
+
error_message: Optional[str] = Field(default=None, description="Optional error message if the file failed processing.")
|
37
43
|
|
38
44
|
# orm metadata, optional fields
|
39
45
|
created_at: Optional[datetime] = Field(default_factory=datetime.utcnow, description="The creation date of the file.")
|
40
46
|
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, description="The update date of the file.")
|
41
47
|
is_deleted: bool = Field(False, description="Whether this file is deleted or not.")
|
42
48
|
|
49
|
+
# This is optional, and only occasionally pulled in since it can be very large
|
50
|
+
content: Optional[str] = Field(
|
51
|
+
default=None, description="Optional full-text content of the file; only populated on demand due to its size."
|
52
|
+
)
|
53
|
+
|
43
54
|
|
44
55
|
class FileAgentBase(LettaBase):
|
45
56
|
"""Base class for the FileMetadata-⇄-Agent association schemas"""
|
@@ -67,6 +78,7 @@ class FileAgent(FileAgentBase):
|
|
67
78
|
)
|
68
79
|
agent_id: str = Field(..., description="Unique identifier of the agent.")
|
69
80
|
file_id: str = Field(..., description="Unique identifier of the file.")
|
81
|
+
file_name: str = Field(..., description="Name of the file.")
|
70
82
|
is_open: bool = Field(True, description="True if the agent currently has the file open.")
|
71
83
|
visible_content: Optional[str] = Field(
|
72
84
|
None,
|
letta/schemas/tool.py
CHANGED
@@ -7,6 +7,7 @@ from letta.constants import (
|
|
7
7
|
FUNCTION_RETURN_CHAR_LIMIT,
|
8
8
|
LETTA_BUILTIN_TOOL_MODULE_NAME,
|
9
9
|
LETTA_CORE_TOOL_MODULE_NAME,
|
10
|
+
LETTA_FILES_TOOL_MODULE_NAME,
|
10
11
|
LETTA_MULTI_AGENT_TOOL_MODULE_NAME,
|
11
12
|
LETTA_VOICE_TOOL_MODULE_NAME,
|
12
13
|
MCP_TOOL_TAG_NAME_PREFIX,
|
@@ -106,6 +107,9 @@ class Tool(BaseTool):
|
|
106
107
|
elif self.tool_type in {ToolType.LETTA_BUILTIN}:
|
107
108
|
# If it's letta voice tool, we generate the json_schema on the fly here
|
108
109
|
self.json_schema = get_json_schema_from_module(module_name=LETTA_BUILTIN_TOOL_MODULE_NAME, function_name=self.name)
|
110
|
+
elif self.tool_type in {ToolType.LETTA_FILES_CORE}:
|
111
|
+
# If it's letta files tool, we generate the json_schema on the fly here
|
112
|
+
self.json_schema = get_json_schema_from_module(module_name=LETTA_FILES_TOOL_MODULE_NAME, function_name=self.name)
|
109
113
|
elif self.tool_type in {ToolType.EXTERNAL_COMPOSIO}:
|
110
114
|
# Composio schemas handled separately
|
111
115
|
pass
|
letta/server/db.py
CHANGED
@@ -13,8 +13,8 @@ from sqlalchemy.orm import sessionmaker
|
|
13
13
|
|
14
14
|
from letta.config import LettaConfig
|
15
15
|
from letta.log import get_logger
|
16
|
+
from letta.otel.tracing import trace_method
|
16
17
|
from letta.settings import settings
|
17
|
-
from letta.tracing import trace_method
|
18
18
|
|
19
19
|
logger = get_logger(__name__)
|
20
20
|
|
@@ -131,7 +131,12 @@ class DatabaseRegistry:
|
|
131
131
|
# Create async session factory
|
132
132
|
self._async_engines["default"] = async_engine
|
133
133
|
self._async_session_factories["default"] = async_sessionmaker(
|
134
|
-
|
134
|
+
expire_on_commit=True,
|
135
|
+
close_resets_only=False,
|
136
|
+
autocommit=False,
|
137
|
+
autoflush=False,
|
138
|
+
bind=self._async_engines["default"],
|
139
|
+
class_=AsyncSession,
|
135
140
|
)
|
136
141
|
self._initialized["async"] = True
|
137
142
|
|
@@ -207,11 +212,6 @@ class DatabaseRegistry:
|
|
207
212
|
self.initialize_sync()
|
208
213
|
return self._engines.get(name)
|
209
214
|
|
210
|
-
def get_async_engine(self, name: str = "default") -> AsyncEngine:
|
211
|
-
"""Get an async database engine by name."""
|
212
|
-
self.initialize_async()
|
213
|
-
return self._async_engines.get(name)
|
214
|
-
|
215
215
|
def get_session_factory(self, name: str = "default") -> sessionmaker:
|
216
216
|
"""Get a session factory by name."""
|
217
217
|
self.initialize_sync()
|
letta/server/rest_api/app.py
CHANGED
@@ -256,13 +256,15 @@ def create_application() -> "FastAPI":
|
|
256
256
|
print(f"▶ Using OTLP tracing with endpoint: {otlp_endpoint}")
|
257
257
|
env_name_suffix = os.getenv("ENV_NAME")
|
258
258
|
service_name = f"letta-server-{env_name_suffix.lower()}" if env_name_suffix else "letta-server"
|
259
|
-
from letta.
|
259
|
+
from letta.otel.metrics import setup_metrics
|
260
|
+
from letta.otel.tracing import setup_tracing
|
260
261
|
|
261
262
|
setup_tracing(
|
262
263
|
endpoint=otlp_endpoint,
|
263
264
|
app=app,
|
264
265
|
service_name=service_name,
|
265
266
|
)
|
267
|
+
setup_metrics(endpoint=otlp_endpoint, app=app, service_name=service_name)
|
266
268
|
|
267
269
|
for route in v1_routes:
|
268
270
|
app.include_router(route, prefix=API_PREFIX)
|
@@ -331,7 +333,7 @@ def start_server(
|
|
331
333
|
if (os.getenv("LOCAL_HTTPS") == "true") or "--localhttps" in sys.argv:
|
332
334
|
print(f"▶ Server running at: https://{host or 'localhost'}:{port or REST_DEFAULT_PORT}")
|
333
335
|
print(f"▶ View using ADE at: https://app.letta.com/development-servers/local/dashboard\n")
|
334
|
-
if importlib.util.find_spec("granian") is not None and settings.
|
336
|
+
if importlib.util.find_spec("granian") is not None and settings.use_granian:
|
335
337
|
from granian import Granian
|
336
338
|
|
337
339
|
# Experimental Granian engine
|
@@ -339,14 +341,14 @@ def start_server(
|
|
339
341
|
target="letta.server.rest_api.app:app",
|
340
342
|
# factory=True,
|
341
343
|
interface="asgi",
|
342
|
-
address=host or "
|
344
|
+
address=host or "127.0.0.1", # Note granian address must be an ip address
|
343
345
|
port=port or REST_DEFAULT_PORT,
|
344
346
|
workers=settings.uvicorn_workers,
|
345
347
|
# threads=
|
346
348
|
reload=reload or settings.uvicorn_reload,
|
347
349
|
reload_ignore_patterns=["openapi_letta.json"],
|
348
350
|
reload_ignore_worker_failure=True,
|
349
|
-
reload_tick=
|
351
|
+
reload_tick=4000, # set to 4s to prevent crashing on weird state
|
350
352
|
# log_level="info"
|
351
353
|
ssl_keyfile="certs/localhost-key.pem",
|
352
354
|
ssl_cert="certs/localhost.pem",
|
@@ -380,14 +382,14 @@ def start_server(
|
|
380
382
|
target="letta.server.rest_api.app:app",
|
381
383
|
# factory=True,
|
382
384
|
interface="asgi",
|
383
|
-
address=host or "
|
385
|
+
address=host or "127.0.0.1", # Note granian address must be an ip address
|
384
386
|
port=port or REST_DEFAULT_PORT,
|
385
387
|
workers=settings.uvicorn_workers,
|
386
388
|
# threads=
|
387
389
|
reload=reload or settings.uvicorn_reload,
|
388
390
|
reload_ignore_patterns=["openapi_letta.json"],
|
389
391
|
reload_ignore_worker_failure=True,
|
390
|
-
reload_tick=
|
392
|
+
reload_tick=4000, # set to 4s to prevent crashing on weird state
|
391
393
|
# log_level="info"
|
392
394
|
).serve()
|
393
395
|
else:
|