Co-authored-by: Shubham Naik <shub@memgpt.ai> Co-authored-by: Matt Zhou <mattzh1314@gmail.com> Co-authored-by: Shubham Naik <shubham.naik10@gmail.com> Co-authored-by: Caren Thomas <caren@letta.com> Co-authored-by: cpacker <packercharles@gmail.com>
124 lines
5.6 KiB
Python
124 lines
5.6 KiB
Python
import uuid
|
|
from typing import TYPE_CHECKING, List, Optional
|
|
|
|
from sqlalchemy import JSON, String, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from letta.orm.block import Block
|
|
from letta.orm.custom_columns import EmbeddingConfigColumn, LLMConfigColumn, ToolRulesColumn
|
|
from letta.orm.message import Message
|
|
from letta.orm.mixins import OrganizationMixin
|
|
from letta.orm.organization import Organization
|
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
from letta.schemas.agent import AgentState as PydanticAgentState
|
|
from letta.schemas.agent import AgentType
|
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
from letta.schemas.llm_config import LLMConfig
|
|
from letta.schemas.memory import Memory
|
|
from letta.schemas.tool_rule import ToolRule
|
|
|
|
if TYPE_CHECKING:
|
|
from letta.orm.agents_tags import AgentsTags
|
|
from letta.orm.organization import Organization
|
|
from letta.orm.source import Source
|
|
from letta.orm.tool import Tool
|
|
|
|
|
|
class Agent(SqlalchemyBase, OrganizationMixin):
|
|
__tablename__ = "agents"
|
|
__pydantic_model__ = PydanticAgentState
|
|
__table_args__ = (UniqueConstraint("organization_id", "name", name="unique_org_agent_name"),)
|
|
|
|
# agent generates its own id
|
|
# TODO: We want to migrate all the ORM models to do this, so we will need to move this to the SqlalchemyBase
|
|
# TODO: Move this in this PR? at the very end?
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: f"agent-{uuid.uuid4()}")
|
|
|
|
# Descriptor fields
|
|
agent_type: Mapped[Optional[AgentType]] = mapped_column(String, nullable=True, doc="The type of Agent")
|
|
name: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="a human-readable identifier for an agent, non-unique.")
|
|
description: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The description of the agent.")
|
|
|
|
# System prompt
|
|
system: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The system prompt used by the agent.")
|
|
|
|
# In context memory
|
|
# TODO: This should be a separate mapping table
|
|
# This is dangerously flexible with the JSON type
|
|
message_ids: Mapped[Optional[List[str]]] = mapped_column(JSON, nullable=True, doc="List of message IDs in in-context memory.")
|
|
|
|
# Metadata and configs
|
|
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, doc="metadata for the agent.")
|
|
llm_config: Mapped[Optional[LLMConfig]] = mapped_column(
|
|
LLMConfigColumn, nullable=True, doc="the LLM backend configuration object for this agent."
|
|
)
|
|
embedding_config: Mapped[Optional[EmbeddingConfig]] = mapped_column(
|
|
EmbeddingConfigColumn, doc="the embedding configuration object for this agent."
|
|
)
|
|
|
|
# Tool rules
|
|
tool_rules: Mapped[Optional[List[ToolRule]]] = mapped_column(ToolRulesColumn, doc="the tool rules for this agent.")
|
|
|
|
# relationships
|
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="agents")
|
|
tools: Mapped[List["Tool"]] = relationship("Tool", secondary="tools_agents", lazy="selectin", passive_deletes=True)
|
|
sources: Mapped[List["Source"]] = relationship("Source", secondary="sources_agents", lazy="selectin")
|
|
core_memory: Mapped[List["Block"]] = relationship("Block", secondary="blocks_agents", lazy="selectin")
|
|
messages: Mapped[List["Message"]] = relationship(
|
|
"Message",
|
|
back_populates="agent",
|
|
lazy="selectin",
|
|
cascade="all, delete-orphan", # Ensure messages are deleted when the agent is deleted
|
|
passive_deletes=True,
|
|
)
|
|
tags: Mapped[List["AgentsTags"]] = relationship(
|
|
"AgentsTags",
|
|
back_populates="agent",
|
|
cascade="all, delete-orphan",
|
|
lazy="selectin",
|
|
doc="Tags associated with the agent.",
|
|
)
|
|
source_passages: Mapped[List["SourcePassage"]] = relationship(
|
|
"SourcePassage",
|
|
secondary="sources_agents", # The join table for Agent -> Source
|
|
primaryjoin="Agent.id == sources_agents.c.agent_id",
|
|
secondaryjoin="and_(SourcePassage.source_id == sources_agents.c.source_id)",
|
|
lazy="selectin",
|
|
order_by="SourcePassage.created_at.desc()",
|
|
viewonly=True, # Ensures SQLAlchemy doesn't attempt to manage this relationship
|
|
doc="All passages derived from sources associated with this agent.",
|
|
)
|
|
agent_passages: Mapped[List["AgentPassage"]] = relationship(
|
|
"AgentPassage",
|
|
back_populates="agent",
|
|
lazy="selectin",
|
|
order_by="AgentPassage.created_at.desc()",
|
|
cascade="all, delete-orphan",
|
|
viewonly=True, # Ensures SQLAlchemy doesn't attempt to manage this relationship
|
|
doc="All passages derived created by this agent.",
|
|
)
|
|
|
|
def to_pydantic(self) -> PydanticAgentState:
|
|
"""converts to the basic pydantic model counterpart"""
|
|
state = {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"message_ids": self.message_ids,
|
|
"tools": self.tools,
|
|
"sources": self.sources,
|
|
"tags": [t.tag for t in self.tags],
|
|
"tool_rules": self.tool_rules,
|
|
"system": self.system,
|
|
"agent_type": self.agent_type,
|
|
"llm_config": self.llm_config,
|
|
"embedding_config": self.embedding_config,
|
|
"metadata_": self.metadata_,
|
|
"memory": Memory(blocks=[b.to_pydantic() for b in self.core_memory]),
|
|
"created_by_id": self.created_by_id,
|
|
"last_updated_by_id": self.last_updated_by_id,
|
|
"created_at": self.created_at,
|
|
"updated_at": self.updated_at,
|
|
}
|
|
return self.__pydantic_model__(**state)
|