letta-nightly 0.5.2.dev20241118104226__py3-none-any.whl → 0.5.3.dev20241120010849__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/__init__.py +1 -1
- letta/agent.py +13 -7
- letta/agent_store/db.py +4 -3
- letta/cli/cli.py +2 -1
- letta/client/client.py +33 -30
- letta/constants.py +4 -1
- letta/functions/functions.py +2 -1
- letta/llm_api/llm_api_tools.py +38 -0
- letta/llm_api/openai.py +0 -1
- letta/local_llm/utils.py +12 -2
- letta/metadata.py +1 -155
- letta/o1_agent.py +3 -1
- letta/orm/__init__.py +1 -0
- letta/orm/block.py +44 -0
- letta/orm/organization.py +1 -0
- letta/providers.py +141 -3
- letta/schemas/block.py +31 -26
- letta/schemas/letta_base.py +1 -1
- letta/schemas/llm_config.py +1 -0
- letta/schemas/openai/chat_completion_response.py +1 -0
- letta/server/rest_api/routers/v1/blocks.py +18 -22
- letta/server/rest_api/routers/v1/sources.py +9 -3
- letta/server/server.py +20 -85
- letta/services/block_manager.py +103 -0
- letta/services/tool_manager.py +4 -0
- letta/settings.py +3 -0
- letta/utils.py +39 -0
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/METADATA +2 -1
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/RECORD +32 -30
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.2.dev20241118104226.dist-info → letta_nightly-0.5.3.dev20241120010849.dist-info}/entry_points.txt +0 -0
letta/metadata.py
CHANGED
|
@@ -4,23 +4,13 @@ import os
|
|
|
4
4
|
import secrets
|
|
5
5
|
from typing import List, Optional
|
|
6
6
|
|
|
7
|
-
from sqlalchemy import
|
|
8
|
-
BIGINT,
|
|
9
|
-
JSON,
|
|
10
|
-
Boolean,
|
|
11
|
-
Column,
|
|
12
|
-
DateTime,
|
|
13
|
-
Index,
|
|
14
|
-
String,
|
|
15
|
-
TypeDecorator,
|
|
16
|
-
)
|
|
7
|
+
from sqlalchemy import JSON, Column, DateTime, Index, String, TypeDecorator
|
|
17
8
|
from sqlalchemy.sql import func
|
|
18
9
|
|
|
19
10
|
from letta.config import LettaConfig
|
|
20
11
|
from letta.orm.base import Base
|
|
21
12
|
from letta.schemas.agent import AgentState
|
|
22
13
|
from letta.schemas.api_key import APIKey
|
|
23
|
-
from letta.schemas.block import Block, Human, Persona
|
|
24
14
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
25
15
|
from letta.schemas.enums import JobStatus
|
|
26
16
|
from letta.schemas.job import Job
|
|
@@ -269,63 +259,6 @@ class AgentSourceMappingModel(Base):
|
|
|
269
259
|
return f"<AgentSourceMapping(user_id='{self.user_id}', agent_id='{self.agent_id}', source_id='{self.source_id}')>"
|
|
270
260
|
|
|
271
261
|
|
|
272
|
-
class BlockModel(Base):
|
|
273
|
-
__tablename__ = "block"
|
|
274
|
-
__table_args__ = {"extend_existing": True}
|
|
275
|
-
|
|
276
|
-
id = Column(String, primary_key=True, nullable=False)
|
|
277
|
-
value = Column(String, nullable=False)
|
|
278
|
-
limit = Column(BIGINT)
|
|
279
|
-
template_name = Column(String, nullable=True, default=None)
|
|
280
|
-
template = Column(Boolean, default=False) # True: listed as possible human/persona
|
|
281
|
-
label = Column(String, nullable=False)
|
|
282
|
-
metadata_ = Column(JSON)
|
|
283
|
-
description = Column(String)
|
|
284
|
-
user_id = Column(String)
|
|
285
|
-
Index(__tablename__ + "_idx_user", user_id),
|
|
286
|
-
|
|
287
|
-
def __repr__(self) -> str:
|
|
288
|
-
return f"<Block(id='{self.id}', template_name='{self.template_name}', template='{self.template_name}', label='{self.label}', user_id='{self.user_id}')>"
|
|
289
|
-
|
|
290
|
-
def to_record(self) -> Block:
|
|
291
|
-
if self.label == "persona":
|
|
292
|
-
return Persona(
|
|
293
|
-
id=self.id,
|
|
294
|
-
value=self.value,
|
|
295
|
-
limit=self.limit,
|
|
296
|
-
template_name=self.template_name,
|
|
297
|
-
template=self.template,
|
|
298
|
-
label=self.label,
|
|
299
|
-
metadata_=self.metadata_,
|
|
300
|
-
description=self.description,
|
|
301
|
-
user_id=self.user_id,
|
|
302
|
-
)
|
|
303
|
-
elif self.label == "human":
|
|
304
|
-
return Human(
|
|
305
|
-
id=self.id,
|
|
306
|
-
value=self.value,
|
|
307
|
-
limit=self.limit,
|
|
308
|
-
template_name=self.template_name,
|
|
309
|
-
template=self.template,
|
|
310
|
-
label=self.label,
|
|
311
|
-
metadata_=self.metadata_,
|
|
312
|
-
description=self.description,
|
|
313
|
-
user_id=self.user_id,
|
|
314
|
-
)
|
|
315
|
-
else:
|
|
316
|
-
return Block(
|
|
317
|
-
id=self.id,
|
|
318
|
-
value=self.value,
|
|
319
|
-
limit=self.limit,
|
|
320
|
-
template_name=self.template_name,
|
|
321
|
-
template=self.template,
|
|
322
|
-
label=self.label,
|
|
323
|
-
metadata_=self.metadata_,
|
|
324
|
-
description=self.description,
|
|
325
|
-
user_id=self.user_id,
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
|
|
329
262
|
class JobModel(Base):
|
|
330
263
|
__tablename__ = "jobs"
|
|
331
264
|
__table_args__ = {"extend_existing": True}
|
|
@@ -425,27 +358,6 @@ class MetadataStore:
|
|
|
425
358
|
session.add(AgentModel(**fields))
|
|
426
359
|
session.commit()
|
|
427
360
|
|
|
428
|
-
@enforce_types
|
|
429
|
-
def create_block(self, block: Block):
|
|
430
|
-
with self.session_maker() as session:
|
|
431
|
-
# TODO: fix?
|
|
432
|
-
# we are only validating that more than one template block
|
|
433
|
-
# with a given name doesn't exist.
|
|
434
|
-
if (
|
|
435
|
-
session.query(BlockModel)
|
|
436
|
-
.filter(BlockModel.template_name == block.template_name)
|
|
437
|
-
.filter(BlockModel.user_id == block.user_id)
|
|
438
|
-
.filter(BlockModel.template == True)
|
|
439
|
-
.filter(BlockModel.label == block.label)
|
|
440
|
-
.count()
|
|
441
|
-
> 0
|
|
442
|
-
):
|
|
443
|
-
|
|
444
|
-
raise ValueError(f"Block with name {block.template_name} already exists")
|
|
445
|
-
|
|
446
|
-
session.add(BlockModel(**vars(block)))
|
|
447
|
-
session.commit()
|
|
448
|
-
|
|
449
361
|
@enforce_types
|
|
450
362
|
def update_agent(self, agent: AgentState):
|
|
451
363
|
with self.session_maker() as session:
|
|
@@ -457,28 +369,6 @@ class MetadataStore:
|
|
|
457
369
|
session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
|
|
458
370
|
session.commit()
|
|
459
371
|
|
|
460
|
-
@enforce_types
|
|
461
|
-
def update_block(self, block: Block):
|
|
462
|
-
with self.session_maker() as session:
|
|
463
|
-
session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
|
|
464
|
-
session.commit()
|
|
465
|
-
|
|
466
|
-
@enforce_types
|
|
467
|
-
def update_or_create_block(self, block: Block):
|
|
468
|
-
with self.session_maker() as session:
|
|
469
|
-
existing_block = session.query(BlockModel).filter(BlockModel.id == block.id).first()
|
|
470
|
-
if existing_block:
|
|
471
|
-
session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
|
|
472
|
-
else:
|
|
473
|
-
session.add(BlockModel(**vars(block)))
|
|
474
|
-
session.commit()
|
|
475
|
-
|
|
476
|
-
@enforce_types
|
|
477
|
-
def delete_block(self, block_id: str):
|
|
478
|
-
with self.session_maker() as session:
|
|
479
|
-
session.query(BlockModel).filter(BlockModel.id == block_id).delete()
|
|
480
|
-
session.commit()
|
|
481
|
-
|
|
482
372
|
@enforce_types
|
|
483
373
|
def delete_agent(self, agent_id: str):
|
|
484
374
|
with self.session_maker() as session:
|
|
@@ -513,50 +403,6 @@ class MetadataStore:
|
|
|
513
403
|
assert len(results) == 1, f"Expected 1 result, got {len(results)}" # should only be one result
|
|
514
404
|
return results[0].to_record()
|
|
515
405
|
|
|
516
|
-
@enforce_types
|
|
517
|
-
def get_block(self, block_id: str) -> Optional[Block]:
|
|
518
|
-
with self.session_maker() as session:
|
|
519
|
-
results = session.query(BlockModel).filter(BlockModel.id == block_id).all()
|
|
520
|
-
if len(results) == 0:
|
|
521
|
-
return None
|
|
522
|
-
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
|
523
|
-
return results[0].to_record()
|
|
524
|
-
|
|
525
|
-
@enforce_types
|
|
526
|
-
def get_blocks(
|
|
527
|
-
self,
|
|
528
|
-
user_id: Optional[str],
|
|
529
|
-
label: Optional[str] = None,
|
|
530
|
-
template: Optional[bool] = None,
|
|
531
|
-
template_name: Optional[str] = None,
|
|
532
|
-
id: Optional[str] = None,
|
|
533
|
-
) -> Optional[List[Block]]:
|
|
534
|
-
"""List available blocks"""
|
|
535
|
-
with self.session_maker() as session:
|
|
536
|
-
query = session.query(BlockModel)
|
|
537
|
-
|
|
538
|
-
if user_id:
|
|
539
|
-
query = query.filter(BlockModel.user_id == user_id)
|
|
540
|
-
|
|
541
|
-
if label:
|
|
542
|
-
query = query.filter(BlockModel.label == label)
|
|
543
|
-
|
|
544
|
-
if template_name:
|
|
545
|
-
query = query.filter(BlockModel.template_name == template_name)
|
|
546
|
-
|
|
547
|
-
if id:
|
|
548
|
-
query = query.filter(BlockModel.id == id)
|
|
549
|
-
|
|
550
|
-
if template:
|
|
551
|
-
query = query.filter(BlockModel.template == template)
|
|
552
|
-
|
|
553
|
-
results = query.all()
|
|
554
|
-
|
|
555
|
-
if len(results) == 0:
|
|
556
|
-
return None
|
|
557
|
-
|
|
558
|
-
return [r.to_record() for r in results]
|
|
559
|
-
|
|
560
406
|
# agent source metadata
|
|
561
407
|
@enforce_types
|
|
562
408
|
def attach_source(self, user_id: str, agent_id: str, source_id: str):
|
letta/o1_agent.py
CHANGED
|
@@ -8,6 +8,7 @@ from letta.schemas.message import Message
|
|
|
8
8
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
9
9
|
from letta.schemas.tool import Tool
|
|
10
10
|
from letta.schemas.usage import LettaUsageStatistics
|
|
11
|
+
from letta.schemas.user import User
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def send_thinking_message(self: "Agent", message: str) -> Optional[str]:
|
|
@@ -43,11 +44,12 @@ class O1Agent(Agent):
|
|
|
43
44
|
self,
|
|
44
45
|
interface: AgentInterface,
|
|
45
46
|
agent_state: AgentState,
|
|
47
|
+
user: User,
|
|
46
48
|
tools: List[Tool] = [],
|
|
47
49
|
max_thinking_steps: int = 10,
|
|
48
50
|
first_message_verify_mono: bool = False,
|
|
49
51
|
):
|
|
50
|
-
super().__init__(interface, agent_state, tools)
|
|
52
|
+
super().__init__(interface, agent_state, tools, user)
|
|
51
53
|
self.max_thinking_steps = max_thinking_steps
|
|
52
54
|
self.tools = tools
|
|
53
55
|
self.first_message_verify_mono = first_message_verify_mono
|
letta/orm/__init__.py
CHANGED
letta/orm/block.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional, Type
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import JSON, BigInteger, Integer
|
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
+
|
|
6
|
+
from letta.orm.mixins import OrganizationMixin
|
|
7
|
+
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
8
|
+
from letta.schemas.block import Block as PydanticBlock
|
|
9
|
+
from letta.schemas.block import Human, Persona
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from letta.orm.organization import Organization
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Block(OrganizationMixin, SqlalchemyBase):
|
|
16
|
+
"""Blocks are sections of the LLM context, representing a specific part of the total Memory"""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "block"
|
|
19
|
+
__pydantic_model__ = PydanticBlock
|
|
20
|
+
|
|
21
|
+
template_name: Mapped[Optional[str]] = mapped_column(
|
|
22
|
+
nullable=True, doc="the unique name that identifies a block in a human-readable way"
|
|
23
|
+
)
|
|
24
|
+
description: Mapped[Optional[str]] = mapped_column(nullable=True, doc="a description of the block for context")
|
|
25
|
+
label: Mapped[str] = mapped_column(doc="the type of memory block in use, ie 'human', 'persona', 'system'")
|
|
26
|
+
is_template: Mapped[bool] = mapped_column(
|
|
27
|
+
doc="whether the block is a template (e.g. saved human/persona options as baselines for other templates)", default=False
|
|
28
|
+
)
|
|
29
|
+
value: Mapped[str] = mapped_column(doc="Text content of the block for the respective section of core memory.")
|
|
30
|
+
limit: Mapped[BigInteger] = mapped_column(Integer, default=2000, doc="Character limit of the block.")
|
|
31
|
+
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, default={}, doc="arbitrary information related to the block.")
|
|
32
|
+
|
|
33
|
+
# relationships
|
|
34
|
+
organization: Mapped[Optional["Organization"]] = relationship("Organization")
|
|
35
|
+
|
|
36
|
+
def to_pydantic(self) -> Type:
|
|
37
|
+
match self.label:
|
|
38
|
+
case "human":
|
|
39
|
+
Schema = Human
|
|
40
|
+
case "persona":
|
|
41
|
+
Schema = Persona
|
|
42
|
+
case _:
|
|
43
|
+
Schema = PydanticBlock
|
|
44
|
+
return Schema.model_validate(self)
|
letta/orm/organization.py
CHANGED
|
@@ -23,6 +23,7 @@ class Organization(SqlalchemyBase):
|
|
|
23
23
|
# relationships
|
|
24
24
|
users: Mapped[List["User"]] = relationship("User", back_populates="organization", cascade="all, delete-orphan")
|
|
25
25
|
tools: Mapped[List["Tool"]] = relationship("Tool", back_populates="organization", cascade="all, delete-orphan")
|
|
26
|
+
blocks: Mapped[List["Block"]] = relationship("Block", back_populates="organization", cascade="all, delete-orphan")
|
|
26
27
|
sources: Mapped[List["Source"]] = relationship("Source", back_populates="organization", cascade="all, delete-orphan")
|
|
27
28
|
agents_tags: Mapped[List["AgentsTags"]] = relationship("AgentsTags", back_populates="organization", cascade="all, delete-orphan")
|
|
28
29
|
files: Mapped[List["FileMetadata"]] = relationship("FileMetadata", back_populates="organization", cascade="all, delete-orphan")
|
letta/providers.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field, model_validator
|
|
4
4
|
|
|
5
|
-
from letta.constants import LLM_MAX_TOKENS
|
|
5
|
+
from letta.constants import LLM_MAX_TOKENS, MIN_CONTEXT_WINDOW
|
|
6
6
|
from letta.llm_api.azure_openai import (
|
|
7
7
|
get_azure_chat_completions_endpoint,
|
|
8
8
|
get_azure_embeddings_endpoint,
|
|
@@ -67,10 +67,15 @@ class OpenAIProvider(Provider):
|
|
|
67
67
|
extra_params = {"supported_parameters": "tools"} if "openrouter.ai" in self.base_url else None
|
|
68
68
|
response = openai_get_model_list(self.base_url, api_key=self.api_key, extra_params=extra_params)
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
# TogetherAI's response is missing the 'data' field
|
|
71
|
+
# assert "data" in response, f"OpenAI model query response missing 'data' field: {response}"
|
|
72
|
+
if "data" in response:
|
|
73
|
+
data = response["data"]
|
|
74
|
+
else:
|
|
75
|
+
data = response
|
|
71
76
|
|
|
72
77
|
configs = []
|
|
73
|
-
for model in
|
|
78
|
+
for model in data:
|
|
74
79
|
assert "id" in model, f"OpenAI model missing 'id' field: {model}"
|
|
75
80
|
model_name = model["id"]
|
|
76
81
|
|
|
@@ -82,6 +87,32 @@ class OpenAIProvider(Provider):
|
|
|
82
87
|
|
|
83
88
|
if not context_window_size:
|
|
84
89
|
continue
|
|
90
|
+
|
|
91
|
+
# TogetherAI includes the type, which we can use to filter out embedding models
|
|
92
|
+
if self.base_url == "https://api.together.ai/v1":
|
|
93
|
+
if "type" in model and model["type"] != "chat":
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# for TogetherAI, we need to skip the models that don't support JSON mode / function calling
|
|
97
|
+
# requests.exceptions.HTTPError: HTTP error occurred: 400 Client Error: Bad Request for url: https://api.together.ai/v1/chat/completions | Status code: 400, Message: {
|
|
98
|
+
# "error": {
|
|
99
|
+
# "message": "mistralai/Mixtral-8x7B-v0.1 is not supported for JSON mode/function calling",
|
|
100
|
+
# "type": "invalid_request_error",
|
|
101
|
+
# "param": null,
|
|
102
|
+
# "code": "constraints_model"
|
|
103
|
+
# }
|
|
104
|
+
# }
|
|
105
|
+
if "config" not in model:
|
|
106
|
+
continue
|
|
107
|
+
if "chat_template" not in model["config"]:
|
|
108
|
+
continue
|
|
109
|
+
if model["config"]["chat_template"] is None:
|
|
110
|
+
continue
|
|
111
|
+
if "tools" not in model["config"]["chat_template"]:
|
|
112
|
+
continue
|
|
113
|
+
# if "config" in data and "chat_template" in data["config"] and "tools" not in data["config"]["chat_template"]:
|
|
114
|
+
# continue
|
|
115
|
+
|
|
85
116
|
configs.append(
|
|
86
117
|
LLMConfig(model=model_name, model_endpoint_type="openai", model_endpoint=self.base_url, context_window=context_window_size)
|
|
87
118
|
)
|
|
@@ -325,6 +356,113 @@ class GroqProvider(OpenAIProvider):
|
|
|
325
356
|
raise NotImplementedError
|
|
326
357
|
|
|
327
358
|
|
|
359
|
+
class TogetherProvider(OpenAIProvider):
|
|
360
|
+
"""TogetherAI provider that uses the /completions API
|
|
361
|
+
|
|
362
|
+
TogetherAI can also be used via the /chat/completions API
|
|
363
|
+
by settings OPENAI_API_KEY and OPENAI_API_BASE to the TogetherAI API key
|
|
364
|
+
and API URL, however /completions is preferred because their /chat/completions
|
|
365
|
+
function calling support is limited.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
name: str = "together"
|
|
369
|
+
base_url: str = "https://api.together.ai/v1"
|
|
370
|
+
api_key: str = Field(..., description="API key for the TogetherAI API.")
|
|
371
|
+
default_prompt_formatter: str = Field(..., description="Default prompt formatter (aka model wrapper) to use on vLLM /completions API.")
|
|
372
|
+
|
|
373
|
+
def list_llm_models(self) -> List[LLMConfig]:
|
|
374
|
+
from letta.llm_api.openai import openai_get_model_list
|
|
375
|
+
|
|
376
|
+
response = openai_get_model_list(self.base_url, api_key=self.api_key)
|
|
377
|
+
|
|
378
|
+
# TogetherAI's response is missing the 'data' field
|
|
379
|
+
# assert "data" in response, f"OpenAI model query response missing 'data' field: {response}"
|
|
380
|
+
if "data" in response:
|
|
381
|
+
data = response["data"]
|
|
382
|
+
else:
|
|
383
|
+
data = response
|
|
384
|
+
|
|
385
|
+
configs = []
|
|
386
|
+
for model in data:
|
|
387
|
+
assert "id" in model, f"TogetherAI model missing 'id' field: {model}"
|
|
388
|
+
model_name = model["id"]
|
|
389
|
+
|
|
390
|
+
if "context_length" in model:
|
|
391
|
+
# Context length is returned in OpenRouter as "context_length"
|
|
392
|
+
context_window_size = model["context_length"]
|
|
393
|
+
else:
|
|
394
|
+
context_window_size = self.get_model_context_window_size(model_name)
|
|
395
|
+
|
|
396
|
+
# We need the context length for embeddings too
|
|
397
|
+
if not context_window_size:
|
|
398
|
+
continue
|
|
399
|
+
|
|
400
|
+
# Skip models that are too small for Letta
|
|
401
|
+
if context_window_size <= MIN_CONTEXT_WINDOW:
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
# TogetherAI includes the type, which we can use to filter for embedding models
|
|
405
|
+
if "type" in model and model["type"] not in ["chat", "language"]:
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
configs.append(
|
|
409
|
+
LLMConfig(
|
|
410
|
+
model=model_name,
|
|
411
|
+
model_endpoint_type="together",
|
|
412
|
+
model_endpoint=self.base_url,
|
|
413
|
+
model_wrapper=self.default_prompt_formatter,
|
|
414
|
+
context_window=context_window_size,
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
return configs
|
|
419
|
+
|
|
420
|
+
def list_embedding_models(self) -> List[EmbeddingConfig]:
|
|
421
|
+
# TODO renable once we figure out how to pass API keys through properly
|
|
422
|
+
return []
|
|
423
|
+
|
|
424
|
+
# from letta.llm_api.openai import openai_get_model_list
|
|
425
|
+
|
|
426
|
+
# response = openai_get_model_list(self.base_url, api_key=self.api_key)
|
|
427
|
+
|
|
428
|
+
# # TogetherAI's response is missing the 'data' field
|
|
429
|
+
# # assert "data" in response, f"OpenAI model query response missing 'data' field: {response}"
|
|
430
|
+
# if "data" in response:
|
|
431
|
+
# data = response["data"]
|
|
432
|
+
# else:
|
|
433
|
+
# data = response
|
|
434
|
+
|
|
435
|
+
# configs = []
|
|
436
|
+
# for model in data:
|
|
437
|
+
# assert "id" in model, f"TogetherAI model missing 'id' field: {model}"
|
|
438
|
+
# model_name = model["id"]
|
|
439
|
+
|
|
440
|
+
# if "context_length" in model:
|
|
441
|
+
# # Context length is returned in OpenRouter as "context_length"
|
|
442
|
+
# context_window_size = model["context_length"]
|
|
443
|
+
# else:
|
|
444
|
+
# context_window_size = self.get_model_context_window_size(model_name)
|
|
445
|
+
|
|
446
|
+
# if not context_window_size:
|
|
447
|
+
# continue
|
|
448
|
+
|
|
449
|
+
# # TogetherAI includes the type, which we can use to filter out embedding models
|
|
450
|
+
# if "type" in model and model["type"] not in ["embedding"]:
|
|
451
|
+
# continue
|
|
452
|
+
|
|
453
|
+
# configs.append(
|
|
454
|
+
# EmbeddingConfig(
|
|
455
|
+
# embedding_model=model_name,
|
|
456
|
+
# embedding_endpoint_type="openai",
|
|
457
|
+
# embedding_endpoint=self.base_url,
|
|
458
|
+
# embedding_dim=context_window_size,
|
|
459
|
+
# embedding_chunk_size=300, # TODO: change?
|
|
460
|
+
# )
|
|
461
|
+
# )
|
|
462
|
+
|
|
463
|
+
# return configs
|
|
464
|
+
|
|
465
|
+
|
|
328
466
|
class GoogleAIProvider(Provider):
|
|
329
467
|
# gemini
|
|
330
468
|
api_key: str = Field(..., description="API key for the Google AI API.")
|
letta/schemas/block.py
CHANGED
|
@@ -14,36 +14,30 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
|
|
14
14
|
__id_prefix__ = "block"
|
|
15
15
|
|
|
16
16
|
# data value
|
|
17
|
-
value:
|
|
17
|
+
value: str = Field(..., description="Value of the block.")
|
|
18
18
|
limit: int = Field(2000, description="Character limit of the block.")
|
|
19
19
|
|
|
20
20
|
# template data (optional)
|
|
21
21
|
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
|
|
22
|
-
|
|
22
|
+
is_template: bool = Field(False, description="Whether the block is a template (e.g. saved human/persona options).")
|
|
23
23
|
|
|
24
24
|
# context window label
|
|
25
|
-
label: str = Field(None, description="Label of the block (e.g. 'human', 'persona') in the context window.")
|
|
25
|
+
label: Optional[str] = Field(None, description="Label of the block (e.g. 'human', 'persona') in the context window.")
|
|
26
26
|
|
|
27
27
|
# metadata
|
|
28
28
|
description: Optional[str] = Field(None, description="Description of the block.")
|
|
29
29
|
metadata_: Optional[dict] = Field({}, description="Metadata of the block.")
|
|
30
30
|
|
|
31
|
-
# associated user/agent
|
|
32
|
-
user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the block.")
|
|
33
|
-
|
|
34
31
|
@model_validator(mode="after")
|
|
35
32
|
def verify_char_limit(self) -> Self:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
except AssertionError:
|
|
39
|
-
error_msg = f"Edit failed: Exceeds {self.limit} character limit (requested {len(self)}) - {str(self)}."
|
|
33
|
+
if len(self.value) > self.limit:
|
|
34
|
+
error_msg = f"Edit failed: Exceeds {self.limit} character limit (requested {len(self.value)}) - {str(self)}."
|
|
40
35
|
raise ValueError(error_msg)
|
|
41
|
-
|
|
42
|
-
raise e
|
|
36
|
+
|
|
43
37
|
return self
|
|
44
38
|
|
|
45
|
-
def __len__(self):
|
|
46
|
-
|
|
39
|
+
# def __len__(self):
|
|
40
|
+
# return len(self.value)
|
|
47
41
|
|
|
48
42
|
def __setattr__(self, name, value):
|
|
49
43
|
"""Run validation if self.value is updated"""
|
|
@@ -52,6 +46,9 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
|
|
52
46
|
# run validation
|
|
53
47
|
self.__class__.model_validate(self.model_dump(exclude_unset=True))
|
|
54
48
|
|
|
49
|
+
class Config:
|
|
50
|
+
extra = "ignore" # Ignores extra fields
|
|
51
|
+
|
|
55
52
|
|
|
56
53
|
class Block(BaseBlock):
|
|
57
54
|
"""
|
|
@@ -61,15 +58,22 @@ class Block(BaseBlock):
|
|
|
61
58
|
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
62
59
|
value (str): The value of the block. This is the string that is represented in the context window.
|
|
63
60
|
limit (int): The character limit of the block.
|
|
61
|
+
is_template (bool): Whether the block is a template (e.g. saved human/persona options). Non-template blocks are not stored in the database and are ephemeral, while templated blocks are stored in the database.
|
|
62
|
+
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
64
63
|
template_name (str): The name of the block template (if it is a template).
|
|
65
|
-
template (bool): Whether the block is a template (e.g. saved human/persona options). Non-template blocks are not stored in the database and are ephemeral, while templated blocks are stored in the database.
|
|
66
64
|
description (str): Description of the block.
|
|
67
65
|
metadata_ (Dict): Metadata of the block.
|
|
68
66
|
user_id (str): The unique identifier of the user associated with the block.
|
|
69
67
|
"""
|
|
70
68
|
|
|
71
69
|
id: str = BaseBlock.generate_id_field()
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
# associated user/agent
|
|
72
|
+
organization_id: Optional[str] = Field(None, description="The unique identifier of the organization associated with the block.")
|
|
73
|
+
|
|
74
|
+
# default orm fields
|
|
75
|
+
created_by_id: Optional[str] = Field(None, description="The id of the user that made this Block.")
|
|
76
|
+
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that last updated this Block.")
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
class Human(Block):
|
|
@@ -84,41 +88,42 @@ class Persona(Block):
|
|
|
84
88
|
label: str = "persona"
|
|
85
89
|
|
|
86
90
|
|
|
87
|
-
class
|
|
91
|
+
class BlockCreate(BaseBlock):
|
|
88
92
|
"""Create a block"""
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
is_template: bool = True
|
|
91
95
|
label: str = Field(..., description="Label of the block.")
|
|
92
96
|
|
|
93
97
|
|
|
94
|
-
class CreatePersona(
|
|
98
|
+
class CreatePersona(BlockCreate):
|
|
95
99
|
"""Create a persona block"""
|
|
96
100
|
|
|
97
|
-
template: bool = True
|
|
98
101
|
label: str = "persona"
|
|
99
102
|
|
|
100
103
|
|
|
101
|
-
class CreateHuman(
|
|
104
|
+
class CreateHuman(BlockCreate):
|
|
102
105
|
"""Create a human block"""
|
|
103
106
|
|
|
104
|
-
template: bool = True
|
|
105
107
|
label: str = "human"
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
class
|
|
110
|
+
class BlockUpdate(BaseBlock):
|
|
109
111
|
"""Update a block"""
|
|
110
112
|
|
|
111
|
-
id: str = Field(..., description="The unique identifier of the block.")
|
|
112
113
|
limit: Optional[int] = Field(2000, description="Character limit of the block.")
|
|
114
|
+
value: Optional[str] = Field(None, description="Value of the block.")
|
|
115
|
+
|
|
116
|
+
class Config:
|
|
117
|
+
extra = "ignore" # Ignores extra fields
|
|
113
118
|
|
|
114
119
|
|
|
115
|
-
class UpdatePersona(
|
|
120
|
+
class UpdatePersona(BlockUpdate):
|
|
116
121
|
"""Update a persona block"""
|
|
117
122
|
|
|
118
123
|
label: str = "persona"
|
|
119
124
|
|
|
120
125
|
|
|
121
|
-
class UpdateHuman(
|
|
126
|
+
class UpdateHuman(BlockUpdate):
|
|
122
127
|
"""Update a human block"""
|
|
123
128
|
|
|
124
129
|
label: str = "human"
|
letta/schemas/letta_base.py
CHANGED
|
@@ -77,6 +77,6 @@ class LettaBase(BaseModel):
|
|
|
77
77
|
"""
|
|
78
78
|
_ = values # for SCA
|
|
79
79
|
if isinstance(v, UUID):
|
|
80
|
-
logger.warning("Bare UUIDs are deprecated, please use the full prefixed id!")
|
|
80
|
+
logger.warning(f"Bare UUIDs are deprecated, please use the full prefixed id ({cls.__id_prefix__})!")
|
|
81
81
|
return f"{cls.__id_prefix__}-{v}"
|
|
82
82
|
return v
|
letta/schemas/llm_config.py
CHANGED
|
@@ -35,6 +35,7 @@ class LLMConfig(BaseModel):
|
|
|
35
35
|
"vllm",
|
|
36
36
|
"hugging-face",
|
|
37
37
|
"mistral",
|
|
38
|
+
"together", # completions endpoint
|
|
38
39
|
] = Field(..., description="The endpoint type for the model.")
|
|
39
40
|
model_endpoint: Optional[str] = Field(None, description="The endpoint for the model.")
|
|
40
41
|
model_wrapper: Optional[str] = Field(None, description="The wrapper for the model.")
|