letta-nightly 0.5.1.dev20241105104128__py3-none-any.whl → 0.5.2.dev20241107104040__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.

Files changed (43) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +14 -4
  3. letta/agent_store/db.py +22 -20
  4. letta/cli/cli.py +14 -1
  5. letta/client/client.py +27 -14
  6. letta/constants.py +3 -0
  7. letta/functions/functions.py +1 -1
  8. letta/helpers/tool_rule_solver.py +1 -2
  9. letta/log.py +1 -1
  10. letta/main.py +3 -0
  11. letta/metadata.py +2 -0
  12. letta/orm/agents_tags.py +28 -0
  13. letta/orm/base.py +5 -2
  14. letta/orm/mixins.py +2 -53
  15. letta/orm/organization.py +4 -1
  16. letta/orm/sqlalchemy_base.py +22 -45
  17. letta/orm/tool.py +3 -2
  18. letta/orm/user.py +3 -1
  19. letta/schemas/agent.py +5 -0
  20. letta/schemas/agents_tags.py +33 -0
  21. letta/schemas/block.py +3 -3
  22. letta/schemas/letta_response.py +110 -0
  23. letta/schemas/llm_config.py +7 -1
  24. letta/schemas/memory.py +4 -0
  25. letta/schemas/organization.py +4 -4
  26. letta/schemas/tool.py +13 -9
  27. letta/schemas/tool_rule.py +12 -2
  28. letta/schemas/user.py +1 -1
  29. letta/server/rest_api/app.py +4 -1
  30. letta/server/rest_api/routers/v1/agents.py +7 -122
  31. letta/server/rest_api/routers/v1/organizations.py +2 -1
  32. letta/server/rest_api/routers/v1/tools.py +3 -2
  33. letta/server/rest_api/routers/v1/users.py +14 -2
  34. letta/server/server.py +75 -44
  35. letta/services/agents_tags_manager.py +64 -0
  36. letta/services/organization_manager.py +4 -4
  37. letta/services/tool_manager.py +22 -30
  38. letta/services/user_manager.py +3 -3
  39. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/METADATA +5 -2
  40. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/RECORD +43 -40
  41. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/LICENSE +0 -0
  42. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/WHEEL +0 -0
  43. {letta_nightly-0.5.1.dev20241105104128.dist-info → letta_nightly-0.5.2.dev20241107104040.dist-info}/entry_points.txt +0 -0
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
- name (str): The name of the block.
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.
@@ -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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
74
+ formatted = formatted.replace("\n", "<br>").replace(" ", "&nbsp;&nbsp;")
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]
@@ -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 '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.
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/memory.py CHANGED
@@ -106,6 +106,10 @@ class Memory(BaseModel, validate_assignment=True):
106
106
  # New format
107
107
  obj.prompt_template = state["prompt_template"]
108
108
  for key, value in state["memory"].items():
109
+ # TODO: This is migration code, please take a look at a later time to get rid of this
110
+ if "name" in value:
111
+ value["template_name"] = value["name"]
112
+ value.pop("name")
109
113
  obj.memory[key] = Block(**value)
110
114
  else:
111
115
  # Old format (pre-template)
@@ -4,16 +4,16 @@ from typing import Optional
4
4
  from pydantic import Field
5
5
 
6
6
  from letta.schemas.letta_base import LettaBase
7
- from letta.utils import get_utc_time
7
+ from letta.utils import create_random_username, get_utc_time
8
8
 
9
9
 
10
10
  class OrganizationBase(LettaBase):
11
- __id_prefix__ = "organization"
11
+ __id_prefix__ = "org"
12
12
 
13
13
 
14
14
  class Organization(OrganizationBase):
15
- id: str = Field(..., description="The id of the organization.")
16
- name: str = Field(..., description="The name of the organization.")
15
+ id: str = OrganizationBase.generate_id_field()
16
+ name: str = Field(create_random_username(), description="The name of the organization.")
17
17
  created_at: Optional[datetime] = Field(default_factory=get_utc_time, description="The creation date of the organization.")
18
18
 
19
19
 
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 (
@@ -33,21 +32,21 @@ class Tool(BaseTool):
33
32
 
34
33
  """
35
34
 
36
- id: str = Field(..., description="The id of the tool.")
35
+ id: str = BaseTool.generate_id_field()
37
36
  description: Optional[str] = Field(None, description="The description of the tool.")
38
37
  source_type: Optional[str] = Field(None, description="The type of the source code.")
39
38
  module: Optional[str] = Field(None, description="The module of the function.")
40
- organization_id: str = Field(..., description="The unique identifier of the organization associated with the tool.")
41
- name: str = Field(..., description="The name of the function.")
42
- tags: List[str] = Field(..., description="Metadata tags.")
39
+ organization_id: Optional[str] = Field(None, description="The unique identifier of the organization associated with the tool.")
40
+ name: Optional[str] = Field(None, description="The name of the function.")
41
+ tags: List[str] = Field([], description="Metadata tags.")
43
42
 
44
43
  # code
45
44
  source_code: str = Field(..., description="The source code of the function.")
46
- json_schema: Dict = Field(default_factory=dict, description="The JSON schema of the function.")
45
+ json_schema: Optional[Dict] = Field(None, description="The JSON schema of the function.")
47
46
 
48
47
  # metadata fields
49
- created_by_id: str = Field(..., description="The id of the user that made this Tool.")
50
- last_updated_by_id: str = Field(..., description="The id of the user that made this Tool.")
48
+ created_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
49
+ last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
51
50
 
52
51
  def to_dict(self):
53
52
  """
@@ -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(..., description="The source type of the function.")
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
@@ -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."""
letta/schemas/user.py CHANGED
@@ -21,7 +21,7 @@ class User(UserBase):
21
21
  created_at (datetime): The creation date of the user.
22
22
  """
23
23
 
24
- id: str = Field(..., description="The id of the user.")
24
+ id: str = UserBase.generate_id_field()
25
25
  organization_id: Optional[str] = Field(OrganizationManager.DEFAULT_ORG_ID, description="The organization id of the user")
26
26
  name: str = Field(..., description="The name of the user.")
27
27
  created_at: Optional[datetime] = Field(default_factory=datetime.utcnow, description="The creation date of the user.")
@@ -8,6 +8,7 @@ import uvicorn
8
8
  from fastapi import FastAPI
9
9
  from starlette.middleware.cors import CORSMiddleware
10
10
 
11
+ from letta.__init__ import __version__
11
12
  from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
12
13
  from letta.schemas.letta_response import LettaResponse
13
14
  from letta.server.constants import REST_DEFAULT_PORT
@@ -66,6 +67,7 @@ def create_application() -> "FastAPI":
66
67
  """the application start routine"""
67
68
  # global server
68
69
  # server = SyncServer(default_interface_factory=lambda: interface())
70
+ print(f"\n[[ Letta server // v{__version__} ]]")
69
71
 
70
72
  app = FastAPI(
71
73
  swagger_ui_parameters={"docExpansion": "none"},
@@ -78,6 +80,7 @@ def create_application() -> "FastAPI":
78
80
 
79
81
  if "--ade" in sys.argv:
80
82
  settings.cors_origins.append("https://app.letta.com")
83
+ print(f"▶ View using ADE at: https://app.letta.com/local-project/agents")
81
84
 
82
85
  app.add_middleware(
83
86
  CORSMiddleware,
@@ -179,7 +182,7 @@ def start_server(
179
182
  # Add the handler to the logger
180
183
  server_logger.addHandler(stream_handler)
181
184
 
182
- print(f"Running: uvicorn server:app --host {host or 'localhost'} --port {port or REST_DEFAULT_PORT}")
185
+ print(f" Server running at: http://{host or 'localhost'}:{port or REST_DEFAULT_PORT}\n")
183
186
  uvicorn.run(
184
187
  app,
185
188
  host=host or "localhost",
@@ -40,6 +40,8 @@ router = APIRouter(prefix="/agents", tags=["agents"])
40
40
 
41
41
  @router.get("/", response_model=List[AgentState], operation_id="list_agents")
42
42
  def list_agents(
43
+ name: Optional[str] = Query(None, description="Name of the agent"),
44
+ tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
43
45
  server: "SyncServer" = Depends(get_letta_server),
44
46
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
45
47
  ):
@@ -49,7 +51,11 @@ def list_agents(
49
51
  """
50
52
  actor = server.get_user_or_default(user_id=user_id)
51
53
 
52
- return server.list_agents(user_id=actor.id)
54
+ agents = server.list_agents(user_id=actor.id, tags=tags)
55
+ # TODO: move this logic to the ORM
56
+ if name:
57
+ agents = [a for a in agents if a.name == name]
58
+ return agents
53
59
 
54
60
 
55
61
  @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="get_agent_context_window")
@@ -529,124 +535,3 @@ async def send_message_to_agent(
529
535
 
530
536
  traceback.print_exc()
531
537
  raise HTTPException(status_code=500, detail=f"{e}")
532
-
533
-
534
- ##### MISSING #######
535
-
536
- # @router.post("/{agent_id}/command")
537
- # def run_command(
538
- # agent_id: "UUID",
539
- # command: "AgentCommandRequest",
540
- #
541
- # server: "SyncServer" = Depends(get_letta_server),
542
- # ):
543
- # """
544
- # Execute a command on a specified agent.
545
-
546
- # This endpoint receives a command to be executed on an agent. It uses the user and agent identifiers to authenticate and route the command appropriately.
547
-
548
- # Raises an HTTPException for any processing errors.
549
- # """
550
- # actor = server.get_current_user()
551
- #
552
- # response = server.run_command(user_id=actor.id,
553
- # agent_id=agent_id,
554
- # command=command.command)
555
-
556
- # return AgentCommandResponse(response=response)
557
-
558
- # @router.get("/{agent_id}/config")
559
- # def get_agent_config(
560
- # agent_id: "UUID",
561
- #
562
- # server: "SyncServer" = Depends(get_letta_server),
563
- # ):
564
- # """
565
- # Retrieve the configuration for a specific agent.
566
-
567
- # This endpoint fetches the configuration details for a given agent, identified by the user and agent IDs.
568
- # """
569
- # actor = server.get_current_user()
570
- #
571
- # if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
572
- ## agent does not exist
573
- # raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
574
-
575
- # agent_state = server.get_agent_config(user_id=actor.id, agent_id=agent_id)
576
- ## get sources
577
- # attached_sources = server.list_attached_sources(agent_id=agent_id)
578
-
579
- ## configs
580
- # llm_config = LLMConfig(**vars(agent_state.llm_config))
581
- # embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
582
-
583
- # return GetAgentResponse(
584
- # agent_state=AgentState(
585
- # id=agent_state.id,
586
- # name=agent_state.name,
587
- # user_id=agent_state.user_id,
588
- # llm_config=llm_config,
589
- # embedding_config=embedding_config,
590
- # state=agent_state.state,
591
- # created_at=int(agent_state.created_at.timestamp()),
592
- # tools=agent_state.tools,
593
- # system=agent_state.system,
594
- # metadata=agent_state._metadata,
595
- # ),
596
- # last_run_at=None, # TODO
597
- # sources=attached_sources,
598
- # )
599
-
600
- # @router.patch("/{agent_id}/rename", response_model=GetAgentResponse)
601
- # def update_agent_name(
602
- # agent_id: "UUID",
603
- # agent_rename: AgentRenameRequest,
604
- #
605
- # server: "SyncServer" = Depends(get_letta_server),
606
- # ):
607
- # """
608
- # Updates the name of a specific agent.
609
-
610
- # This changes the name of the agent in the database but does NOT edit the agent's persona.
611
- # """
612
- # valid_name = agent_rename.agent_name
613
- # actor = server.get_current_user()
614
- #
615
- # agent_state = server.rename_agent(user_id=actor.id, agent_id=agent_id, new_agent_name=valid_name)
616
- ## get sources
617
- # attached_sources = server.list_attached_sources(agent_id=agent_id)
618
- # llm_config = LLMConfig(**vars(agent_state.llm_config))
619
- # embedding_config = EmbeddingConfig(**vars(agent_state.embedding_config))
620
-
621
- # return GetAgentResponse(
622
- # agent_state=AgentState(
623
- # id=agent_state.id,
624
- # name=agent_state.name,
625
- # user_id=agent_state.user_id,
626
- # llm_config=llm_config,
627
- # embedding_config=embedding_config,
628
- # state=agent_state.state,
629
- # created_at=int(agent_state.created_at.timestamp()),
630
- # tools=agent_state.tools,
631
- # system=agent_state.system,
632
- # ),
633
- # last_run_at=None, # TODO
634
- # sources=attached_sources,
635
- # )
636
-
637
-
638
- # @router.get("/{agent_id}/archival/all", response_model=GetAgentArchivalMemoryResponse)
639
- # def get_agent_archival_memory_all(
640
- # agent_id: "UUID",
641
- #
642
- # server: "SyncServer" = Depends(get_letta_server),
643
- # ):
644
- # """
645
- # Retrieve the memories in an agent's archival memory store (non-paginated, returns all entries at once).
646
- # """
647
- # actor = server.get_current_user()
648
- #
649
- # archival_memories = server.get_all_archival_memories(user_id=actor.id, agent_id=agent_id)
650
- # print("archival_memories:", archival_memories)
651
- # archival_memory_objects = [ArchivalMemoryObject(id=passage["id"], contents=passage["contents"]) for passage in archival_memories]
652
- # return GetAgentArchivalMemoryResponse(archival_memory=archival_memory_objects)
@@ -38,7 +38,8 @@ def create_org(
38
38
  """
39
39
  Create a new org in the database
40
40
  """
41
- org = server.organization_manager.create_organization(name=request.name)
41
+ org = Organization(**request.model_dump())
42
+ org = server.organization_manager.create_organization(pydantic_org=org)
42
43
  return org
43
44
 
44
45
 
@@ -89,7 +89,8 @@ def create_tool(
89
89
  actor = server.get_user_or_default(user_id=user_id)
90
90
 
91
91
  # Send request to create the tool
92
- return server.tool_manager.create_or_update_tool(tool_create=request, actor=actor)
92
+ tool = Tool(**request.model_dump())
93
+ return server.tool_manager.create_or_update_tool(pydantic_tool=tool, actor=actor)
93
94
 
94
95
 
95
96
  @router.patch("/{tool_id}", response_model=Tool, operation_id="update_tool")
@@ -103,7 +104,7 @@ def update_tool(
103
104
  Update an existing tool
104
105
  """
105
106
  actor = server.get_user_or_default(user_id=user_id)
106
- return server.tool_manager.update_tool_by_id(tool_id, actor.id, request)
107
+ return server.tool_manager.update_tool_by_id(tool_id=tool_id, tool_update=request, actor=actor)
107
108
 
108
109
 
109
110
  @router.post("/add-base-tools", response_model=List[Tool], operation_id="add_base_tools")
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional
3
3
  from fastapi import APIRouter, Body, Depends, HTTPException, Query
4
4
 
5
5
  from letta.schemas.api_key import APIKey, APIKeyCreate
6
- from letta.schemas.user import User, UserCreate
6
+ from letta.schemas.user import User, UserCreate, UserUpdate
7
7
  from letta.server.rest_api.utils import get_letta_server
8
8
 
9
9
  # from letta.server.schemas.users import (
@@ -51,8 +51,20 @@ def create_user(
51
51
  """
52
52
  Create a new user in the database
53
53
  """
54
+ user = User(**request.model_dump())
55
+ user = server.user_manager.create_user(user)
56
+ return user
57
+
54
58
 
55
- user = server.user_manager.create_user(request)
59
+ @router.put("/", tags=["admin"], response_model=User, operation_id="update_user")
60
+ def update_user(
61
+ user: UserUpdate = Body(...),
62
+ server: "SyncServer" = Depends(get_letta_server),
63
+ ):
64
+ """
65
+ Update a user in the database
66
+ """
67
+ user = server.user_manager.update_user(user)
56
68
  return user
57
69
 
58
70