Skip to content

Commit a22e55c

Browse files
authored
chore: bump v0.11.4 (#2767)
2 parents 0c359e1 + f280406 commit a22e55c

File tree

128 files changed

+7561
-3415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+7561
-3415
lines changed

.github/workflows/send-message-integration-tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ jobs:
148148
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
149149
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
150150
GOOGLE_CLOUD_LOCATION: ${{ secrets.GOOGLE_CLOUD_LOCATION }}
151+
LETTA_GEMINI_FORCE_MINIMUM_THINKING_BUDGET: true
151152
run: |
152153
poetry run pytest \
153154
-s -vv \
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""add api version to byok providers
2+
3+
Revision ID: ffb17eb241fc
4+
Revises: 5fb8bba2c373
5+
Create Date: 2025-08-12 14:35:26.375985
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "ffb17eb241fc"
17+
down_revision: Union[str, None] = "5fb8bba2c373"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column("providers", sa.Column("api_version", sa.String(), nullable=True))
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade() -> None:
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.drop_column("providers", "api_version")
31+
# ### end Alembic commands ###

examples/docs/node/example.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { LettaClient } from '@letta-ai/letta-client';
21
import type {
32
AssistantMessage,
43
ReasoningMessage,

letta/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
__version__ = version("letta")
66
except PackageNotFoundError:
77
# Fallback for development installations
8-
__version__ = "0.11.3"
8+
__version__ = "0.11.4"
99

1010
if os.environ.get("LETTA_VERSION"):
1111
__version__ = os.environ["LETTA_VERSION"]

letta/agents/helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import List, Optional, Tuple
55

66
from letta.helpers import ToolRulesSolver
7+
from letta.log import get_logger
78
from letta.schemas.agent import AgentState
89
from letta.schemas.letta_message import MessageType
910
from letta.schemas.letta_response import LettaResponse
@@ -15,6 +16,8 @@
1516
from letta.server.rest_api.utils import create_input_messages
1617
from letta.services.message_manager import MessageManager
1718

19+
logger = get_logger(__name__)
20+
1821

1922
def _create_letta_response(
2023
new_in_context_messages: list[Message],
@@ -222,6 +225,7 @@ def _safe_load_tool_call_str(tool_call_args_str: str) -> dict:
222225
# Load it again - this is due to sometimes Anthropic returning weird json @caren
223226
tool_args = json.loads(tool_args)
224227
except json.JSONDecodeError:
228+
logger.error("Failed to JSON decode tool call argument string: %s", tool_call_args_str)
225229
tool_args = {}
226230

227231
return tool_args

letta/agents/letta_agent.py

Lines changed: 142 additions & 5 deletions
Large diffs are not rendered by default.

letta/constants.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
DEFAULT_ORG_ID = "org-00000000-0000-4000-8000-000000000000"
3737
DEFAULT_ORG_NAME = "default_org"
3838

39+
AGENT_ID_PATTERN = re.compile(r"^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", re.IGNORECASE)
3940

4041
# String in the error message for when the context window is too large
4142
# Example full message:
@@ -330,15 +331,15 @@ def FUNCTION_RETURN_VALUE_TRUNCATED(return_str, return_char: int, return_char_li
330331
MESSAGE_SUMMARY_REQUEST_ACK = "Understood, I will respond with a summary of the message (and only the summary, nothing else) once I receive the conversation history. I'm ready."
331332

332333
# Maximum length of an error message
333-
MAX_ERROR_MESSAGE_CHAR_LIMIT = 500
334+
MAX_ERROR_MESSAGE_CHAR_LIMIT = 1000
334335

335336
# Default memory limits
336-
CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 5000
337-
CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 5000
338-
CORE_MEMORY_BLOCK_CHAR_LIMIT: int = 5000
337+
CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 20000
338+
CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 20000
339+
CORE_MEMORY_BLOCK_CHAR_LIMIT: int = 20000
339340

340341
# Function return limits
341-
FUNCTION_RETURN_CHAR_LIMIT = 6000 # ~300 words
342+
FUNCTION_RETURN_CHAR_LIMIT = 50000 # ~300 words
342343
BASE_FUNCTION_RETURN_CHAR_LIMIT = 1000000 # very high (we rely on implementation)
343344
FILE_IS_TRUNCATED_WARNING = "# NOTE: This block is truncated, use functions to view the full content."
344345

@@ -396,5 +397,7 @@ def FUNCTION_RETURN_VALUE_TRUNCATED(return_str, return_char: int, return_char_li
396397
WEB_SEARCH_MODEL_ENV_VAR_NAME = "LETTA_BUILTIN_WEBSEARCH_OPENAI_MODEL_NAME"
397398
WEB_SEARCH_MODEL_ENV_VAR_DEFAULT_VALUE = "gpt-4.1-mini-2025-04-14"
398399

399-
# Excluded providers from base tool rules
400-
EXCLUDED_PROVIDERS_FROM_BASE_TOOL_RULES = {"anthropic", "openai", "google_ai", "google_vertex"}
400+
# Excluded model keywords from base tool rules
401+
EXCLUDE_MODEL_KEYWORDS_FROM_BASE_TOOL_RULES = ["claude-4-sonnet", "claude-3-5-sonnet", "gpt-5", "gemini-2.5-pro"]
402+
# But include models with these keywords in base tool rules (overrides exclusion)
403+
INCLUDE_MODEL_KEYWORDS_BASE_TOOL_RULES = ["mini"]

letta/data_sources/connectors.py

Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from letta.constants import EMBEDDING_BATCH_SIZE
66
from letta.data_sources.connectors_helper import assert_all_files_exist_locally, extract_metadata_from_files, get_filenames_in_dir
7-
from letta.embeddings import embedding_model
87
from letta.schemas.file import FileMetadata
98
from letta.schemas.passage import Passage
109
from letta.schemas.source import Source
@@ -40,61 +39,29 @@ def generate_passages(self, file: FileMetadata, chunk_size: int = 1024) -> Itera
4039

4140
async def load_data(connector: DataConnector, source: Source, passage_manager: PassageManager, file_manager: FileManager, actor: "User"):
4241
from letta.llm_api.llm_client import LLMClient
43-
from letta.schemas.embedding_config import EmbeddingConfig
4442

4543
"""Load data from a connector (generates file and passages) into a specified source_id, associated with a user_id."""
4644
embedding_config = source.embedding_config
4745

4846
# insert passages/file
49-
texts = []
5047
embedding_to_document_name = {}
5148
passage_count = 0
5249
file_count = 0
5350

54-
async def generate_embeddings(texts: List[str], embedding_config: EmbeddingConfig) -> List[Passage]:
55-
passages = []
56-
if embedding_config.embedding_endpoint_type == "openai":
57-
texts.append(passage_text)
58-
59-
client = LLMClient.create(
60-
provider_type=embedding_config.embedding_endpoint_type,
61-
actor=actor,
62-
)
63-
embeddings = await client.request_embeddings(texts, embedding_config)
64-
65-
else:
66-
embed_model = embedding_model(embedding_config)
67-
embeddings = [embed_model.get_text_embedding(text) for text in texts]
68-
69-
# collate passage and embedding
70-
for text, embedding in zip(texts, embeddings):
71-
passage = Passage(
72-
text=text,
73-
file_id=file_metadata.id,
74-
source_id=source.id,
75-
metadata=passage_metadata,
76-
organization_id=source.organization_id,
77-
embedding_config=source.embedding_config,
78-
embedding=embedding,
79-
)
80-
hashable_embedding = tuple(passage.embedding)
81-
file_name = file_metadata.file_name
82-
if hashable_embedding in embedding_to_document_name:
83-
typer.secho(
84-
f"Warning: Duplicate embedding found for passage in {file_name} (already exists in {embedding_to_document_name[hashable_embedding]}), skipping insert into VectorDB.",
85-
fg=typer.colors.YELLOW,
86-
)
87-
continue
88-
89-
passages.append(passage)
90-
embedding_to_document_name[hashable_embedding] = file_name
91-
return passages
51+
# Use the new LLMClient for all embedding requests
52+
client = LLMClient.create(
53+
provider_type=embedding_config.embedding_endpoint_type,
54+
actor=actor,
55+
)
9256

9357
for file_metadata in connector.find_files(source):
9458
file_count += 1
9559
await file_manager.create_file(file_metadata, actor)
9660

97-
# generate passages
61+
# generate passages for this file
62+
texts = []
63+
metadatas = []
64+
9865
for passage_text, passage_metadata in connector.generate_passages(file_metadata, chunk_size=embedding_config.embedding_chunk_size):
9966
# for some reason, llama index parsers sometimes return empty strings
10067
if len(passage_text) == 0:
@@ -104,24 +71,74 @@ async def generate_embeddings(texts: List[str], embedding_config: EmbeddingConfi
10471
)
10572
continue
10673

107-
# get embedding
10874
texts.append(passage_text)
75+
metadatas.append(passage_metadata)
76+
10977
if len(texts) >= EMBEDDING_BATCH_SIZE:
110-
passages = await generate_embeddings(texts, embedding_config)
78+
# Process the batch
79+
embeddings = await client.request_embeddings(texts, embedding_config)
80+
passages = []
81+
82+
for text, embedding, passage_metadata in zip(texts, embeddings, metadatas):
83+
passage = Passage(
84+
text=text,
85+
file_id=file_metadata.id,
86+
source_id=source.id,
87+
metadata=passage_metadata,
88+
organization_id=source.organization_id,
89+
embedding_config=source.embedding_config,
90+
embedding=embedding,
91+
)
92+
hashable_embedding = tuple(passage.embedding)
93+
file_name = file_metadata.file_name
94+
if hashable_embedding in embedding_to_document_name:
95+
typer.secho(
96+
f"Warning: Duplicate embedding found for passage in {file_name} (already exists in {embedding_to_document_name[hashable_embedding]}), skipping insert into VectorDB.",
97+
fg=typer.colors.YELLOW,
98+
)
99+
continue
100+
101+
passages.append(passage)
102+
embedding_to_document_name[hashable_embedding] = file_name
103+
104+
# insert passages into passage store
105+
await passage_manager.create_many_passages_async(passages, actor)
106+
passage_count += len(passages)
107+
108+
# Reset for next batch
111109
texts = []
112-
else:
113-
continue
110+
metadatas = []
111+
112+
# Process final remaining texts for this file
113+
if len(texts) > 0:
114+
embeddings = await client.request_embeddings(texts, embedding_config)
115+
passages = []
116+
117+
for text, embedding, passage_metadata in zip(texts, embeddings, metadatas):
118+
passage = Passage(
119+
text=text,
120+
file_id=file_metadata.id,
121+
source_id=source.id,
122+
metadata=passage_metadata,
123+
organization_id=source.organization_id,
124+
embedding_config=source.embedding_config,
125+
embedding=embedding,
126+
)
127+
hashable_embedding = tuple(passage.embedding)
128+
file_name = file_metadata.file_name
129+
if hashable_embedding in embedding_to_document_name:
130+
typer.secho(
131+
f"Warning: Duplicate embedding found for passage in {file_name} (already exists in {embedding_to_document_name[hashable_embedding]}), skipping insert into VectorDB.",
132+
fg=typer.colors.YELLOW,
133+
)
134+
continue
135+
136+
passages.append(passage)
137+
embedding_to_document_name[hashable_embedding] = file_name
114138

115-
# insert passages into passage store
116139
await passage_manager.create_many_passages_async(passages, actor)
117140
passage_count += len(passages)
118141

119-
# final remaining
120-
if len(texts) > 0:
121-
passages = await generate_embeddings(texts, embedding_config)
122-
await passage_manager.create_many_passages_async(passages, actor)
123-
passage_count += len(passages)
124-
125142
return passage_count, file_count
126143

127144

0 commit comments

Comments
 (0)