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.
- letta/__init__.py +24 -0
- letta/__main__.py +3 -0
- letta/agent.py +1427 -0
- letta/agent_store/chroma.py +295 -0
- letta/agent_store/db.py +546 -0
- letta/agent_store/lancedb.py +177 -0
- letta/agent_store/milvus.py +198 -0
- letta/agent_store/qdrant.py +201 -0
- letta/agent_store/storage.py +188 -0
- letta/benchmark/benchmark.py +96 -0
- letta/benchmark/constants.py +14 -0
- letta/cli/cli.py +689 -0
- letta/cli/cli_config.py +1282 -0
- letta/cli/cli_load.py +166 -0
- letta/client/__init__.py +0 -0
- letta/client/admin.py +171 -0
- letta/client/client.py +2360 -0
- letta/client/streaming.py +90 -0
- letta/client/utils.py +61 -0
- letta/config.py +484 -0
- letta/configs/anthropic.json +13 -0
- letta/configs/letta_hosted.json +11 -0
- letta/configs/openai.json +12 -0
- letta/constants.py +134 -0
- letta/credentials.py +140 -0
- letta/data_sources/connectors.py +247 -0
- letta/embeddings.py +218 -0
- letta/errors.py +26 -0
- letta/functions/__init__.py +0 -0
- letta/functions/function_sets/base.py +174 -0
- letta/functions/function_sets/extras.py +132 -0
- letta/functions/functions.py +105 -0
- letta/functions/schema_generator.py +205 -0
- letta/humans/__init__.py +0 -0
- letta/humans/examples/basic.txt +1 -0
- letta/humans/examples/cs_phd.txt +9 -0
- letta/interface.py +314 -0
- letta/llm_api/__init__.py +0 -0
- letta/llm_api/anthropic.py +383 -0
- letta/llm_api/azure_openai.py +155 -0
- letta/llm_api/cohere.py +396 -0
- letta/llm_api/google_ai.py +468 -0
- letta/llm_api/llm_api_tools.py +485 -0
- letta/llm_api/openai.py +470 -0
- letta/local_llm/README.md +3 -0
- letta/local_llm/__init__.py +0 -0
- letta/local_llm/chat_completion_proxy.py +279 -0
- letta/local_llm/constants.py +31 -0
- letta/local_llm/function_parser.py +68 -0
- letta/local_llm/grammars/__init__.py +0 -0
- letta/local_llm/grammars/gbnf_grammar_generator.py +1324 -0
- letta/local_llm/grammars/json.gbnf +26 -0
- letta/local_llm/grammars/json_func_calls_with_inner_thoughts.gbnf +32 -0
- letta/local_llm/groq/api.py +97 -0
- letta/local_llm/json_parser.py +202 -0
- letta/local_llm/koboldcpp/api.py +62 -0
- letta/local_llm/koboldcpp/settings.py +23 -0
- letta/local_llm/llamacpp/api.py +58 -0
- letta/local_llm/llamacpp/settings.py +22 -0
- letta/local_llm/llm_chat_completion_wrappers/__init__.py +0 -0
- letta/local_llm/llm_chat_completion_wrappers/airoboros.py +452 -0
- letta/local_llm/llm_chat_completion_wrappers/chatml.py +470 -0
- letta/local_llm/llm_chat_completion_wrappers/configurable_wrapper.py +387 -0
- letta/local_llm/llm_chat_completion_wrappers/dolphin.py +246 -0
- letta/local_llm/llm_chat_completion_wrappers/llama3.py +345 -0
- letta/local_llm/llm_chat_completion_wrappers/simple_summary_wrapper.py +156 -0
- letta/local_llm/llm_chat_completion_wrappers/wrapper_base.py +11 -0
- letta/local_llm/llm_chat_completion_wrappers/zephyr.py +345 -0
- letta/local_llm/lmstudio/api.py +100 -0
- letta/local_llm/lmstudio/settings.py +29 -0
- letta/local_llm/ollama/api.py +88 -0
- letta/local_llm/ollama/settings.py +32 -0
- letta/local_llm/settings/__init__.py +0 -0
- letta/local_llm/settings/deterministic_mirostat.py +45 -0
- letta/local_llm/settings/settings.py +72 -0
- letta/local_llm/settings/simple.py +28 -0
- letta/local_llm/utils.py +265 -0
- letta/local_llm/vllm/api.py +63 -0
- letta/local_llm/webui/api.py +60 -0
- letta/local_llm/webui/legacy_api.py +58 -0
- letta/local_llm/webui/legacy_settings.py +23 -0
- letta/local_llm/webui/settings.py +24 -0
- letta/log.py +76 -0
- letta/main.py +437 -0
- letta/memory.py +440 -0
- letta/metadata.py +884 -0
- letta/openai_backcompat/__init__.py +0 -0
- letta/openai_backcompat/openai_object.py +437 -0
- letta/persistence_manager.py +148 -0
- letta/personas/__init__.py +0 -0
- letta/personas/examples/anna_pa.txt +13 -0
- letta/personas/examples/google_search_persona.txt +15 -0
- letta/personas/examples/memgpt_doc.txt +6 -0
- letta/personas/examples/memgpt_starter.txt +4 -0
- letta/personas/examples/sam.txt +14 -0
- letta/personas/examples/sam_pov.txt +14 -0
- letta/personas/examples/sam_simple_pov_gpt35.txt +13 -0
- letta/personas/examples/sqldb/test.db +0 -0
- letta/prompts/__init__.py +0 -0
- letta/prompts/gpt_summarize.py +14 -0
- letta/prompts/gpt_system.py +26 -0
- letta/prompts/system/memgpt_base.txt +49 -0
- letta/prompts/system/memgpt_chat.txt +58 -0
- letta/prompts/system/memgpt_chat_compressed.txt +13 -0
- letta/prompts/system/memgpt_chat_fstring.txt +51 -0
- letta/prompts/system/memgpt_doc.txt +50 -0
- letta/prompts/system/memgpt_gpt35_extralong.txt +53 -0
- letta/prompts/system/memgpt_intuitive_knowledge.txt +31 -0
- letta/prompts/system/memgpt_modified_chat.txt +23 -0
- letta/pytest.ini +0 -0
- letta/schemas/agent.py +117 -0
- letta/schemas/api_key.py +21 -0
- letta/schemas/block.py +135 -0
- letta/schemas/document.py +21 -0
- letta/schemas/embedding_config.py +54 -0
- letta/schemas/enums.py +35 -0
- letta/schemas/job.py +38 -0
- letta/schemas/letta_base.py +80 -0
- letta/schemas/letta_message.py +175 -0
- letta/schemas/letta_request.py +23 -0
- letta/schemas/letta_response.py +28 -0
- letta/schemas/llm_config.py +54 -0
- letta/schemas/memory.py +224 -0
- letta/schemas/message.py +727 -0
- letta/schemas/openai/chat_completion_request.py +123 -0
- letta/schemas/openai/chat_completion_response.py +136 -0
- letta/schemas/openai/chat_completions.py +123 -0
- letta/schemas/openai/embedding_response.py +11 -0
- letta/schemas/openai/openai.py +157 -0
- letta/schemas/organization.py +20 -0
- letta/schemas/passage.py +80 -0
- letta/schemas/source.py +62 -0
- letta/schemas/tool.py +143 -0
- letta/schemas/usage.py +18 -0
- letta/schemas/user.py +33 -0
- letta/server/__init__.py +0 -0
- letta/server/constants.py +6 -0
- letta/server/rest_api/__init__.py +0 -0
- letta/server/rest_api/admin/__init__.py +0 -0
- letta/server/rest_api/admin/agents.py +21 -0
- letta/server/rest_api/admin/tools.py +83 -0
- letta/server/rest_api/admin/users.py +98 -0
- letta/server/rest_api/app.py +193 -0
- letta/server/rest_api/auth/__init__.py +0 -0
- letta/server/rest_api/auth/index.py +43 -0
- letta/server/rest_api/auth_token.py +22 -0
- letta/server/rest_api/interface.py +726 -0
- letta/server/rest_api/routers/__init__.py +0 -0
- letta/server/rest_api/routers/openai/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/__init__.py +0 -0
- letta/server/rest_api/routers/openai/assistants/assistants.py +115 -0
- letta/server/rest_api/routers/openai/assistants/schemas.py +121 -0
- letta/server/rest_api/routers/openai/assistants/threads.py +336 -0
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +131 -0
- letta/server/rest_api/routers/v1/__init__.py +15 -0
- letta/server/rest_api/routers/v1/agents.py +543 -0
- letta/server/rest_api/routers/v1/blocks.py +73 -0
- letta/server/rest_api/routers/v1/jobs.py +46 -0
- letta/server/rest_api/routers/v1/llms.py +28 -0
- letta/server/rest_api/routers/v1/organizations.py +61 -0
- letta/server/rest_api/routers/v1/sources.py +199 -0
- letta/server/rest_api/routers/v1/tools.py +103 -0
- letta/server/rest_api/routers/v1/users.py +109 -0
- letta/server/rest_api/static_files.py +74 -0
- letta/server/rest_api/utils.py +69 -0
- letta/server/server.py +1995 -0
- letta/server/startup.sh +8 -0
- letta/server/static_files/assets/index-0cbf7ad5.js +274 -0
- letta/server/static_files/assets/index-156816da.css +1 -0
- letta/server/static_files/assets/index-486e3228.js +274 -0
- letta/server/static_files/favicon.ico +0 -0
- letta/server/static_files/index.html +39 -0
- letta/server/static_files/memgpt_logo_transparent.png +0 -0
- letta/server/utils.py +46 -0
- letta/server/ws_api/__init__.py +0 -0
- letta/server/ws_api/example_client.py +104 -0
- letta/server/ws_api/interface.py +108 -0
- letta/server/ws_api/protocol.py +100 -0
- letta/server/ws_api/server.py +145 -0
- letta/settings.py +165 -0
- letta/streaming_interface.py +396 -0
- letta/system.py +207 -0
- letta/utils.py +1065 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/LICENSE +190 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/METADATA +98 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/RECORD +189 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/WHEEL +4 -0
- letta_nightly-0.1.7.dev20240924104148.dist-info/entry_points.txt +3 -0
letta/schemas/source.py
ADDED
|
@@ -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.")
|
letta/server/__init__.py
ADDED
|
File without changes
|
|
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
|