letta-nightly 0.6.14.dev20250123104106__py3-none-any.whl → 0.6.15.dev20250124054224__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/client/client.py +144 -68
- letta/client/streaming.py +1 -1
- letta/functions/function_sets/extras.py +8 -3
- letta/functions/function_sets/multi_agent.py +1 -1
- letta/functions/helpers.py +2 -2
- letta/llm_api/llm_api_tools.py +2 -2
- letta/llm_api/openai.py +30 -138
- letta/memory.py +4 -4
- letta/offline_memory_agent.py +10 -10
- letta/orm/agent.py +10 -2
- letta/orm/block.py +14 -3
- letta/orm/job.py +2 -1
- letta/orm/message.py +12 -1
- letta/orm/passage.py +6 -2
- letta/orm/source.py +6 -1
- letta/orm/sqlalchemy_base.py +80 -32
- letta/orm/tool.py +5 -2
- letta/schemas/embedding_config_overrides.py +3 -0
- letta/schemas/enums.py +4 -0
- letta/schemas/job.py +1 -1
- letta/schemas/letta_message.py +22 -5
- letta/schemas/llm_config.py +5 -0
- letta/schemas/llm_config_overrides.py +38 -0
- letta/schemas/message.py +61 -15
- letta/schemas/openai/chat_completions.py +1 -1
- letta/schemas/passage.py +1 -1
- letta/schemas/providers.py +24 -8
- letta/schemas/source.py +1 -1
- letta/server/rest_api/app.py +12 -3
- letta/server/rest_api/interface.py +5 -7
- letta/server/rest_api/routers/v1/agents.py +7 -12
- letta/server/rest_api/routers/v1/blocks.py +19 -0
- letta/server/rest_api/routers/v1/organizations.py +2 -2
- letta/server/rest_api/routers/v1/providers.py +2 -2
- letta/server/rest_api/routers/v1/runs.py +15 -7
- letta/server/rest_api/routers/v1/sandbox_configs.py +4 -4
- letta/server/rest_api/routers/v1/sources.py +2 -2
- letta/server/rest_api/routers/v1/tags.py +2 -2
- letta/server/rest_api/routers/v1/tools.py +2 -2
- letta/server/rest_api/routers/v1/users.py +2 -2
- letta/server/server.py +62 -34
- letta/services/agent_manager.py +80 -33
- letta/services/block_manager.py +15 -2
- letta/services/helpers/agent_manager_helper.py +11 -4
- letta/services/job_manager.py +19 -9
- letta/services/message_manager.py +14 -8
- letta/services/organization_manager.py +8 -4
- letta/services/provider_manager.py +8 -4
- letta/services/sandbox_config_manager.py +16 -8
- letta/services/source_manager.py +4 -4
- letta/services/tool_manager.py +3 -3
- letta/services/user_manager.py +9 -5
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/METADATA +2 -1
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/RECORD +58 -57
- letta/orm/job_usage_statistics.py +0 -30
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.14.dev20250123104106.dist-info → letta_nightly-0.6.15.dev20250124054224.dist-info}/entry_points.txt +0 -0
letta/llm_api/llm_api_tools.py
CHANGED
|
@@ -237,6 +237,7 @@ def create(
|
|
|
237
237
|
data=dict(
|
|
238
238
|
contents=[m.to_google_ai_dict() for m in messages],
|
|
239
239
|
tools=tools,
|
|
240
|
+
generation_config={"temperature": llm_config.temperature},
|
|
240
241
|
),
|
|
241
242
|
inner_thoughts_in_kwargs=llm_config.put_inner_thoughts_in_kwargs,
|
|
242
243
|
)
|
|
@@ -261,6 +262,7 @@ def create(
|
|
|
261
262
|
# user=str(user_id),
|
|
262
263
|
# NOTE: max_tokens is required for Anthropic API
|
|
263
264
|
max_tokens=1024, # TODO make dynamic
|
|
265
|
+
temperature=llm_config.temperature,
|
|
264
266
|
),
|
|
265
267
|
)
|
|
266
268
|
|
|
@@ -290,7 +292,6 @@ def create(
|
|
|
290
292
|
# # max_tokens=1024, # TODO make dynamic
|
|
291
293
|
# ),
|
|
292
294
|
# )
|
|
293
|
-
|
|
294
295
|
elif llm_config.model_endpoint_type == "groq":
|
|
295
296
|
if stream:
|
|
296
297
|
raise NotImplementedError(f"Streaming not yet implemented for Groq.")
|
|
@@ -329,7 +330,6 @@ def create(
|
|
|
329
330
|
try:
|
|
330
331
|
# groq uses the openai chat completions API, so this component should be reusable
|
|
331
332
|
response = openai_chat_completions_request(
|
|
332
|
-
url=llm_config.model_endpoint,
|
|
333
333
|
api_key=model_settings.groq_api_key,
|
|
334
334
|
chat_completion_request=data,
|
|
335
335
|
)
|
letta/llm_api/openai.py
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import warnings
|
|
3
2
|
from typing import Generator, List, Optional, Union
|
|
4
3
|
|
|
5
|
-
import httpx
|
|
6
4
|
import requests
|
|
7
|
-
from
|
|
8
|
-
from httpx_sse._exceptions import SSEError
|
|
5
|
+
from openai import OpenAI
|
|
9
6
|
|
|
10
|
-
from letta.constants import OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING
|
|
11
|
-
from letta.errors import LLMError
|
|
12
7
|
from letta.llm_api.helpers import add_inner_thoughts_to_functions, convert_to_structured_output, make_post_request
|
|
13
8
|
from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION
|
|
14
9
|
from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
|
|
@@ -130,7 +125,8 @@ def build_openai_chat_completions_request(
|
|
|
130
125
|
tools=[Tool(type="function", function=f) for f in functions] if functions else None,
|
|
131
126
|
tool_choice=tool_choice,
|
|
132
127
|
user=str(user_id),
|
|
133
|
-
|
|
128
|
+
max_completion_tokens=max_tokens,
|
|
129
|
+
temperature=llm_config.temperature,
|
|
134
130
|
)
|
|
135
131
|
else:
|
|
136
132
|
data = ChatCompletionRequest(
|
|
@@ -139,7 +135,8 @@ def build_openai_chat_completions_request(
|
|
|
139
135
|
functions=functions,
|
|
140
136
|
function_call=function_call,
|
|
141
137
|
user=str(user_id),
|
|
142
|
-
|
|
138
|
+
max_completion_tokens=max_tokens,
|
|
139
|
+
temperature=llm_config.temperature,
|
|
143
140
|
)
|
|
144
141
|
# https://platform.openai.com/docs/guides/text-generation/json-mode
|
|
145
142
|
# only supported by gpt-4o, gpt-4-turbo, or gpt-3.5-turbo
|
|
@@ -378,126 +375,21 @@ def openai_chat_completions_process_stream(
|
|
|
378
375
|
return chat_completion_response
|
|
379
376
|
|
|
380
377
|
|
|
381
|
-
def _sse_post(url: str, data: dict, headers: dict) -> Generator[ChatCompletionChunkResponse, None, None]:
|
|
382
|
-
|
|
383
|
-
with httpx.Client() as client:
|
|
384
|
-
with connect_sse(client, method="POST", url=url, json=data, headers=headers) as event_source:
|
|
385
|
-
|
|
386
|
-
# Inspect for errors before iterating (see https://github.com/florimondmanca/httpx-sse/pull/12)
|
|
387
|
-
if not event_source.response.is_success:
|
|
388
|
-
# handle errors
|
|
389
|
-
from letta.utils import printd
|
|
390
|
-
|
|
391
|
-
printd("Caught error before iterating SSE request:", vars(event_source.response))
|
|
392
|
-
printd(event_source.response.read())
|
|
393
|
-
|
|
394
|
-
try:
|
|
395
|
-
response_bytes = event_source.response.read()
|
|
396
|
-
response_dict = json.loads(response_bytes.decode("utf-8"))
|
|
397
|
-
error_message = response_dict["error"]["message"]
|
|
398
|
-
# e.g.: This model's maximum context length is 8192 tokens. However, your messages resulted in 8198 tokens (7450 in the messages, 748 in the functions). Please reduce the length of the messages or functions.
|
|
399
|
-
if OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING in error_message:
|
|
400
|
-
raise LLMError(error_message)
|
|
401
|
-
except LLMError:
|
|
402
|
-
raise
|
|
403
|
-
except:
|
|
404
|
-
print(f"Failed to parse SSE message, throwing SSE HTTP error up the stack")
|
|
405
|
-
event_source.response.raise_for_status()
|
|
406
|
-
|
|
407
|
-
try:
|
|
408
|
-
for sse in event_source.iter_sse():
|
|
409
|
-
# printd(sse.event, sse.data, sse.id, sse.retry)
|
|
410
|
-
if sse.data == OPENAI_SSE_DONE:
|
|
411
|
-
# print("finished")
|
|
412
|
-
break
|
|
413
|
-
else:
|
|
414
|
-
chunk_data = json.loads(sse.data)
|
|
415
|
-
# print("chunk_data::", chunk_data)
|
|
416
|
-
chunk_object = ChatCompletionChunkResponse(**chunk_data)
|
|
417
|
-
# print("chunk_object::", chunk_object)
|
|
418
|
-
# id=chunk_data["id"],
|
|
419
|
-
# choices=[ChunkChoice],
|
|
420
|
-
# model=chunk_data["model"],
|
|
421
|
-
# system_fingerprint=chunk_data["system_fingerprint"]
|
|
422
|
-
# )
|
|
423
|
-
yield chunk_object
|
|
424
|
-
|
|
425
|
-
except SSEError as e:
|
|
426
|
-
print("Caught an error while iterating the SSE stream:", str(e))
|
|
427
|
-
if "application/json" in str(e): # Check if the error is because of JSON response
|
|
428
|
-
# TODO figure out a better way to catch the error other than re-trying with a POST
|
|
429
|
-
response = client.post(url=url, json=data, headers=headers) # Make the request again to get the JSON response
|
|
430
|
-
if response.headers["Content-Type"].startswith("application/json"):
|
|
431
|
-
error_details = response.json() # Parse the JSON to get the error message
|
|
432
|
-
print("Request:", vars(response.request))
|
|
433
|
-
print("POST Error:", error_details)
|
|
434
|
-
print("Original SSE Error:", str(e))
|
|
435
|
-
else:
|
|
436
|
-
print("Failed to retrieve JSON error message via retry.")
|
|
437
|
-
else:
|
|
438
|
-
print("SSEError not related to 'application/json' content type.")
|
|
439
|
-
|
|
440
|
-
# Optionally re-raise the exception if you need to propagate it
|
|
441
|
-
raise e
|
|
442
|
-
|
|
443
|
-
except Exception as e:
|
|
444
|
-
if event_source.response.request is not None:
|
|
445
|
-
print("HTTP Request:", vars(event_source.response.request))
|
|
446
|
-
if event_source.response is not None:
|
|
447
|
-
print("HTTP Status:", event_source.response.status_code)
|
|
448
|
-
print("HTTP Headers:", event_source.response.headers)
|
|
449
|
-
# print("HTTP Body:", event_source.response.text)
|
|
450
|
-
print("Exception message:", str(e))
|
|
451
|
-
raise e
|
|
452
|
-
|
|
453
|
-
|
|
454
378
|
def openai_chat_completions_request_stream(
|
|
455
379
|
url: str,
|
|
456
380
|
api_key: str,
|
|
457
381
|
chat_completion_request: ChatCompletionRequest,
|
|
458
382
|
) -> Generator[ChatCompletionChunkResponse, None, None]:
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
data.pop("functions")
|
|
470
|
-
data.pop("function_call", None) # extra safe, should exist always (default="auto")
|
|
471
|
-
|
|
472
|
-
if "tools" in data and data["tools"] is None:
|
|
473
|
-
data.pop("tools")
|
|
474
|
-
data.pop("tool_choice", None) # extra safe, should exist always (default="auto")
|
|
475
|
-
|
|
476
|
-
if "tools" in data:
|
|
477
|
-
for tool in data["tools"]:
|
|
478
|
-
# tool["strict"] = True
|
|
479
|
-
try:
|
|
480
|
-
tool["function"] = convert_to_structured_output(tool["function"])
|
|
481
|
-
except ValueError as e:
|
|
482
|
-
warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
|
|
483
|
-
|
|
484
|
-
# print(f"\n\n\n\nData[tools]: {json.dumps(data['tools'], indent=2)}")
|
|
485
|
-
|
|
486
|
-
printd(f"Sending request to {url}")
|
|
487
|
-
try:
|
|
488
|
-
return _sse_post(url=url, data=data, headers=headers)
|
|
489
|
-
except requests.exceptions.HTTPError as http_err:
|
|
490
|
-
# Handle HTTP errors (e.g., response 4XX, 5XX)
|
|
491
|
-
printd(f"Got HTTPError, exception={http_err}, payload={data}")
|
|
492
|
-
raise http_err
|
|
493
|
-
except requests.exceptions.RequestException as req_err:
|
|
494
|
-
# Handle other requests-related errors (e.g., connection error)
|
|
495
|
-
printd(f"Got RequestException, exception={req_err}")
|
|
496
|
-
raise req_err
|
|
497
|
-
except Exception as e:
|
|
498
|
-
# Handle other potential errors
|
|
499
|
-
printd(f"Got unknown Exception, exception={e}")
|
|
500
|
-
raise e
|
|
383
|
+
data = prepare_openai_payload(chat_completion_request)
|
|
384
|
+
data["stream"] = True
|
|
385
|
+
client = OpenAI(
|
|
386
|
+
api_key=api_key,
|
|
387
|
+
base_url=url,
|
|
388
|
+
)
|
|
389
|
+
stream = client.chat.completions.create(**data)
|
|
390
|
+
for chunk in stream:
|
|
391
|
+
# TODO: Use the native OpenAI objects here?
|
|
392
|
+
yield ChatCompletionChunkResponse(**chunk.model_dump(exclude_none=True))
|
|
501
393
|
|
|
502
394
|
|
|
503
395
|
def openai_chat_completions_request(
|
|
@@ -512,18 +404,28 @@ def openai_chat_completions_request(
|
|
|
512
404
|
|
|
513
405
|
https://platform.openai.com/docs/guides/text-generation?lang=curl
|
|
514
406
|
"""
|
|
515
|
-
|
|
407
|
+
data = prepare_openai_payload(chat_completion_request)
|
|
408
|
+
client = OpenAI(api_key=api_key, base_url=url)
|
|
409
|
+
chat_completion = client.chat.completions.create(**data)
|
|
410
|
+
return ChatCompletionResponse(**chat_completion.model_dump())
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def openai_embeddings_request(url: str, api_key: str, data: dict) -> EmbeddingResponse:
|
|
414
|
+
"""https://platform.openai.com/docs/api-reference/embeddings/create"""
|
|
516
415
|
|
|
517
|
-
url = smart_urljoin(url, "
|
|
416
|
+
url = smart_urljoin(url, "embeddings")
|
|
518
417
|
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
418
|
+
response_json = make_post_request(url, headers, data)
|
|
419
|
+
return EmbeddingResponse(**response_json)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def prepare_openai_payload(chat_completion_request: ChatCompletionRequest):
|
|
519
423
|
data = chat_completion_request.model_dump(exclude_none=True)
|
|
520
424
|
|
|
521
425
|
# add check otherwise will cause error: "Invalid value for 'parallel_tool_calls': 'parallel_tool_calls' is only allowed when 'tools' are specified."
|
|
522
426
|
if chat_completion_request.tools is not None:
|
|
523
427
|
data["parallel_tool_calls"] = False
|
|
524
428
|
|
|
525
|
-
printd("Request:\n", json.dumps(data, indent=2))
|
|
526
|
-
|
|
527
429
|
# If functions == None, strip from the payload
|
|
528
430
|
if "functions" in data and data["functions"] is None:
|
|
529
431
|
data.pop("functions")
|
|
@@ -540,14 +442,4 @@ def openai_chat_completions_request(
|
|
|
540
442
|
except ValueError as e:
|
|
541
443
|
warnings.warn(f"Failed to convert tool function to structured output, tool={tool}, error={e}")
|
|
542
444
|
|
|
543
|
-
|
|
544
|
-
return ChatCompletionResponse(**response_json)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
def openai_embeddings_request(url: str, api_key: str, data: dict) -> EmbeddingResponse:
|
|
548
|
-
"""https://platform.openai.com/docs/api-reference/embeddings/create"""
|
|
549
|
-
|
|
550
|
-
url = smart_urljoin(url, "embeddings")
|
|
551
|
-
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
|
|
552
|
-
response_json = make_post_request(url, headers, data)
|
|
553
|
-
return EmbeddingResponse(**response_json)
|
|
445
|
+
return data
|
letta/memory.py
CHANGED
|
@@ -6,7 +6,7 @@ from letta.prompts.gpt_summarize import SYSTEM as SUMMARY_PROMPT_SYSTEM
|
|
|
6
6
|
from letta.schemas.agent import AgentState
|
|
7
7
|
from letta.schemas.enums import MessageRole
|
|
8
8
|
from letta.schemas.memory import Memory
|
|
9
|
-
from letta.schemas.message import Message
|
|
9
|
+
from letta.schemas.message import Message, TextContent
|
|
10
10
|
from letta.settings import summarizer_settings
|
|
11
11
|
from letta.utils import count_tokens, printd
|
|
12
12
|
|
|
@@ -60,9 +60,9 @@ def summarize_messages(
|
|
|
60
60
|
|
|
61
61
|
dummy_agent_id = agent_state.id
|
|
62
62
|
message_sequence = [
|
|
63
|
-
Message(agent_id=dummy_agent_id, role=MessageRole.system, text=summary_prompt),
|
|
64
|
-
Message(agent_id=dummy_agent_id, role=MessageRole.assistant, text=MESSAGE_SUMMARY_REQUEST_ACK),
|
|
65
|
-
Message(agent_id=dummy_agent_id, role=MessageRole.user, text=summary_input),
|
|
63
|
+
Message(agent_id=dummy_agent_id, role=MessageRole.system, content=[TextContent(text=summary_prompt)]),
|
|
64
|
+
Message(agent_id=dummy_agent_id, role=MessageRole.assistant, content=[TextContent(text=MESSAGE_SUMMARY_REQUEST_ACK)]),
|
|
65
|
+
Message(agent_id=dummy_agent_id, role=MessageRole.user, content=[TextContent(text=summary_input)]),
|
|
66
66
|
]
|
|
67
67
|
|
|
68
68
|
# TODO: We need to eventually have a separate LLM config for the summarizer LLM
|
letta/offline_memory_agent.py
CHANGED
|
@@ -8,12 +8,12 @@ from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
|
8
8
|
from letta.schemas.usage import LettaUsageStatistics
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def trigger_rethink_memory(agent_state: "AgentState", message:
|
|
11
|
+
def trigger_rethink_memory(agent_state: "AgentState", message: str) -> None: # type: ignore
|
|
12
12
|
"""
|
|
13
13
|
Called if and only when user says the word trigger_rethink_memory". It will trigger the re-evaluation of the memory.
|
|
14
14
|
|
|
15
15
|
Args:
|
|
16
|
-
message (
|
|
16
|
+
message (str): Description of what aspect of the memory should be re-evaluated.
|
|
17
17
|
|
|
18
18
|
"""
|
|
19
19
|
from letta import create_client
|
|
@@ -25,12 +25,12 @@ def trigger_rethink_memory(agent_state: "AgentState", message: Optional[str]) ->
|
|
|
25
25
|
client.user_message(agent_id=agent.id, message=message)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def trigger_rethink_memory_convo(agent_state: "AgentState", message:
|
|
28
|
+
def trigger_rethink_memory_convo(agent_state: "AgentState", message: str) -> None: # type: ignore
|
|
29
29
|
"""
|
|
30
30
|
Called if and only when user says the word "trigger_rethink_memory". It will trigger the re-evaluation of the memory.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
|
-
message (
|
|
33
|
+
message (str): Description of what aspect of the memory should be re-evaluated.
|
|
34
34
|
|
|
35
35
|
"""
|
|
36
36
|
from letta import create_client
|
|
@@ -48,7 +48,7 @@ def trigger_rethink_memory_convo(agent_state: "AgentState", message: Optional[st
|
|
|
48
48
|
client.user_message(agent_id=agent.id, message=message)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def rethink_memory_convo(agent_state: "AgentState", new_memory: str, target_block_label:
|
|
51
|
+
def rethink_memory_convo(agent_state: "AgentState", new_memory: str, target_block_label: str, source_block_label: str) -> None: # type: ignore
|
|
52
52
|
"""
|
|
53
53
|
Re-evaluate the memory in block_name, integrating new and updated facts. Replace outdated information with the most likely truths, avoiding redundancy with original memories. Ensure consistency with other memory blocks.
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ def rethink_memory_convo(agent_state: "AgentState", new_memory: str, target_bloc
|
|
|
58
58
|
target_block_label (str): The name of the block to write to. This should be chat_agent_human_new or chat_agent_persona_new.
|
|
59
59
|
|
|
60
60
|
Returns:
|
|
61
|
-
|
|
61
|
+
None: None is always returned as this function does not produce a response.
|
|
62
62
|
"""
|
|
63
63
|
if target_block_label is not None:
|
|
64
64
|
if agent_state.memory.get_block(target_block_label) is None:
|
|
@@ -67,7 +67,7 @@ def rethink_memory_convo(agent_state: "AgentState", new_memory: str, target_bloc
|
|
|
67
67
|
return None
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def rethink_memory(agent_state: "AgentState", new_memory: str, target_block_label:
|
|
70
|
+
def rethink_memory(agent_state: "AgentState", new_memory: str, target_block_label: str, source_block_label: str) -> None: # type: ignore
|
|
71
71
|
"""
|
|
72
72
|
Re-evaluate the memory in block_name, integrating new and updated facts.
|
|
73
73
|
Replace outdated information with the most likely truths, avoiding redundancy with original memories.
|
|
@@ -78,7 +78,7 @@ def rethink_memory(agent_state: "AgentState", new_memory: str, target_block_labe
|
|
|
78
78
|
source_block_label (str): The name of the block to integrate information from. None if all the information has been integrated to terminate the loop.
|
|
79
79
|
target_block_label (str): The name of the block to write to.
|
|
80
80
|
Returns:
|
|
81
|
-
|
|
81
|
+
None: None is always returned as this function does not produce a response.
|
|
82
82
|
"""
|
|
83
83
|
|
|
84
84
|
if target_block_label is not None:
|
|
@@ -88,7 +88,7 @@ def rethink_memory(agent_state: "AgentState", new_memory: str, target_block_labe
|
|
|
88
88
|
return None
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def finish_rethinking_memory(agent_state: "AgentState") ->
|
|
91
|
+
def finish_rethinking_memory(agent_state: "AgentState") -> None: # type: ignore
|
|
92
92
|
"""
|
|
93
93
|
This function is called when the agent is done rethinking the memory.
|
|
94
94
|
|
|
@@ -98,7 +98,7 @@ def finish_rethinking_memory(agent_state: "AgentState") -> Optional[str]: # typ
|
|
|
98
98
|
return None
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def finish_rethinking_memory_convo(agent_state: "AgentState") ->
|
|
101
|
+
def finish_rethinking_memory_convo(agent_state: "AgentState") -> None: # type: ignore
|
|
102
102
|
"""
|
|
103
103
|
This function is called when the agent is done rethinking the memory.
|
|
104
104
|
|
letta/orm/agent.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from typing import TYPE_CHECKING, List, Optional
|
|
3
3
|
|
|
4
|
-
from sqlalchemy import JSON, String
|
|
4
|
+
from sqlalchemy import JSON, Index, String
|
|
5
5
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
6
|
|
|
7
7
|
from letta.orm.block import Block
|
|
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
|
|
27
27
|
class Agent(SqlalchemyBase, OrganizationMixin):
|
|
28
28
|
__tablename__ = "agents"
|
|
29
29
|
__pydantic_model__ = PydanticAgentState
|
|
30
|
+
__table_args__ = (Index("ix_agents_created_at", "created_at", "id"),)
|
|
30
31
|
|
|
31
32
|
# agent generates its own id
|
|
32
33
|
# TODO: We want to migrate all the ORM models to do this, so we will need to move this to the SqlalchemyBase
|
|
@@ -69,7 +70,14 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
69
70
|
)
|
|
70
71
|
tools: Mapped[List["Tool"]] = relationship("Tool", secondary="tools_agents", lazy="selectin", passive_deletes=True)
|
|
71
72
|
sources: Mapped[List["Source"]] = relationship("Source", secondary="sources_agents", lazy="selectin")
|
|
72
|
-
core_memory: Mapped[List["Block"]] = relationship(
|
|
73
|
+
core_memory: Mapped[List["Block"]] = relationship(
|
|
74
|
+
"Block",
|
|
75
|
+
secondary="blocks_agents",
|
|
76
|
+
lazy="selectin",
|
|
77
|
+
passive_deletes=True, # Ensures SQLAlchemy doesn't fetch blocks_agents rows before deleting
|
|
78
|
+
back_populates="agents",
|
|
79
|
+
doc="Blocks forming the core memory of the agent.",
|
|
80
|
+
)
|
|
73
81
|
messages: Mapped[List["Message"]] = relationship(
|
|
74
82
|
"Message",
|
|
75
83
|
back_populates="agent",
|
letta/orm/block.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Optional, Type
|
|
1
|
+
from typing import TYPE_CHECKING, List, Optional, Type
|
|
2
2
|
|
|
3
|
-
from sqlalchemy import JSON, BigInteger, Integer, UniqueConstraint, event
|
|
3
|
+
from sqlalchemy import JSON, BigInteger, Index, Integer, UniqueConstraint, event
|
|
4
4
|
from sqlalchemy.orm import Mapped, attributes, mapped_column, relationship
|
|
5
5
|
|
|
6
6
|
from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT
|
|
@@ -20,7 +20,10 @@ class Block(OrganizationMixin, SqlalchemyBase):
|
|
|
20
20
|
__tablename__ = "block"
|
|
21
21
|
__pydantic_model__ = PydanticBlock
|
|
22
22
|
# This may seem redundant, but is necessary for the BlocksAgents composite FK relationship
|
|
23
|
-
__table_args__ = (
|
|
23
|
+
__table_args__ = (
|
|
24
|
+
UniqueConstraint("id", "label", name="unique_block_id_label"),
|
|
25
|
+
Index("created_at_label_idx", "created_at", "label"),
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
template_name: Mapped[Optional[str]] = mapped_column(
|
|
26
29
|
nullable=True, doc="the unique name that identifies a block in a human-readable way"
|
|
@@ -36,6 +39,14 @@ class Block(OrganizationMixin, SqlalchemyBase):
|
|
|
36
39
|
|
|
37
40
|
# relationships
|
|
38
41
|
organization: Mapped[Optional["Organization"]] = relationship("Organization")
|
|
42
|
+
agents: Mapped[List["Agent"]] = relationship(
|
|
43
|
+
"Agent",
|
|
44
|
+
secondary="blocks_agents",
|
|
45
|
+
lazy="selectin",
|
|
46
|
+
passive_deletes=True, # Ensures SQLAlchemy doesn't fetch blocks_agents rows before deleting
|
|
47
|
+
back_populates="core_memory",
|
|
48
|
+
doc="Agents associated with this block.",
|
|
49
|
+
)
|
|
39
50
|
|
|
40
51
|
def to_pydantic(self) -> Type:
|
|
41
52
|
match self.label:
|
letta/orm/job.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import TYPE_CHECKING, List, Optional
|
|
3
3
|
|
|
4
|
-
from sqlalchemy import JSON, String
|
|
4
|
+
from sqlalchemy import JSON, Index, String
|
|
5
5
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
6
|
|
|
7
7
|
from letta.orm.enums import JobType
|
|
@@ -25,6 +25,7 @@ class Job(SqlalchemyBase, UserMixin):
|
|
|
25
25
|
|
|
26
26
|
__tablename__ = "jobs"
|
|
27
27
|
__pydantic_model__ = PydanticJob
|
|
28
|
+
__table_args__ = (Index("ix_jobs_created_at", "created_at", "id"),)
|
|
28
29
|
|
|
29
30
|
status: Mapped[JobStatus] = mapped_column(String, default=JobStatus.created, doc="The current status of the job.")
|
|
30
31
|
completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True, doc="The unix timestamp of when the job was completed.")
|
letta/orm/message.py
CHANGED
|
@@ -8,13 +8,17 @@ from letta.orm.custom_columns import ToolCallColumn
|
|
|
8
8
|
from letta.orm.mixins import AgentMixin, OrganizationMixin
|
|
9
9
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
10
10
|
from letta.schemas.message import Message as PydanticMessage
|
|
11
|
+
from letta.schemas.message import TextContent as PydanticTextContent
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
14
15
|
"""Defines data model for storing Message objects"""
|
|
15
16
|
|
|
16
17
|
__tablename__ = "messages"
|
|
17
|
-
__table_args__ = (
|
|
18
|
+
__table_args__ = (
|
|
19
|
+
Index("ix_messages_agent_created_at", "agent_id", "created_at"),
|
|
20
|
+
Index("ix_messages_created_at", "created_at", "id"),
|
|
21
|
+
)
|
|
18
22
|
__pydantic_model__ = PydanticMessage
|
|
19
23
|
|
|
20
24
|
id: Mapped[str] = mapped_column(primary_key=True, doc="Unique message identifier")
|
|
@@ -42,3 +46,10 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
|
42
46
|
def job(self) -> Optional["Job"]:
|
|
43
47
|
"""Get the job associated with this message, if any."""
|
|
44
48
|
return self.job_message.job if self.job_message else None
|
|
49
|
+
|
|
50
|
+
def to_pydantic(self) -> PydanticMessage:
|
|
51
|
+
"""custom pydantic conversion for message content mapping"""
|
|
52
|
+
model = self.__pydantic_model__.model_validate(self)
|
|
53
|
+
if self.text:
|
|
54
|
+
model.content = [PydanticTextContent(text=self.text)]
|
|
55
|
+
return model
|
letta/orm/passage.py
CHANGED
|
@@ -45,8 +45,12 @@ class BasePassage(SqlalchemyBase, OrganizationMixin):
|
|
|
45
45
|
@declared_attr
|
|
46
46
|
def __table_args__(cls):
|
|
47
47
|
if settings.letta_pg_uri_no_default:
|
|
48
|
-
return (
|
|
49
|
-
|
|
48
|
+
return (
|
|
49
|
+
Index(f"{cls.__tablename__}_org_idx", "organization_id"),
|
|
50
|
+
Index(f"{cls.__tablename__}_created_at_id_idx", "created_at", "id"),
|
|
51
|
+
{"extend_existing": True},
|
|
52
|
+
)
|
|
53
|
+
return (Index(f"{cls.__tablename__}_created_at_id_idx", "created_at", "id"), {"extend_existing": True})
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
class SourcePassage(BasePassage, FileMixin, SourceMixin):
|
letta/orm/source.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, List, Optional
|
|
2
2
|
|
|
3
|
-
from sqlalchemy import JSON
|
|
3
|
+
from sqlalchemy import JSON, Index
|
|
4
4
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
5
|
|
|
6
6
|
from letta.orm import FileMetadata
|
|
@@ -23,6 +23,11 @@ class Source(SqlalchemyBase, OrganizationMixin):
|
|
|
23
23
|
__tablename__ = "sources"
|
|
24
24
|
__pydantic_model__ = PydanticSource
|
|
25
25
|
|
|
26
|
+
__table_args__ = (
|
|
27
|
+
Index(f"source_created_at_id_idx", "created_at", "id"),
|
|
28
|
+
{"extend_existing": True},
|
|
29
|
+
)
|
|
30
|
+
|
|
26
31
|
name: Mapped[str] = mapped_column(doc="the name of the source, must be unique within the org", nullable=False)
|
|
27
32
|
description: Mapped[str] = mapped_column(nullable=True, doc="a human-readable description of the source")
|
|
28
33
|
embedding_config: Mapped[EmbeddingConfig] = mapped_column(EmbeddingConfigColumn, doc="Configuration settings for embedding.")
|