letta-nightly 0.5.1.dev20241106104104__py3-none-any.whl → 0.5.2.dev20241108104049__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 +3 -1
- letta/cli/cli.py +14 -1
- letta/client/client.py +16 -8
- letta/constants.py +3 -0
- letta/functions/functions.py +1 -1
- letta/helpers/tool_rule_solver.py +1 -2
- letta/log.py +1 -1
- letta/main.py +3 -0
- letta/metadata.py +2 -0
- letta/orm/agents_tags.py +28 -0
- letta/orm/organization.py +1 -0
- letta/orm/sqlalchemy_base.py +16 -0
- letta/schemas/agent.py +5 -0
- letta/schemas/agents_tags.py +33 -0
- letta/schemas/block.py +3 -3
- letta/schemas/letta_response.py +110 -0
- letta/schemas/llm_config.py +7 -1
- letta/schemas/tool.py +6 -2
- letta/schemas/tool_rule.py +12 -2
- letta/server/generate_openapi_schema.sh +12 -0
- letta/server/rest_api/app.py +36 -29
- letta/server/rest_api/routers/v1/agents.py +2 -122
- letta/server/rest_api/routers/v1/tools.py +1 -1
- letta/server/rest_api/routers/v1/users.py +13 -1
- letta/server/server.py +73 -42
- letta/server/startup.sh +9 -3
- letta/services/agents_tags_manager.py +64 -0
- letta/services/tool_manager.py +7 -16
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241108104049.dist-info}/METADATA +4 -1
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241108104049.dist-info}/RECORD +35 -31
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241108104049.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241108104049.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.1.dev20241106104104.dist-info → letta_nightly-0.5.2.dev20241108104049.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
letta/agent.py
CHANGED
|
@@ -3,7 +3,6 @@ import inspect
|
|
|
3
3
|
import traceback
|
|
4
4
|
import warnings
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
-
from lib2to3.fixer_util import is_list
|
|
7
6
|
from typing import List, Literal, Optional, Tuple, Union
|
|
8
7
|
|
|
9
8
|
from tqdm import tqdm
|
|
@@ -249,12 +248,21 @@ class Agent(BaseAgent):
|
|
|
249
248
|
# initialize a tool rules solver
|
|
250
249
|
if agent_state.tool_rules:
|
|
251
250
|
# if there are tool rules, print out a warning
|
|
252
|
-
|
|
251
|
+
for rule in agent_state.tool_rules:
|
|
252
|
+
if not isinstance(rule, TerminalToolRule):
|
|
253
|
+
warnings.warn("Tool rules only work reliably for the latest OpenAI models that support structured outputs.")
|
|
254
|
+
break
|
|
253
255
|
# add default rule for having send_message be a terminal tool
|
|
254
|
-
|
|
255
|
-
if not is_list(agent_state.tool_rules):
|
|
256
|
+
if agent_state.tool_rules is None:
|
|
256
257
|
agent_state.tool_rules = []
|
|
257
|
-
|
|
258
|
+
# Define the rule to add
|
|
259
|
+
send_message_terminal_rule = TerminalToolRule(tool_name="send_message")
|
|
260
|
+
# Check if an equivalent rule is already present
|
|
261
|
+
if not any(
|
|
262
|
+
isinstance(rule, TerminalToolRule) and rule.tool_name == send_message_terminal_rule.tool_name for rule in agent_state.tool_rules
|
|
263
|
+
):
|
|
264
|
+
agent_state.tool_rules.append(send_message_terminal_rule)
|
|
265
|
+
|
|
258
266
|
self.tool_rules_solver = ToolRulesSolver(tool_rules=agent_state.tool_rules)
|
|
259
267
|
|
|
260
268
|
# gpt-4, gpt-3.5-turbo, ...
|
|
@@ -396,7 +404,6 @@ class Agent(BaseAgent):
|
|
|
396
404
|
exec(tool.module, env)
|
|
397
405
|
else:
|
|
398
406
|
exec(tool.source_code, env)
|
|
399
|
-
|
|
400
407
|
self.functions_python[tool.json_schema["name"]] = env[tool.json_schema["name"]]
|
|
401
408
|
self.functions.append(tool.json_schema)
|
|
402
409
|
except Exception as e:
|
|
@@ -788,7 +795,6 @@ class Agent(BaseAgent):
|
|
|
788
795
|
|
|
789
796
|
# Update ToolRulesSolver state with last called function
|
|
790
797
|
self.tool_rules_solver.update_tool_usage(function_name)
|
|
791
|
-
|
|
792
798
|
# Update heartbeat request according to provided tool rules
|
|
793
799
|
if self.tool_rules_solver.has_children_tools(function_name):
|
|
794
800
|
heartbeat_request = True
|
letta/agent_store/db.py
CHANGED
|
@@ -578,4 +578,6 @@ class SQLLiteStorageConnector(SQLStorageConnector):
|
|
|
578
578
|
def attach_base():
|
|
579
579
|
# This should be invoked in server.py to make sure Base gets initialized properly
|
|
580
580
|
# DO NOT REMOVE
|
|
581
|
-
|
|
581
|
+
from letta.utils import printd
|
|
582
|
+
|
|
583
|
+
printd("Initializing database...")
|
letta/cli/cli.py
CHANGED
|
@@ -10,7 +10,7 @@ import letta.utils as utils
|
|
|
10
10
|
from letta import create_client
|
|
11
11
|
from letta.agent import Agent, save_agent
|
|
12
12
|
from letta.config import LettaConfig
|
|
13
|
-
from letta.constants import CLI_WARNING_PREFIX, LETTA_DIR
|
|
13
|
+
from letta.constants import CLI_WARNING_PREFIX, LETTA_DIR, MIN_CONTEXT_WINDOW
|
|
14
14
|
from letta.local_llm.constants import ASSISTANT_MESSAGE_CLI_SYMBOL
|
|
15
15
|
from letta.log import get_logger
|
|
16
16
|
from letta.metadata import MetadataStore
|
|
@@ -244,6 +244,19 @@ def run(
|
|
|
244
244
|
llm_model_name = questionary.select("Select LLM model:", choices=llm_choices).ask().model
|
|
245
245
|
llm_config = [llm_config for llm_config in llm_configs if llm_config.model == llm_model_name][0]
|
|
246
246
|
|
|
247
|
+
# option to override context window
|
|
248
|
+
if llm_config.context_window is not None:
|
|
249
|
+
context_window_validator = lambda x: x.isdigit() and int(x) > MIN_CONTEXT_WINDOW and int(x) <= llm_config.context_window
|
|
250
|
+
context_window_input = questionary.text(
|
|
251
|
+
"Select LLM context window limit (hit enter for default):",
|
|
252
|
+
default=str(llm_config.context_window),
|
|
253
|
+
validate=context_window_validator,
|
|
254
|
+
).ask()
|
|
255
|
+
if context_window_input is not None:
|
|
256
|
+
llm_config.context_window = int(context_window_input)
|
|
257
|
+
else:
|
|
258
|
+
sys.exit(1)
|
|
259
|
+
|
|
247
260
|
# choose form list of embedding configs
|
|
248
261
|
embedding_configs = client.list_embedding_configs()
|
|
249
262
|
embedding_options = [embedding_config.embedding_model for embedding_config in embedding_configs]
|
letta/client/client.py
CHANGED
|
@@ -77,6 +77,7 @@ class AbstractClient(object):
|
|
|
77
77
|
memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
|
|
78
78
|
system: Optional[str] = None,
|
|
79
79
|
tools: Optional[List[str]] = None,
|
|
80
|
+
tool_rules: Optional[List[BaseToolRule]] = None,
|
|
80
81
|
include_base_tools: Optional[bool] = True,
|
|
81
82
|
metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
|
|
82
83
|
description: Optional[str] = None,
|
|
@@ -333,8 +334,12 @@ class RESTClient(AbstractClient):
|
|
|
333
334
|
self._default_llm_config = default_llm_config
|
|
334
335
|
self._default_embedding_config = default_embedding_config
|
|
335
336
|
|
|
336
|
-
def list_agents(self) -> List[AgentState]:
|
|
337
|
-
|
|
337
|
+
def list_agents(self, tags: Optional[List[str]] = None) -> List[AgentState]:
|
|
338
|
+
params = {}
|
|
339
|
+
if tags:
|
|
340
|
+
params["tags"] = tags
|
|
341
|
+
|
|
342
|
+
response = requests.get(f"{self.base_url}/{self.api_prefix}/agents", headers=self.headers, params=params)
|
|
338
343
|
return [AgentState(**agent) for agent in response.json()]
|
|
339
344
|
|
|
340
345
|
def agent_exists(self, agent_id: str) -> bool:
|
|
@@ -372,6 +377,7 @@ class RESTClient(AbstractClient):
|
|
|
372
377
|
system: Optional[str] = None,
|
|
373
378
|
# tools
|
|
374
379
|
tools: Optional[List[str]] = None,
|
|
380
|
+
tool_rules: Optional[List[BaseToolRule]] = None,
|
|
375
381
|
include_base_tools: Optional[bool] = True,
|
|
376
382
|
# metadata
|
|
377
383
|
metadata: Optional[Dict] = {"human:": DEFAULT_HUMAN, "persona": DEFAULT_PERSONA},
|
|
@@ -425,6 +431,7 @@ class RESTClient(AbstractClient):
|
|
|
425
431
|
metadata_=metadata,
|
|
426
432
|
memory=memory,
|
|
427
433
|
tools=tool_names,
|
|
434
|
+
tool_rules=tool_rules,
|
|
428
435
|
system=system,
|
|
429
436
|
agent_type=agent_type,
|
|
430
437
|
llm_config=llm_config if llm_config else self._default_llm_config,
|
|
@@ -477,6 +484,7 @@ class RESTClient(AbstractClient):
|
|
|
477
484
|
description: Optional[str] = None,
|
|
478
485
|
system: Optional[str] = None,
|
|
479
486
|
tools: Optional[List[str]] = None,
|
|
487
|
+
tags: Optional[List[str]] = None,
|
|
480
488
|
metadata: Optional[Dict] = None,
|
|
481
489
|
llm_config: Optional[LLMConfig] = None,
|
|
482
490
|
embedding_config: Optional[EmbeddingConfig] = None,
|
|
@@ -506,6 +514,7 @@ class RESTClient(AbstractClient):
|
|
|
506
514
|
name=name,
|
|
507
515
|
system=system,
|
|
508
516
|
tools=tools,
|
|
517
|
+
tags=tags,
|
|
509
518
|
description=description,
|
|
510
519
|
metadata_=metadata,
|
|
511
520
|
llm_config=llm_config,
|
|
@@ -1614,13 +1623,10 @@ class LocalClient(AbstractClient):
|
|
|
1614
1623
|
self.organization = self.server.get_organization_or_default(self.org_id)
|
|
1615
1624
|
|
|
1616
1625
|
# agents
|
|
1617
|
-
def list_agents(self) -> List[AgentState]:
|
|
1626
|
+
def list_agents(self, tags: Optional[List[str]] = None) -> List[AgentState]:
|
|
1618
1627
|
self.interface.clear()
|
|
1619
1628
|
|
|
1620
|
-
|
|
1621
|
-
# return self.server.list_agents(user_id=self.user_id)
|
|
1622
|
-
|
|
1623
|
-
return self.server.ms.list_agents(user_id=self.user_id)
|
|
1629
|
+
return self.server.list_agents(user_id=self.user_id, tags=tags)
|
|
1624
1630
|
|
|
1625
1631
|
def agent_exists(self, agent_id: Optional[str] = None, agent_name: Optional[str] = None) -> bool:
|
|
1626
1632
|
"""
|
|
@@ -1754,6 +1760,7 @@ class LocalClient(AbstractClient):
|
|
|
1754
1760
|
description: Optional[str] = None,
|
|
1755
1761
|
system: Optional[str] = None,
|
|
1756
1762
|
tools: Optional[List[str]] = None,
|
|
1763
|
+
tags: Optional[List[str]] = None,
|
|
1757
1764
|
metadata: Optional[Dict] = None,
|
|
1758
1765
|
llm_config: Optional[LLMConfig] = None,
|
|
1759
1766
|
embedding_config: Optional[EmbeddingConfig] = None,
|
|
@@ -1785,6 +1792,7 @@ class LocalClient(AbstractClient):
|
|
|
1785
1792
|
name=name,
|
|
1786
1793
|
system=system,
|
|
1787
1794
|
tools=tools,
|
|
1795
|
+
tags=tags,
|
|
1788
1796
|
description=description,
|
|
1789
1797
|
metadata_=metadata,
|
|
1790
1798
|
llm_config=llm_config,
|
|
@@ -1869,7 +1877,7 @@ class LocalClient(AbstractClient):
|
|
|
1869
1877
|
agent_state (AgentState): State of the agent
|
|
1870
1878
|
"""
|
|
1871
1879
|
self.interface.clear()
|
|
1872
|
-
return self.server.
|
|
1880
|
+
return self.server.get_agent_state(agent_name=agent_name, user_id=self.user_id, agent_id=None)
|
|
1873
1881
|
|
|
1874
1882
|
def get_agent(self, agent_id: str) -> AgentState:
|
|
1875
1883
|
"""
|
letta/constants.py
CHANGED
|
@@ -18,6 +18,9 @@ IN_CONTEXT_MEMORY_KEYWORD = "CORE_MEMORY"
|
|
|
18
18
|
# OpenAI error message: Invalid 'messages[1].tool_calls[0].id': string too long. Expected a string with maximum length 29, but got a string with length 36 instead.
|
|
19
19
|
TOOL_CALL_ID_MAX_LEN = 29
|
|
20
20
|
|
|
21
|
+
# minimum context window size
|
|
22
|
+
MIN_CONTEXT_WINDOW = 4000
|
|
23
|
+
|
|
21
24
|
# embeddings
|
|
22
25
|
MAX_EMBEDDING_DIM = 4096 # maximum supported embeding size - do NOT change or else DBs will need to be reset
|
|
23
26
|
|
letta/functions/functions.py
CHANGED
|
@@ -9,7 +9,7 @@ from letta.constants import CLI_WARNING_PREFIX
|
|
|
9
9
|
from letta.functions.schema_generator import generate_schema
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def derive_openai_json_schema(source_code: str, name: Optional[str]) -> dict:
|
|
12
|
+
def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> dict:
|
|
13
13
|
# auto-generate openai schema
|
|
14
14
|
try:
|
|
15
15
|
# Define a custom environment with necessary imports
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import warnings
|
|
2
1
|
from typing import Dict, List, Optional, Set
|
|
3
2
|
|
|
4
3
|
from pydantic import BaseModel, Field
|
|
@@ -67,7 +66,7 @@ class ToolRulesSolver(BaseModel):
|
|
|
67
66
|
if error_on_empty:
|
|
68
67
|
raise RuntimeError(message)
|
|
69
68
|
else:
|
|
70
|
-
warnings.warn(message)
|
|
69
|
+
# warnings.warn(message)
|
|
71
70
|
return []
|
|
72
71
|
|
|
73
72
|
def is_terminal_tool(self, tool_name: str) -> bool:
|
letta/log.py
CHANGED
letta/main.py
CHANGED
|
@@ -26,6 +26,9 @@ from letta.streaming_interface import AgentRefreshStreamingInterface
|
|
|
26
26
|
|
|
27
27
|
# interface = interface()
|
|
28
28
|
|
|
29
|
+
# disable composio print on exit
|
|
30
|
+
os.environ["COMPOSIO_DISABLE_VERSION_CHECK"] = "true"
|
|
31
|
+
|
|
29
32
|
app = typer.Typer(pretty_exceptions_enable=False)
|
|
30
33
|
app.command(name="run")(run)
|
|
31
34
|
app.command(name="version")(version)
|
letta/metadata.py
CHANGED
|
@@ -493,6 +493,7 @@ class MetadataStore:
|
|
|
493
493
|
fields = vars(agent)
|
|
494
494
|
fields["memory"] = agent.memory.to_dict()
|
|
495
495
|
del fields["_internal_memory"]
|
|
496
|
+
del fields["tags"]
|
|
496
497
|
session.add(AgentModel(**fields))
|
|
497
498
|
session.commit()
|
|
498
499
|
|
|
@@ -531,6 +532,7 @@ class MetadataStore:
|
|
|
531
532
|
if isinstance(agent.memory, Memory): # TODO: this is nasty but this whole class will soon be removed so whatever
|
|
532
533
|
fields["memory"] = agent.memory.to_dict()
|
|
533
534
|
del fields["_internal_memory"]
|
|
535
|
+
del fields["tags"]
|
|
534
536
|
session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
|
|
535
537
|
session.commit()
|
|
536
538
|
|
letta/orm/agents_tags.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import ForeignKey, String, UniqueConstraint
|
|
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.agents_tags import AgentsTags as PydanticAgentsTags
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from letta.orm.organization import Organization
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentsTags(SqlalchemyBase, OrganizationMixin):
|
|
15
|
+
"""Associates tags with agents, allowing agents to have multiple tags and supporting tag-based filtering."""
|
|
16
|
+
|
|
17
|
+
__tablename__ = "agents_tags"
|
|
18
|
+
__pydantic_model__ = PydanticAgentsTags
|
|
19
|
+
__table_args__ = (UniqueConstraint("agent_id", "tag", name="unique_agent_tag"),)
|
|
20
|
+
|
|
21
|
+
# The agent associated with this tag
|
|
22
|
+
agent_id = mapped_column(String, ForeignKey("agents.id"), primary_key=True)
|
|
23
|
+
|
|
24
|
+
# The name of the tag
|
|
25
|
+
tag: Mapped[str] = mapped_column(String, nullable=False, doc="The name of the tag associated with the agent.")
|
|
26
|
+
|
|
27
|
+
# relationships
|
|
28
|
+
organization: Mapped["Organization"] = relationship("Organization", back_populates="agents_tags")
|
letta/orm/organization.py
CHANGED
|
@@ -23,6 +23,7 @@ class Organization(SqlalchemyBase):
|
|
|
23
23
|
|
|
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
|
+
agents_tags: Mapped[List["AgentsTags"]] = relationship("AgentsTags", back_populates="organization", cascade="all, delete-orphan")
|
|
26
27
|
|
|
27
28
|
# TODO: Map these relationships later when we actually make these models
|
|
28
29
|
# below is just a suggestion
|
letta/orm/sqlalchemy_base.py
CHANGED
|
@@ -112,6 +112,22 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
112
112
|
self.is_deleted = True
|
|
113
113
|
return self.update(db_session)
|
|
114
114
|
|
|
115
|
+
def hard_delete(self, db_session: "Session", actor: Optional["User"] = None) -> None:
|
|
116
|
+
"""Permanently removes the record from the database."""
|
|
117
|
+
if actor:
|
|
118
|
+
logger.info(f"User {actor.id} requested hard deletion of {self.__class__.__name__} with ID {self.id}")
|
|
119
|
+
|
|
120
|
+
with db_session as session:
|
|
121
|
+
try:
|
|
122
|
+
session.delete(self)
|
|
123
|
+
session.commit()
|
|
124
|
+
except Exception as e:
|
|
125
|
+
session.rollback()
|
|
126
|
+
logger.exception(f"Failed to hard delete {self.__class__.__name__} with ID {self.id}")
|
|
127
|
+
raise ValueError(f"Failed to hard delete {self.__class__.__name__} with ID {self.id}: {e}")
|
|
128
|
+
else:
|
|
129
|
+
logger.info(f"{self.__class__.__name__} with ID {self.id} successfully hard deleted")
|
|
130
|
+
|
|
115
131
|
def update(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
|
|
116
132
|
if actor:
|
|
117
133
|
self._set_created_and_updated_by_fields(actor.id)
|
letta/schemas/agent.py
CHANGED
|
@@ -64,6 +64,9 @@ class AgentState(BaseAgent, validate_assignment=True):
|
|
|
64
64
|
# tool rules
|
|
65
65
|
tool_rules: Optional[List[BaseToolRule]] = Field(default=None, description="The list of tool rules.")
|
|
66
66
|
|
|
67
|
+
# tags
|
|
68
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
69
|
+
|
|
67
70
|
# system prompt
|
|
68
71
|
system: str = Field(..., description="The system prompt used by the agent.")
|
|
69
72
|
|
|
@@ -108,6 +111,7 @@ class CreateAgent(BaseAgent):
|
|
|
108
111
|
memory: Optional[Memory] = Field(None, description="The in-context memory of the agent.")
|
|
109
112
|
tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
|
|
110
113
|
tool_rules: Optional[List[BaseToolRule]] = Field(None, description="The tool rules governing the agent.")
|
|
114
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
111
115
|
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
|
112
116
|
agent_type: Optional[AgentType] = Field(None, description="The type of agent.")
|
|
113
117
|
llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
|
|
@@ -148,6 +152,7 @@ class UpdateAgentState(BaseAgent):
|
|
|
148
152
|
id: str = Field(..., description="The id of the agent.")
|
|
149
153
|
name: Optional[str] = Field(None, description="The name of the agent.")
|
|
150
154
|
tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
|
|
155
|
+
tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
|
|
151
156
|
system: Optional[str] = Field(None, description="The system prompt used by the agent.")
|
|
152
157
|
llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
|
|
153
158
|
embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the agent.")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from letta.schemas.letta_base import LettaBase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentsTagsBase(LettaBase):
|
|
10
|
+
__id_prefix__ = "agents_tags"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentsTags(AgentsTagsBase):
|
|
14
|
+
"""
|
|
15
|
+
Schema representing the relationship between tags and agents.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
agent_id (str): The ID of the associated agent.
|
|
19
|
+
tag_id (str): The ID of the associated tag.
|
|
20
|
+
tag_name (str): The name of the tag.
|
|
21
|
+
created_at (datetime): The date this relationship was created.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
id: str = AgentsTagsBase.generate_id_field()
|
|
25
|
+
agent_id: str = Field(..., description="The ID of the associated agent.")
|
|
26
|
+
tag: str = Field(..., description="The name of the tag.")
|
|
27
|
+
created_at: Optional[datetime] = Field(None, description="The creation date of the association.")
|
|
28
|
+
updated_at: Optional[datetime] = Field(None, description="The update date of the tag.")
|
|
29
|
+
is_deleted: bool = Field(False, description="Whether this tag is deleted or not.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AgentsTagsCreate(AgentsTagsBase):
|
|
33
|
+
tag: str = Field(..., description="The tag name.")
|
letta/schemas/block.py
CHANGED
|
@@ -18,7 +18,7 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
|
|
18
18
|
limit: int = Field(2000, description="Character limit of the block.")
|
|
19
19
|
|
|
20
20
|
# template data (optional)
|
|
21
|
-
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.")
|
|
21
|
+
template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
|
|
22
22
|
template: bool = Field(False, description="Whether the block is a template (e.g. saved human/persona options).")
|
|
23
23
|
|
|
24
24
|
# context window label
|
|
@@ -58,11 +58,11 @@ class Block(BaseBlock):
|
|
|
58
58
|
A Block represents a reserved section of the LLM's context window which is editable. `Block` objects contained in the `Memory` object, which is able to edit the Block values.
|
|
59
59
|
|
|
60
60
|
Parameters:
|
|
61
|
-
|
|
61
|
+
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
62
62
|
value (str): The value of the block. This is the string that is represented in the context window.
|
|
63
63
|
limit (int): The character limit of the block.
|
|
64
|
+
template_name (str): The name of the block template (if it is a template).
|
|
64
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.
|
|
65
|
-
label (str): The label of the block (e.g. 'human', 'persona'). This defines a category for the block.
|
|
66
66
|
description (str): Description of the block.
|
|
67
67
|
metadata_ (Dict): Metadata of the block.
|
|
68
68
|
user_id (str): The unique identifier of the user associated with the block.
|
letta/schemas/letta_response.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
1
4
|
from typing import List, Union
|
|
2
5
|
|
|
3
6
|
from pydantic import BaseModel, Field
|
|
@@ -34,6 +37,113 @@ class LettaResponse(BaseModel):
|
|
|
34
37
|
indent=4,
|
|
35
38
|
)
|
|
36
39
|
|
|
40
|
+
def _repr_html_(self):
|
|
41
|
+
def get_formatted_content(msg):
|
|
42
|
+
if msg.message_type == "internal_monologue":
|
|
43
|
+
return f'<div class="content"><span class="internal-monologue">{html.escape(msg.internal_monologue)}</span></div>'
|
|
44
|
+
elif msg.message_type == "function_call":
|
|
45
|
+
args = format_json(msg.function_call.arguments)
|
|
46
|
+
return f'<div class="content"><span class="function-name">{html.escape(msg.function_call.name)}</span>({args})</div>'
|
|
47
|
+
elif msg.message_type == "function_return":
|
|
48
|
+
|
|
49
|
+
return_value = format_json(msg.function_return)
|
|
50
|
+
# return f'<div class="status-line">Status: {html.escape(msg.status)}</div><div class="content">{return_value}</div>'
|
|
51
|
+
return f'<div class="content">{return_value}</div>'
|
|
52
|
+
elif msg.message_type == "user_message":
|
|
53
|
+
if is_json(msg.message):
|
|
54
|
+
return f'<div class="content">{format_json(msg.message)}</div>'
|
|
55
|
+
else:
|
|
56
|
+
return f'<div class="content">{html.escape(msg.message)}</div>'
|
|
57
|
+
elif msg.message_type in ["assistant_message", "system_message"]:
|
|
58
|
+
return f'<div class="content">{html.escape(msg.message)}</div>'
|
|
59
|
+
else:
|
|
60
|
+
return f'<div class="content">{html.escape(str(msg))}</div>'
|
|
61
|
+
|
|
62
|
+
def is_json(string):
|
|
63
|
+
try:
|
|
64
|
+
json.loads(string)
|
|
65
|
+
return True
|
|
66
|
+
except ValueError:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def format_json(json_str):
|
|
70
|
+
try:
|
|
71
|
+
parsed = json.loads(json_str)
|
|
72
|
+
formatted = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
73
|
+
formatted = formatted.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
74
|
+
formatted = formatted.replace("\n", "<br>").replace(" ", " ")
|
|
75
|
+
formatted = re.sub(r'(".*?"):', r'<span class="json-key">\1</span>:', formatted)
|
|
76
|
+
formatted = re.sub(r': (".*?")', r': <span class="json-string">\1</span>', formatted)
|
|
77
|
+
formatted = re.sub(r": (\d+)", r': <span class="json-number">\1</span>', formatted)
|
|
78
|
+
formatted = re.sub(r": (true|false)", r': <span class="json-boolean">\1</span>', formatted)
|
|
79
|
+
return formatted
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return html.escape(json_str)
|
|
82
|
+
|
|
83
|
+
html_output = """
|
|
84
|
+
<style>
|
|
85
|
+
.message-container, .usage-container {
|
|
86
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
87
|
+
max-width: 800px;
|
|
88
|
+
margin: 20px auto;
|
|
89
|
+
background-color: #1e1e1e;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
color: #d4d4d4;
|
|
93
|
+
}
|
|
94
|
+
.message, .usage-stats {
|
|
95
|
+
padding: 10px 15px;
|
|
96
|
+
border-bottom: 1px solid #3a3a3a;
|
|
97
|
+
}
|
|
98
|
+
.message:last-child, .usage-stats:last-child {
|
|
99
|
+
border-bottom: none;
|
|
100
|
+
}
|
|
101
|
+
.title {
|
|
102
|
+
font-weight: bold;
|
|
103
|
+
margin-bottom: 5px;
|
|
104
|
+
color: #ffffff;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
font-size: 0.9em;
|
|
107
|
+
}
|
|
108
|
+
.content {
|
|
109
|
+
background-color: #2d2d2d;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
padding: 5px 10px;
|
|
112
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
113
|
+
white-space: pre-wrap;
|
|
114
|
+
}
|
|
115
|
+
.json-key, .function-name, .json-boolean { color: #9cdcfe; }
|
|
116
|
+
.json-string { color: #ce9178; }
|
|
117
|
+
.json-number { color: #b5cea8; }
|
|
118
|
+
.internal-monologue { font-style: italic; }
|
|
119
|
+
</style>
|
|
120
|
+
<div class="message-container">
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
for msg in self.messages:
|
|
124
|
+
content = get_formatted_content(msg)
|
|
125
|
+
title = msg.message_type.replace("_", " ").upper()
|
|
126
|
+
html_output += f"""
|
|
127
|
+
<div class="message">
|
|
128
|
+
<div class="title">{title}</div>
|
|
129
|
+
{content}
|
|
130
|
+
</div>
|
|
131
|
+
"""
|
|
132
|
+
html_output += "</div>"
|
|
133
|
+
|
|
134
|
+
# Formatting the usage statistics
|
|
135
|
+
usage_html = json.dumps(self.usage.model_dump(), indent=2)
|
|
136
|
+
html_output += f"""
|
|
137
|
+
<div class="usage-container">
|
|
138
|
+
<div class="usage-stats">
|
|
139
|
+
<div class="title">USAGE STATISTICS</div>
|
|
140
|
+
<div class="content">{format_json(usage_html)}</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
return html_output
|
|
146
|
+
|
|
37
147
|
|
|
38
148
|
# The streaming response is either [DONE], [DONE_STEP], [DONE], an error, or a LettaMessage
|
|
39
149
|
LettaStreamingResponse = Union[LettaMessage, MessageStreamStatus, LettaUsageStatistics]
|
letta/schemas/llm_config.py
CHANGED
|
@@ -13,7 +13,7 @@ class LLMConfig(BaseModel):
|
|
|
13
13
|
model_endpoint (str): The endpoint for the model.
|
|
14
14
|
model_wrapper (str): The wrapper for the model. This is used to wrap additional text around the input/output of the model. This is useful for text-to-text completions, such as the Completions API in OpenAI.
|
|
15
15
|
context_window (int): The context window size for the model.
|
|
16
|
-
put_inner_thoughts_in_kwargs (bool): Puts
|
|
16
|
+
put_inner_thoughts_in_kwargs (bool): Puts `inner_thoughts` as a kwarg in the function call if this is set to True. This helps with function calling performance and also the generation of inner thoughts.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
# TODO: 🤮 don't default to a vendor! bug city!
|
|
@@ -67,6 +67,12 @@ class LLMConfig(BaseModel):
|
|
|
67
67
|
|
|
68
68
|
@classmethod
|
|
69
69
|
def default_config(cls, model_name: str):
|
|
70
|
+
"""
|
|
71
|
+
Convinience function to generate a default `LLMConfig` from a model name. Only some models are supported in this function.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
model_name (str): The name of the model (gpt-4, gpt-4o-mini, letta).
|
|
75
|
+
"""
|
|
70
76
|
if model_name == "gpt-4":
|
|
71
77
|
return cls(
|
|
72
78
|
model="gpt-4",
|
letta/schemas/tool.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
-
from composio import LogLevel
|
|
4
3
|
from pydantic import Field
|
|
5
4
|
|
|
6
5
|
from letta.functions.helpers import (
|
|
@@ -68,7 +67,7 @@ class ToolCreate(LettaBase):
|
|
|
68
67
|
tags: List[str] = Field([], description="Metadata tags.")
|
|
69
68
|
module: Optional[str] = Field(None, description="The source code of the function.")
|
|
70
69
|
source_code: str = Field(..., description="The source code of the function.")
|
|
71
|
-
source_type: str = Field(
|
|
70
|
+
source_type: str = Field("python", description="The source type of the function.")
|
|
72
71
|
json_schema: Optional[Dict] = Field(
|
|
73
72
|
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
|
|
74
73
|
)
|
|
@@ -86,6 +85,7 @@ class ToolCreate(LettaBase):
|
|
|
86
85
|
Returns:
|
|
87
86
|
Tool: A Letta Tool initialized with attributes derived from the Composio tool.
|
|
88
87
|
"""
|
|
88
|
+
from composio import LogLevel
|
|
89
89
|
from composio_langchain import ComposioToolSet
|
|
90
90
|
|
|
91
91
|
composio_toolset = ComposioToolSet(logging_level=LogLevel.ERROR)
|
|
@@ -216,3 +216,7 @@ class ToolUpdate(LettaBase):
|
|
|
216
216
|
json_schema: Optional[Dict] = Field(
|
|
217
217
|
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
|
|
218
218
|
)
|
|
219
|
+
|
|
220
|
+
class Config:
|
|
221
|
+
extra = "ignore" # Allows extra fields without validation errors
|
|
222
|
+
# TODO: Remove this, and clean usage of ToolUpdate everywhere else
|
letta/schemas/tool_rule.py
CHANGED
|
@@ -11,15 +11,25 @@ class BaseToolRule(LettaBase):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ToolRule(BaseToolRule):
|
|
14
|
+
"""
|
|
15
|
+
A ToolRule represents a tool that can be invoked by the agent.
|
|
16
|
+
"""
|
|
17
|
+
|
|
14
18
|
type: str = Field("ToolRule")
|
|
15
19
|
children: List[str] = Field(..., description="The children tools that can be invoked.")
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class InitToolRule(BaseToolRule):
|
|
23
|
+
"""
|
|
24
|
+
Represents the initial tool rule configuration.
|
|
25
|
+
"""
|
|
26
|
+
|
|
19
27
|
type: str = Field("InitToolRule")
|
|
20
|
-
"""Represents the initial tool rule configuration."""
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
class TerminalToolRule(BaseToolRule):
|
|
31
|
+
"""
|
|
32
|
+
Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop.
|
|
33
|
+
"""
|
|
34
|
+
|
|
24
35
|
type: str = Field("TerminalToolRule")
|
|
25
|
-
"""Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
echo "Generating OpenAPI schema..."
|
|
3
|
+
|
|
4
|
+
# check if poetry is installed
|
|
5
|
+
if ! command -v poetry &> /dev/null
|
|
6
|
+
then
|
|
7
|
+
echo "Poetry could not be found. Please install poetry to generate the OpenAPI schema."
|
|
8
|
+
exit
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
# generate OpenAPI schema
|
|
12
|
+
poetry run python -c 'from letta.server.rest_api.app import app, generate_openapi_schema; generate_openapi_schema(app);'
|