letta-nightly 0.1.7.dev20240924104148__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 (189) hide show
  1. letta/__init__.py +24 -0
  2. letta/__main__.py +3 -0
  3. letta/agent.py +1427 -0
  4. letta/agent_store/chroma.py +295 -0
  5. letta/agent_store/db.py +546 -0
  6. letta/agent_store/lancedb.py +177 -0
  7. letta/agent_store/milvus.py +198 -0
  8. letta/agent_store/qdrant.py +201 -0
  9. letta/agent_store/storage.py +188 -0
  10. letta/benchmark/benchmark.py +96 -0
  11. letta/benchmark/constants.py +14 -0
  12. letta/cli/cli.py +689 -0
  13. letta/cli/cli_config.py +1282 -0
  14. letta/cli/cli_load.py +166 -0
  15. letta/client/__init__.py +0 -0
  16. letta/client/admin.py +171 -0
  17. letta/client/client.py +2360 -0
  18. letta/client/streaming.py +90 -0
  19. letta/client/utils.py +61 -0
  20. letta/config.py +484 -0
  21. letta/configs/anthropic.json +13 -0
  22. letta/configs/letta_hosted.json +11 -0
  23. letta/configs/openai.json +12 -0
  24. letta/constants.py +134 -0
  25. letta/credentials.py +140 -0
  26. letta/data_sources/connectors.py +247 -0
  27. letta/embeddings.py +218 -0
  28. letta/errors.py +26 -0
  29. letta/functions/__init__.py +0 -0
  30. letta/functions/function_sets/base.py +174 -0
  31. letta/functions/function_sets/extras.py +132 -0
  32. letta/functions/functions.py +105 -0
  33. letta/functions/schema_generator.py +205 -0
  34. letta/humans/__init__.py +0 -0
  35. letta/humans/examples/basic.txt +1 -0
  36. letta/humans/examples/cs_phd.txt +9 -0
  37. letta/interface.py +314 -0
  38. letta/llm_api/__init__.py +0 -0
  39. letta/llm_api/anthropic.py +383 -0
  40. letta/llm_api/azure_openai.py +155 -0
  41. letta/llm_api/cohere.py +396 -0
  42. letta/llm_api/google_ai.py +468 -0
  43. letta/llm_api/llm_api_tools.py +485 -0
  44. letta/llm_api/openai.py +470 -0
  45. letta/local_llm/README.md +3 -0
  46. letta/local_llm/__init__.py +0 -0
  47. letta/local_llm/chat_completion_proxy.py +279 -0
  48. letta/local_llm/constants.py +31 -0
  49. letta/local_llm/function_parser.py +68 -0
  50. letta/local_llm/grammars/__init__.py +0 -0
  51. letta/local_llm/grammars/gbnf_grammar_generator.py +1324 -0
  52. letta/local_llm/grammars/json.gbnf +26 -0
  53. letta/local_llm/grammars/json_func_calls_with_inner_thoughts.gbnf +32 -0
  54. letta/local_llm/groq/api.py +97 -0
  55. letta/local_llm/json_parser.py +202 -0
  56. letta/local_llm/koboldcpp/api.py +62 -0
  57. letta/local_llm/koboldcpp/settings.py +23 -0
  58. letta/local_llm/llamacpp/api.py +58 -0
  59. letta/local_llm/llamacpp/settings.py +22 -0
  60. letta/local_llm/llm_chat_completion_wrappers/__init__.py +0 -0
  61. letta/local_llm/llm_chat_completion_wrappers/airoboros.py +452 -0
  62. letta/local_llm/llm_chat_completion_wrappers/chatml.py +470 -0
  63. letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +387 -0
  64. letta/local_llm/llm_chat_completion_wrappers/dolphin.py +246 -0
  65. letta/local_llm/llm_chat_completion_wrappers/llama3.py +345 -0
  66. letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +156 -0
  67. letta/local_llm/llm_chat_completion_wrappers/wrapper_base.py +11 -0
  68. letta/local_llm/llm_chat_completion_wrappers/zephyr.py +345 -0
  69. letta/local_llm/lmstudio/api.py +100 -0
  70. letta/local_llm/lmstudio/settings.py +29 -0
  71. letta/local_llm/ollama/api.py +88 -0
  72. letta/local_llm/ollama/settings.py +32 -0
  73. letta/local_llm/settings/__init__.py +0 -0
  74. letta/local_llm/settings/deterministic_mirostat.py +45 -0
  75. letta/local_llm/settings/settings.py +72 -0
  76. letta/local_llm/settings/simple.py +28 -0
  77. letta/local_llm/utils.py +265 -0
  78. letta/local_llm/vllm/api.py +63 -0
  79. letta/local_llm/webui/api.py +60 -0
  80. letta/local_llm/webui/legacy_api.py +58 -0
  81. letta/local_llm/webui/legacy_settings.py +23 -0
  82. letta/local_llm/webui/settings.py +24 -0
  83. letta/log.py +76 -0
  84. letta/main.py +437 -0
  85. letta/memory.py +440 -0
  86. letta/metadata.py +884 -0
  87. letta/openai_backcompat/__init__.py +0 -0
  88. letta/openai_backcompat/openai_object.py +437 -0
  89. letta/persistence_manager.py +148 -0
  90. letta/personas/__init__.py +0 -0
  91. letta/personas/examples/anna_pa.txt +13 -0
  92. letta/personas/examples/google_search_persona.txt +15 -0
  93. letta/personas/examples/memgpt_doc.txt +6 -0
  94. letta/personas/examples/memgpt_starter.txt +4 -0
  95. letta/personas/examples/sam.txt +14 -0
  96. letta/personas/examples/sam_pov.txt +14 -0
  97. letta/personas/examples/sam_simple_pov_gpt35.txt +13 -0
  98. letta/personas/examples/sqldb/test.db +0 -0
  99. letta/prompts/__init__.py +0 -0
  100. letta/prompts/gpt_summarize.py +14 -0
  101. letta/prompts/gpt_system.py +26 -0
  102. letta/prompts/system/memgpt_base.txt +49 -0
  103. letta/prompts/system/memgpt_chat.txt +58 -0
  104. letta/prompts/system/memgpt_chat_compressed.txt +13 -0
  105. letta/prompts/system/memgpt_chat_fstring.txt +51 -0
  106. letta/prompts/system/memgpt_doc.txt +50 -0
  107. letta/prompts/system/memgpt_gpt35_extralong.txt +53 -0
  108. letta/prompts/system/memgpt_intuitive_knowledge.txt +31 -0
  109. letta/prompts/system/memgpt_modified_chat.txt +23 -0
  110. letta/pytest.ini +0 -0
  111. letta/schemas/agent.py +117 -0
  112. letta/schemas/api_key.py +21 -0
  113. letta/schemas/block.py +135 -0
  114. letta/schemas/document.py +21 -0
  115. letta/schemas/embedding_config.py +54 -0
  116. letta/schemas/enums.py +35 -0
  117. letta/schemas/job.py +38 -0
  118. letta/schemas/letta_base.py +80 -0
  119. letta/schemas/letta_message.py +175 -0
  120. letta/schemas/letta_request.py +23 -0
  121. letta/schemas/letta_response.py +28 -0
  122. letta/schemas/llm_config.py +54 -0
  123. letta/schemas/memory.py +224 -0
  124. letta/schemas/message.py +727 -0
  125. letta/schemas/openai/chat_completion_request.py +123 -0
  126. letta/schemas/openai/chat_completion_response.py +136 -0
  127. letta/schemas/openai/chat_completions.py +123 -0
  128. letta/schemas/openai/embedding_response.py +11 -0
  129. letta/schemas/openai/openai.py +157 -0
  130. letta/schemas/organization.py +20 -0
  131. letta/schemas/passage.py +80 -0
  132. letta/schemas/source.py +62 -0
  133. letta/schemas/tool.py +143 -0
  134. letta/schemas/usage.py +18 -0
  135. letta/schemas/user.py +33 -0
  136. letta/server/__init__.py +0 -0
  137. letta/server/constants.py +6 -0
  138. letta/server/rest_api/__init__.py +0 -0
  139. letta/server/rest_api/admin/__init__.py +0 -0
  140. letta/server/rest_api/admin/agents.py +21 -0
  141. letta/server/rest_api/admin/tools.py +83 -0
  142. letta/server/rest_api/admin/users.py +98 -0
  143. letta/server/rest_api/app.py +193 -0
  144. letta/server/rest_api/auth/__init__.py +0 -0
  145. letta/server/rest_api/auth/index.py +43 -0
  146. letta/server/rest_api/auth_token.py +22 -0
  147. letta/server/rest_api/interface.py +726 -0
  148. letta/server/rest_api/routers/__init__.py +0 -0
  149. letta/server/rest_api/routers/openai/__init__.py +0 -0
  150. letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
  151. letta/server/rest_api/routers/openai/assistants/assistants.py +115 -0
  152. letta/server/rest_api/routers/openai/assistants/schemas.py +121 -0
  153. letta/server/rest_api/routers/openai/assistants/threads.py +336 -0
  154. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  155. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +131 -0
  156. letta/server/rest_api/routers/v1/__init__.py +15 -0
  157. letta/server/rest_api/routers/v1/agents.py +543 -0
  158. letta/server/rest_api/routers/v1/blocks.py +73 -0
  159. letta/server/rest_api/routers/v1/jobs.py +46 -0
  160. letta/server/rest_api/routers/v1/llms.py +28 -0
  161. letta/server/rest_api/routers/v1/organizations.py +61 -0
  162. letta/server/rest_api/routers/v1/sources.py +199 -0
  163. letta/server/rest_api/routers/v1/tools.py +103 -0
  164. letta/server/rest_api/routers/v1/users.py +109 -0
  165. letta/server/rest_api/static_files.py +74 -0
  166. letta/server/rest_api/utils.py +69 -0
  167. letta/server/server.py +1995 -0
  168. letta/server/startup.sh +8 -0
  169. letta/server/static_files/assets/index-0cbf7ad5.js +274 -0
  170. letta/server/static_files/assets/index-156816da.css +1 -0
  171. letta/server/static_files/assets/index-486e3228.js +274 -0
  172. letta/server/static_files/favicon.ico +0 -0
  173. letta/server/static_files/index.html +39 -0
  174. letta/server/static_files/memgpt_logo_transparent.png +0 -0
  175. letta/server/utils.py +46 -0
  176. letta/server/ws_api/__init__.py +0 -0
  177. letta/server/ws_api/example_client.py +104 -0
  178. letta/server/ws_api/interface.py +108 -0
  179. letta/server/ws_api/protocol.py +100 -0
  180. letta/server/ws_api/server.py +145 -0
  181. letta/settings.py +165 -0
  182. letta/streaming_interface.py +396 -0
  183. letta/system.py +207 -0
  184. letta/utils.py +1065 -0
  185. letta_nightly-0.1.7.dev20240924104148.dist-info/LICENSE +190 -0
  186. letta_nightly-0.1.7.dev20240924104148.dist-info/METADATA +98 -0
  187. letta_nightly-0.1.7.dev20240924104148.dist-info/RECORD +189 -0
  188. letta_nightly-0.1.7.dev20240924104148.dist-info/WHEEL +4 -0
  189. letta_nightly-0.1.7.dev20240924104148.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,62 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from fastapi import UploadFile
5
+ from pydantic import BaseModel, Field
6
+
7
+ from letta.schemas.embedding_config import EmbeddingConfig
8
+ from letta.schemas.letta_base import LettaBase
9
+ from letta.utils import get_utc_time
10
+
11
+
12
+ class BaseSource(LettaBase):
13
+ """
14
+ Shared attributes accourss all source schemas.
15
+ """
16
+
17
+ __id_prefix__ = "source"
18
+ description: Optional[str] = Field(None, description="The description of the source.")
19
+ embedding_config: Optional[EmbeddingConfig] = Field(None, description="The embedding configuration used by the passage.")
20
+ # NOTE: .metadata is a reserved attribute on SQLModel
21
+ metadata_: Optional[dict] = Field(None, description="Metadata associated with the source.")
22
+
23
+
24
+ class SourceCreate(BaseSource):
25
+ name: str = Field(..., description="The name of the source.")
26
+ description: Optional[str] = Field(None, description="The description of the source.")
27
+
28
+
29
+ class Source(BaseSource):
30
+ """
31
+ Representation of a source, which is a collection of documents and passages.
32
+
33
+ Parameters:
34
+ id (str): The ID of the source
35
+ name (str): The name of the source.
36
+ embedding_config (EmbeddingConfig): The embedding configuration used by the source.
37
+ created_at (datetime): The creation date of the source.
38
+ user_id (str): The ID of the user that created the source.
39
+ metadata_ (dict): Metadata associated with the source.
40
+ description (str): The description of the source.
41
+ """
42
+
43
+ id: str = BaseSource.generate_id_field()
44
+ name: str = Field(..., description="The name of the source.")
45
+ embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the source.")
46
+ created_at: datetime = Field(default_factory=get_utc_time, description="The creation date of the source.")
47
+ user_id: str = Field(..., description="The ID of the user that created the source.")
48
+
49
+
50
+ class SourceUpdate(BaseSource):
51
+ id: str = Field(..., description="The ID of the source.")
52
+ name: Optional[str] = Field(None, description="The name of the source.")
53
+
54
+
55
+ class UploadFileToSourceRequest(BaseModel):
56
+ file: UploadFile = Field(..., description="The file to upload.")
57
+
58
+
59
+ class UploadFileToSourceResponse(BaseModel):
60
+ source: Source = Field(..., description="The source the file was uploaded to.")
61
+ added_passages: int = Field(..., description="The number of passages added to the source.")
62
+ added_documents: int = Field(..., description="The number of documents added to the source.")
letta/schemas/tool.py ADDED
@@ -0,0 +1,143 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from pydantic import Field
4
+
5
+ from letta.functions.schema_generator import (
6
+ generate_crewai_tool_wrapper,
7
+ generate_langchain_tool_wrapper,
8
+ generate_schema_from_args_schema,
9
+ )
10
+ from letta.schemas.letta_base import LettaBase
11
+ from letta.schemas.openai.chat_completions import ToolCall
12
+
13
+
14
+ class BaseTool(LettaBase):
15
+ __id_prefix__ = "tool"
16
+
17
+ # optional fields
18
+ description: Optional[str] = Field(None, description="The description of the tool.")
19
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
20
+ module: Optional[str] = Field(None, description="The module of the function.")
21
+
22
+ # optional: user_id (user-specific tools)
23
+ user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the function.")
24
+
25
+
26
+ class Tool(BaseTool):
27
+ """
28
+ Representation of a tool, which is a function that can be called by the agent.
29
+
30
+ Parameters:
31
+ id (str): The unique identifier of the tool.
32
+ name (str): The name of the function.
33
+ tags (List[str]): Metadata tags.
34
+ source_code (str): The source code of the function.
35
+ json_schema (Dict): The JSON schema of the function.
36
+
37
+ """
38
+
39
+ id: str = BaseTool.generate_id_field()
40
+
41
+ name: str = Field(..., description="The name of the function.")
42
+ tags: List[str] = Field(..., description="Metadata tags.")
43
+
44
+ # code
45
+ 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.")
47
+
48
+ def to_dict(self):
49
+ """
50
+ Convert tool into OpenAI representation.
51
+ """
52
+ return vars(
53
+ ToolCall(
54
+ tool_id=self.id,
55
+ tool_call_type="function",
56
+ function=self.module,
57
+ )
58
+ )
59
+
60
+ @classmethod
61
+ def from_langchain(cls, langchain_tool) -> "Tool":
62
+ """
63
+ Class method to create an instance of Tool from a Langchain tool (must be from langchain_community.tools).
64
+
65
+ Args:
66
+ langchain_tool (LangchainTool): An instance of a crewAI BaseTool (BaseTool from crewai)
67
+
68
+ Returns:
69
+ Tool: A Letta Tool initialized with attributes derived from the provided crewAI BaseTool object.
70
+ """
71
+ description = langchain_tool.description
72
+ source_type = "python"
73
+ tags = ["langchain"]
74
+ # NOTE: langchain tools may come from different packages
75
+ wrapper_func_name, wrapper_function_str = generate_langchain_tool_wrapper(langchain_tool.__class__.__name__)
76
+ json_schema = generate_schema_from_args_schema(langchain_tool.args_schema, name=wrapper_func_name, description=description)
77
+
78
+ # append heartbeat (necessary for triggering another reasoning step after this tool call)
79
+ json_schema["parameters"]["properties"]["request_heartbeat"] = {
80
+ "type": "boolean",
81
+ "description": "Request an immediate heartbeat after function execution. Set to 'true' if you want to send a follow-up message or run a follow-up function.",
82
+ }
83
+ json_schema["parameters"]["required"].append("request_heartbeat")
84
+
85
+ return cls(
86
+ name=wrapper_func_name,
87
+ description=description,
88
+ source_type=source_type,
89
+ tags=tags,
90
+ source_code=wrapper_function_str,
91
+ json_schema=json_schema,
92
+ )
93
+
94
+ @classmethod
95
+ def from_crewai(cls, crewai_tool) -> "Tool":
96
+ """
97
+ Class method to create an instance of Tool from a crewAI BaseTool object.
98
+
99
+ Args:
100
+ crewai_tool (CrewAIBaseTool): An instance of a crewAI BaseTool (BaseTool from crewai)
101
+
102
+ Returns:
103
+ Tool: A Letta Tool initialized with attributes derived from the provided crewAI BaseTool object.
104
+ """
105
+ crewai_tool.name
106
+ description = crewai_tool.description
107
+ source_type = "python"
108
+ tags = ["crew-ai"]
109
+ wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool.__class__.__name__)
110
+ json_schema = generate_schema_from_args_schema(crewai_tool.args_schema, name=wrapper_func_name, description=description)
111
+
112
+ # append heartbeat (necessary for triggering another reasoning step after this tool call)
113
+ json_schema["parameters"]["properties"]["request_heartbeat"] = {
114
+ "type": "boolean",
115
+ "description": "Request an immediate heartbeat after function execution. Set to 'true' if you want to send a follow-up message or run a follow-up function.",
116
+ }
117
+ json_schema["parameters"]["required"].append("request_heartbeat")
118
+
119
+ return cls(
120
+ name=wrapper_func_name,
121
+ description=description,
122
+ source_type=source_type,
123
+ tags=tags,
124
+ source_code=wrapper_function_str,
125
+ json_schema=json_schema,
126
+ )
127
+
128
+
129
+ class ToolCreate(BaseTool):
130
+ name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
131
+ tags: List[str] = Field(..., description="Metadata tags.")
132
+ source_code: str = Field(..., description="The source code of the function.")
133
+ json_schema: Optional[Dict] = Field(
134
+ None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
135
+ )
136
+
137
+
138
+ class ToolUpdate(ToolCreate):
139
+ id: str = Field(..., description="The unique identifier of the tool.")
140
+ name: Optional[str] = Field(None, description="The name of the function.")
141
+ tags: Optional[List[str]] = Field(None, description="Metadata tags.")
142
+ source_code: Optional[str] = Field(None, description="The source code of the function.")
143
+ json_schema: Optional[Dict] = Field(None, description="The JSON schema of the function.")
letta/schemas/usage.py ADDED
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class LettaUsageStatistics(BaseModel):
5
+ """
6
+ Usage statistics for the agent interaction.
7
+
8
+ Attributes:
9
+ completion_tokens (int): The number of tokens generated by the agent.
10
+ prompt_tokens (int): The number of tokens in the prompt.
11
+ total_tokens (int): The total number of tokens processed by the agent.
12
+ step_count (int): The number of steps taken by the agent.
13
+ """
14
+
15
+ completion_tokens: int = Field(0, description="The number of tokens generated by the agent.")
16
+ prompt_tokens: int = Field(0, description="The number of tokens in the prompt.")
17
+ total_tokens: int = Field(0, description="The total number of tokens processed by the agent.")
18
+ step_count: int = Field(0, description="The number of steps taken by the agent.")
letta/schemas/user.py ADDED
@@ -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 UserBase(LettaBase):
10
+ __id_prefix__ = "user"
11
+
12
+
13
+ class User(UserBase):
14
+ """
15
+ Representation of a user.
16
+
17
+ Parameters:
18
+ id (str): The unique identifier of the user.
19
+ name (str): The name of the user.
20
+ created_at (datetime): The creation date of the user.
21
+ """
22
+
23
+ id: str = UserBase.generate_id_field()
24
+ org_id: Optional[str] = Field(
25
+ ..., description="The organization id of the user"
26
+ ) # TODO: dont make optional, and pass in default org ID
27
+ name: str = Field(..., description="The name of the user.")
28
+ created_at: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the user.")
29
+
30
+
31
+ class UserCreate(UserBase):
32
+ name: Optional[str] = Field(None, description="The name of the user.")
33
+ org_id: Optional[str] = Field(None, description="The organization id of the user.")
File without changes
@@ -0,0 +1,6 @@
1
+ # WebSockets
2
+ WS_DEFAULT_PORT = 8282
3
+ WS_CLIENT_TIMEOUT = 30
4
+
5
+ # REST
6
+ REST_DEFAULT_PORT = 8283
File without changes
File without changes
@@ -0,0 +1,21 @@
1
+ from typing import List
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from letta.schemas.agent import AgentState
6
+ from letta.server.rest_api.interface import QueuingInterface
7
+ from letta.server.server import SyncServer
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ def setup_agents_admin_router(server: SyncServer, interface: QueuingInterface):
13
+ @router.get("/agents", tags=["agents"], response_model=List[AgentState])
14
+ def get_all_agents():
15
+ """
16
+ Get a list of all agents in the database
17
+ """
18
+ interface.clear()
19
+ return server.list_agents()
20
+
21
+ return router
@@ -0,0 +1,83 @@
1
+ from typing import List, Literal, Optional
2
+
3
+ from fastapi import APIRouter, Body, HTTPException
4
+ from pydantic import BaseModel, Field
5
+
6
+ from letta.schemas.tool import Tool as ToolModel # TODO: modify
7
+ from letta.server.rest_api.interface import QueuingInterface
8
+ from letta.server.server import SyncServer
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ class ListToolsResponse(BaseModel):
14
+ tools: List[ToolModel] = Field(..., description="List of tools (functions).")
15
+
16
+
17
+ class CreateToolRequest(BaseModel):
18
+ json_schema: dict = Field(..., description="JSON schema of the tool.")
19
+ source_code: str = Field(..., description="The source code of the function.")
20
+ source_type: Optional[Literal["python"]] = Field(None, description="The type of the source code.")
21
+ tags: Optional[List[str]] = Field(None, description="Metadata tags.")
22
+
23
+
24
+ class CreateToolResponse(BaseModel):
25
+ tool: ToolModel = Field(..., description="Information about the newly created tool.")
26
+
27
+
28
+ def setup_tools_index_router(server: SyncServer, interface: QueuingInterface):
29
+ # get_current_user_with_server = partial(partial(get_current_user, server), password)
30
+
31
+ @router.delete("/tools/{tool_name}", tags=["tools"])
32
+ async def delete_tool(
33
+ tool_name: str,
34
+ # user_id: uuid.UUID = Depends(get_current_user_with_server), # TODO: add back when user-specific
35
+ ):
36
+ """
37
+ Delete a tool by name
38
+ """
39
+ # Clear the interface
40
+ interface.clear()
41
+ # tool = server.ms.delete_tool(user_id=user_id, tool_name=tool_name) TODO: add back when user-specific
42
+ server.ms.delete_tool(name=tool_name, user_id=None)
43
+
44
+ @router.get("/tools/{tool_name}", tags=["tools"], response_model=ToolModel)
45
+ async def get_tool(tool_name: str):
46
+ """
47
+ Get a tool by name
48
+ """
49
+ # Clear the interface
50
+ interface.clear()
51
+ # tool = server.ms.get_tool(user_id=user_id, tool_name=tool_name) TODO: add back when user-specific
52
+ tool = server.ms.get_tool(tool_name=tool_name, user_id=None)
53
+ if tool is None:
54
+ # return 404 error
55
+ raise HTTPException(status_code=404, detail=f"Tool with name {tool_name} not found.")
56
+ return tool
57
+
58
+ @router.get("/tools", tags=["tools"], response_model=ListToolsResponse)
59
+ async def list_all_tools():
60
+ """
61
+ Get a list of all tools available to agents created by a user
62
+ """
63
+ # Clear the interface
64
+ interface.clear()
65
+ # tools = server.ms.list_tools(user_id=user_id) TODO: add back when user-specific
66
+ tools = server.ms.list_tools(user_id=None)
67
+ return ListToolsResponse(tools=tools)
68
+
69
+ @router.post("/tools", tags=["tools"], response_model=ToolModel)
70
+ async def create_tool(
71
+ request: CreateToolRequest = Body(...),
72
+ ):
73
+ """
74
+ Create a new tool
75
+ """
76
+ try:
77
+ return server.create_tool(
78
+ json_schema=request.json_schema, source_code=request.source_code, source_type=request.source_type, tags=request.tags
79
+ )
80
+ except Exception as e:
81
+ raise HTTPException(status_code=500, detail=f"Failed to create tool: {e}")
82
+
83
+ return router
@@ -0,0 +1,98 @@
1
+ from typing import List, Optional
2
+
3
+ from fastapi import APIRouter, Body, HTTPException, Query
4
+
5
+ from letta.schemas.api_key import APIKey, APIKeyCreate
6
+ from letta.schemas.user import User, UserCreate
7
+ from letta.server.rest_api.interface import QueuingInterface
8
+ from letta.server.server import SyncServer
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ def setup_admin_router(server: SyncServer, interface: QueuingInterface):
14
+ @router.get("/users", tags=["admin"], response_model=List[User])
15
+ def get_all_users(cursor: Optional[str] = Query(None), limit: Optional[int] = Query(50)):
16
+ """
17
+ Get a list of all users in the database
18
+ """
19
+ try:
20
+ # TODO: make this call a server function
21
+ _, users = server.ms.get_all_users(cursor=cursor, limit=limit)
22
+ except HTTPException:
23
+ raise
24
+ except Exception as e:
25
+ raise HTTPException(status_code=500, detail=f"{e}")
26
+ return users
27
+
28
+ @router.post("/users", tags=["admin"], response_model=User)
29
+ def create_user(request: UserCreate = Body(...)):
30
+ """
31
+ Create a new user in the database
32
+ """
33
+ try:
34
+ user = server.create_user(request)
35
+ except HTTPException:
36
+ raise
37
+ except Exception as e:
38
+ raise HTTPException(status_code=500, detail=f"{e}")
39
+ return user
40
+
41
+ @router.delete("/users", tags=["admin"], response_model=User)
42
+ def delete_user(
43
+ user_id: str = Query(..., description="The user_id key to be deleted."),
44
+ ):
45
+ # TODO make a soft deletion, instead of a hard deletion
46
+ try:
47
+ user = server.ms.get_user(user_id=user_id)
48
+ if user is None:
49
+ raise HTTPException(status_code=404, detail=f"User does not exist")
50
+ server.ms.delete_user(user_id=user_id)
51
+ except HTTPException:
52
+ raise
53
+ except Exception as e:
54
+ raise HTTPException(status_code=500, detail=f"{e}")
55
+ return user
56
+
57
+ @router.post("/users/keys", tags=["admin"], response_model=APIKey)
58
+ def create_new_api_key(request: APIKeyCreate = Body(...)):
59
+ """
60
+ Create a new API key for a user
61
+ """
62
+ try:
63
+ api_key = server.create_api_key(request)
64
+ except HTTPException:
65
+ raise
66
+ except Exception as e:
67
+ raise HTTPException(status_code=500, detail=f"{e}")
68
+ return api_key
69
+
70
+ @router.get("/users/keys", tags=["admin"], response_model=List[APIKey])
71
+ def get_api_keys(
72
+ user_id: str = Query(..., description="The unique identifier of the user."),
73
+ ):
74
+ """
75
+ Get a list of all API keys for a user
76
+ """
77
+ try:
78
+ if server.ms.get_user(user_id=user_id) is None:
79
+ raise HTTPException(status_code=404, detail=f"User does not exist")
80
+ api_keys = server.ms.get_all_api_keys_for_user(user_id=user_id)
81
+ except HTTPException:
82
+ raise
83
+ except Exception as e:
84
+ raise HTTPException(status_code=500, detail=f"{e}")
85
+ return api_keys
86
+
87
+ @router.delete("/users/keys", tags=["admin"], response_model=APIKey)
88
+ def delete_api_key(
89
+ api_key: str = Query(..., description="The API key to be deleted."),
90
+ ):
91
+ try:
92
+ return server.delete_api_key(api_key)
93
+ except HTTPException:
94
+ raise
95
+ except Exception as e:
96
+ raise HTTPException(status_code=500, detail=f"{e}")
97
+
98
+ return router
@@ -0,0 +1,193 @@
1
+ import json
2
+ import logging
3
+ import secrets
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import typer
8
+ import uvicorn
9
+ from fastapi import FastAPI, Request
10
+ from fastapi.responses import JSONResponse
11
+ from starlette.middleware.cors import CORSMiddleware
12
+
13
+ from letta.server.constants import REST_DEFAULT_PORT
14
+
15
+ # NOTE(charles): these are extra routes that are not part of v1 but we still need to mount to pass tests
16
+ from letta.server.rest_api.auth.index import (
17
+ setup_auth_router, # TODO: probably remove right?
18
+ )
19
+ from letta.server.rest_api.interface import StreamingServerInterface
20
+ from letta.server.rest_api.routers.openai.assistants.assistants import (
21
+ router as openai_assistants_router,
22
+ )
23
+ from letta.server.rest_api.routers.openai.assistants.threads import (
24
+ router as openai_threads_router,
25
+ )
26
+ from letta.server.rest_api.routers.openai.chat_completions.chat_completions import (
27
+ router as openai_chat_completions_router,
28
+ )
29
+
30
+ # from letta.orm.utilities import get_db_session # TODO(ethan) reenable once we merge ORM
31
+ from letta.server.rest_api.routers.v1 import ROUTERS as v1_routes
32
+ from letta.server.rest_api.routers.v1.organizations import (
33
+ router as organizations_router,
34
+ )
35
+ from letta.server.rest_api.routers.v1.users import (
36
+ router as users_router, # TODO: decide on admin
37
+ )
38
+ from letta.server.rest_api.static_files import mount_static_files
39
+ from letta.server.server import SyncServer
40
+ from letta.settings import settings
41
+
42
+ # TODO(ethan)
43
+ # NOTE(charles): @ethan I had to add this to get the global as the bottom to work
44
+ interface: StreamingServerInterface = StreamingServerInterface
45
+ server = SyncServer(default_interface_factory=lambda: interface())
46
+
47
+ # TODO: remove
48
+ password = None
49
+ ## TODO(ethan): eventuall remove
50
+ # if password := settings.server_pass:
51
+ # # if the pass was specified in the environment, use it
52
+ # print(f"Using existing admin server password from environment.")
53
+ # else:
54
+ # # Autogenerate a password for this session and dump it to stdout
55
+ # password = secrets.token_urlsafe(16)
56
+ # #typer.secho(f"Generated admin server password for this session: {password}", fg=typer.colors.GREEN)
57
+
58
+
59
+ ADMIN_PREFIX = "/v1/admin"
60
+ API_PREFIX = "/v1"
61
+ OPENAI_API_PREFIX = "/openai"
62
+
63
+
64
+ def create_application() -> "FastAPI":
65
+ """the application start routine"""
66
+ # global server
67
+ # server = SyncServer(default_interface_factory=lambda: interface())
68
+
69
+ app = FastAPI(
70
+ swagger_ui_parameters={"docExpansion": "none"},
71
+ # openapi_tags=TAGS_METADATA,
72
+ title="Letta",
73
+ summary="Create LLM agents with long-term memory and custom tools 📚🦙",
74
+ version="1.0.0", # TODO wire this up to the version in the package
75
+ )
76
+ app.add_middleware(
77
+ CORSMiddleware,
78
+ allow_origins=settings.cors_origins,
79
+ allow_credentials=True,
80
+ allow_methods=["*"],
81
+ allow_headers=["*"],
82
+ )
83
+
84
+ @app.middleware("http")
85
+ async def set_current_user_middleware(request: Request, call_next):
86
+ user_id = request.headers.get("user_id")
87
+ if user_id:
88
+ try:
89
+ server.set_current_user(user_id)
90
+ except ValueError as e:
91
+ # Return an HTTP 401 Unauthorized response
92
+ # raise HTTPException(status_code=401, detail=str(e))
93
+ return JSONResponse(status_code=401, content={"detail": str(e)})
94
+ else:
95
+ server.set_current_user(None)
96
+ response = await call_next(request)
97
+ return response
98
+
99
+ for route in v1_routes:
100
+ app.include_router(route, prefix=API_PREFIX)
101
+ # this gives undocumented routes for "latest" and bare api calls.
102
+ # we should always tie this to the newest version of the api.
103
+ # app.include_router(route, prefix="", include_in_schema=False)
104
+ app.include_router(route, prefix="/latest", include_in_schema=False)
105
+
106
+ # NOTE: ethan these are the extra routes
107
+ # TODO(ethan) remove
108
+
109
+ # admin/users
110
+ app.include_router(users_router, prefix=ADMIN_PREFIX)
111
+ app.include_router(organizations_router, prefix=ADMIN_PREFIX)
112
+
113
+ # openai
114
+ app.include_router(openai_assistants_router, prefix=OPENAI_API_PREFIX)
115
+ app.include_router(openai_threads_router, prefix=OPENAI_API_PREFIX)
116
+ app.include_router(openai_chat_completions_router, prefix=OPENAI_API_PREFIX)
117
+
118
+ # /api/auth endpoints
119
+ app.include_router(setup_auth_router(server, interface, password), prefix=API_PREFIX)
120
+
121
+ # / static files
122
+ mount_static_files(app)
123
+
124
+ @app.on_event("startup")
125
+ def on_startup():
126
+ # load the default tools
127
+ # from letta.orm.tool import Tool
128
+
129
+ # Tool.load_default_tools(get_db_session())
130
+
131
+ # Update the OpenAPI schema
132
+ if not app.openapi_schema:
133
+ app.openapi_schema = app.openapi()
134
+
135
+ openai_docs, letta_docs = [app.openapi_schema.copy() for _ in range(2)]
136
+
137
+ openai_docs["paths"] = {k: v for k, v in openai_docs["paths"].items() if k.startswith("/openai")}
138
+ openai_docs["info"]["title"] = "OpenAI Assistants API"
139
+ letta_docs["paths"] = {k: v for k, v in letta_docs["paths"].items() if not k.startswith("/openai")}
140
+ letta_docs["info"]["title"] = "Letta API"
141
+
142
+ # Split the API docs into Letta API, and OpenAI Assistants compatible API
143
+ for name, docs in [
144
+ (
145
+ "openai",
146
+ openai_docs,
147
+ ),
148
+ (
149
+ "letta",
150
+ letta_docs,
151
+ ),
152
+ ]:
153
+ if settings.cors_origins:
154
+ docs["servers"] = [{"url": host} for host in settings.cors_origins]
155
+ Path(f"openapi_{name}.json").write_text(json.dumps(docs, indent=2))
156
+
157
+ @app.on_event("shutdown")
158
+ def on_shutdown():
159
+ global server
160
+ server.save_agents()
161
+ # server = None
162
+
163
+ return app
164
+
165
+
166
+ app = create_application()
167
+
168
+
169
+ def start_server(
170
+ port: Optional[int] = None,
171
+ host: Optional[str] = None,
172
+ debug: bool = False,
173
+ ):
174
+ """Convenience method to start the server from within Python"""
175
+ if debug:
176
+ from letta.server.server import logger as server_logger
177
+
178
+ # Set the logging level
179
+ server_logger.setLevel(logging.DEBUG)
180
+ # Create a StreamHandler
181
+ stream_handler = logging.StreamHandler()
182
+ # Set the formatter (optional)
183
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
184
+ stream_handler.setFormatter(formatter)
185
+ # Add the handler to the logger
186
+ server_logger.addHandler(stream_handler)
187
+
188
+ print(f"Running: uvicorn server:app --host {host or 'localhost'} --port {port or REST_DEFAULT_PORT}")
189
+ uvicorn.run(
190
+ app,
191
+ host=host or "localhost",
192
+ port=port or REST_DEFAULT_PORT,
193
+ )
File without changes