letta-nightly 0.11.4.dev20250825104222__py3-none-any.whl → 0.11.5__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.
- letta/__init__.py +1 -1
- letta/agent.py +9 -3
- letta/agents/base_agent.py +2 -2
- letta/agents/letta_agent.py +56 -45
- letta/agents/voice_agent.py +2 -2
- letta/data_sources/redis_client.py +146 -1
- letta/errors.py +4 -0
- letta/functions/function_sets/files.py +2 -2
- letta/functions/mcp_client/types.py +30 -6
- letta/functions/schema_generator.py +46 -1
- letta/functions/schema_validator.py +17 -2
- letta/functions/types.py +1 -1
- letta/helpers/tool_execution_helper.py +0 -2
- letta/llm_api/anthropic_client.py +27 -5
- letta/llm_api/deepseek_client.py +97 -0
- letta/llm_api/groq_client.py +79 -0
- letta/llm_api/helpers.py +0 -1
- letta/llm_api/llm_api_tools.py +2 -113
- letta/llm_api/llm_client.py +21 -0
- letta/llm_api/llm_client_base.py +11 -9
- letta/llm_api/openai_client.py +3 -0
- letta/llm_api/xai_client.py +85 -0
- letta/prompts/prompt_generator.py +190 -0
- letta/schemas/agent_file.py +17 -2
- letta/schemas/file.py +24 -1
- letta/schemas/job.py +2 -0
- letta/schemas/letta_message.py +2 -0
- letta/schemas/letta_request.py +22 -0
- letta/schemas/message.py +10 -1
- letta/schemas/providers/bedrock.py +1 -0
- letta/server/rest_api/redis_stream_manager.py +300 -0
- letta/server/rest_api/routers/v1/agents.py +129 -7
- letta/server/rest_api/routers/v1/folders.py +15 -5
- letta/server/rest_api/routers/v1/runs.py +101 -11
- letta/server/rest_api/routers/v1/sources.py +21 -53
- letta/server/rest_api/routers/v1/telemetry.py +14 -4
- letta/server/rest_api/routers/v1/tools.py +2 -2
- letta/server/rest_api/streaming_response.py +3 -24
- letta/server/server.py +0 -1
- letta/services/agent_manager.py +2 -2
- letta/services/agent_serialization_manager.py +129 -32
- letta/services/file_manager.py +111 -6
- letta/services/file_processor/file_processor.py +5 -2
- letta/services/files_agents_manager.py +60 -0
- letta/services/helpers/agent_manager_helper.py +4 -205
- letta/services/helpers/tool_parser_helper.py +6 -3
- letta/services/mcp/base_client.py +7 -1
- letta/services/mcp/sse_client.py +7 -2
- letta/services/mcp/stdio_client.py +5 -0
- letta/services/mcp/streamable_http_client.py +11 -2
- letta/services/mcp_manager.py +31 -30
- letta/services/source_manager.py +26 -1
- letta/services/summarizer/summarizer.py +21 -10
- letta/services/tool_executor/files_tool_executor.py +13 -9
- letta/services/tool_executor/mcp_tool_executor.py +3 -0
- letta/services/tool_executor/tool_execution_manager.py +13 -0
- letta/services/tool_manager.py +43 -20
- letta/settings.py +1 -0
- letta/utils.py +37 -0
- {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/METADATA +2 -2
- {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/RECORD +64 -63
- letta/functions/mcp_client/__init__.py +0 -0
- letta/functions/mcp_client/base_client.py +0 -156
- letta/functions/mcp_client/sse_client.py +0 -51
- letta/functions/mcp_client/stdio_client.py +0 -109
- {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/LICENSE +0 -0
- {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/entry_points.txt +0 -0
@@ -1,156 +0,0 @@
|
|
1
|
-
from letta.log import get_logger
|
2
|
-
|
3
|
-
logger = get_logger(__name__)
|
4
|
-
|
5
|
-
|
6
|
-
# class BaseMCPClient:
|
7
|
-
# def __init__(self, server_config: BaseServerConfig):
|
8
|
-
# self.server_config = server_config
|
9
|
-
# self.session: Optional[ClientSession] = None
|
10
|
-
# self.stdio = None
|
11
|
-
# self.write = None
|
12
|
-
# self.initialized = False
|
13
|
-
# self.loop = asyncio.new_event_loop()
|
14
|
-
# self.cleanup_funcs = []
|
15
|
-
#
|
16
|
-
# def connect_to_server(self):
|
17
|
-
# asyncio.set_event_loop(self.loop)
|
18
|
-
# success = self._initialize_connection(self.server_config, timeout=tool_settings.mcp_connect_to_server_timeout)
|
19
|
-
#
|
20
|
-
# if success:
|
21
|
-
# try:
|
22
|
-
# self.loop.run_until_complete(
|
23
|
-
# asyncio.wait_for(self.session.initialize(), timeout=tool_settings.mcp_connect_to_server_timeout)
|
24
|
-
# )
|
25
|
-
# self.initialized = True
|
26
|
-
# except asyncio.TimeoutError:
|
27
|
-
# raise MCPTimeoutError("initializing session", self.server_config.server_name, tool_settings.mcp_connect_to_server_timeout)
|
28
|
-
# else:
|
29
|
-
# raise RuntimeError(
|
30
|
-
# f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}"
|
31
|
-
# )
|
32
|
-
#
|
33
|
-
# def _initialize_connection(self, server_config: BaseServerConfig, timeout: float) -> bool:
|
34
|
-
# raise NotImplementedError("Subclasses must implement _initialize_connection")
|
35
|
-
#
|
36
|
-
# def list_tools(self) -> List[MCPTool]:
|
37
|
-
# self._check_initialized()
|
38
|
-
# try:
|
39
|
-
# response = self.loop.run_until_complete(
|
40
|
-
# asyncio.wait_for(self.session.list_tools(), timeout=tool_settings.mcp_list_tools_timeout)
|
41
|
-
# )
|
42
|
-
# return response.tools
|
43
|
-
# except asyncio.TimeoutError:
|
44
|
-
# logger.error(
|
45
|
-
# f"Timed out while listing tools for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_list_tools_timeout}s)."
|
46
|
-
# )
|
47
|
-
# raise MCPTimeoutError("listing tools", self.server_config.server_name, tool_settings.mcp_list_tools_timeout)
|
48
|
-
#
|
49
|
-
# def execute_tool(self, tool_name: str, tool_args: dict) -> Tuple[str, bool]:
|
50
|
-
# self._check_initialized()
|
51
|
-
# try:
|
52
|
-
# result = self.loop.run_until_complete(
|
53
|
-
# asyncio.wait_for(self.session.call_tool(tool_name, tool_args), timeout=tool_settings.mcp_execute_tool_timeout)
|
54
|
-
# )
|
55
|
-
#
|
56
|
-
# parsed_content = []
|
57
|
-
# for content_piece in result.content:
|
58
|
-
# if isinstance(content_piece, TextContent):
|
59
|
-
# parsed_content.append(content_piece.text)
|
60
|
-
# print("parsed_content (text)", parsed_content)
|
61
|
-
# else:
|
62
|
-
# parsed_content.append(str(content_piece))
|
63
|
-
# print("parsed_content (other)", parsed_content)
|
64
|
-
#
|
65
|
-
# if len(parsed_content) > 0:
|
66
|
-
# final_content = " ".join(parsed_content)
|
67
|
-
# else:
|
68
|
-
# # TODO move hardcoding to constants
|
69
|
-
# final_content = "Empty response from tool"
|
70
|
-
#
|
71
|
-
# return final_content, result.isError
|
72
|
-
# except asyncio.TimeoutError:
|
73
|
-
# logger.error(
|
74
|
-
# f"Timed out while executing tool '{tool_name}' for MCP server {self.server_config.server_name} (timeout={tool_settings.mcp_execute_tool_timeout}s)."
|
75
|
-
# )
|
76
|
-
# raise MCPTimeoutError(f"executing tool '{tool_name}'", self.server_config.server_name, tool_settings.mcp_execute_tool_timeout)
|
77
|
-
#
|
78
|
-
# def _check_initialized(self):
|
79
|
-
# if not self.initialized:
|
80
|
-
# logger.error("MCPClient has not been initialized")
|
81
|
-
# raise RuntimeError("MCPClient has not been initialized")
|
82
|
-
#
|
83
|
-
# def cleanup(self):
|
84
|
-
# try:
|
85
|
-
# for cleanup_func in self.cleanup_funcs:
|
86
|
-
# cleanup_func()
|
87
|
-
# self.initialized = False
|
88
|
-
# if not self.loop.is_closed():
|
89
|
-
# self.loop.close()
|
90
|
-
# except Exception as e:
|
91
|
-
# logger.warning(e)
|
92
|
-
# finally:
|
93
|
-
# logger.info("Cleaned up MCP clients on shutdown.")
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# class BaseAsyncMCPClient:
|
97
|
-
# def __init__(self, server_config: BaseServerConfig):
|
98
|
-
# self.server_config = server_config
|
99
|
-
# self.session: Optional[ClientSession] = None
|
100
|
-
# self.stdio = None
|
101
|
-
# self.write = None
|
102
|
-
# self.initialized = False
|
103
|
-
# self.cleanup_funcs = []
|
104
|
-
#
|
105
|
-
# async def connect_to_server(self):
|
106
|
-
#
|
107
|
-
# success = await self._initialize_connection(self.server_config, timeout=tool_settings.mcp_connect_to_server_timeout)
|
108
|
-
#
|
109
|
-
# if success:
|
110
|
-
# self.initialized = True
|
111
|
-
# else:
|
112
|
-
# raise RuntimeError(
|
113
|
-
# f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}"
|
114
|
-
# )
|
115
|
-
#
|
116
|
-
# async def list_tools(self) -> List[MCPTool]:
|
117
|
-
# self._check_initialized()
|
118
|
-
# response = await self.session.list_tools()
|
119
|
-
# return response.tools
|
120
|
-
#
|
121
|
-
# async def execute_tool(self, tool_name: str, tool_args: dict) -> Tuple[str, bool]:
|
122
|
-
# self._check_initialized()
|
123
|
-
# result = await self.session.call_tool(tool_name, tool_args)
|
124
|
-
#
|
125
|
-
# parsed_content = []
|
126
|
-
# for content_piece in result.content:
|
127
|
-
# if isinstance(content_piece, TextContent):
|
128
|
-
# parsed_content.append(content_piece.text)
|
129
|
-
# else:
|
130
|
-
# parsed_content.append(str(content_piece))
|
131
|
-
#
|
132
|
-
# if len(parsed_content) > 0:
|
133
|
-
# final_content = " ".join(parsed_content)
|
134
|
-
# else:
|
135
|
-
# # TODO move hardcoding to constants
|
136
|
-
# final_content = "Empty response from tool"
|
137
|
-
#
|
138
|
-
# return final_content, result.isError
|
139
|
-
#
|
140
|
-
# def _check_initialized(self):
|
141
|
-
# if not self.initialized:
|
142
|
-
# logger.error("MCPClient has not been initialized")
|
143
|
-
# raise RuntimeError("MCPClient has not been initialized")
|
144
|
-
#
|
145
|
-
# async def cleanup(self):
|
146
|
-
# try:
|
147
|
-
# for cleanup_func in self.cleanup_funcs:
|
148
|
-
# cleanup_func()
|
149
|
-
# self.initialized = False
|
150
|
-
# if not self.loop.is_closed():
|
151
|
-
# self.loop.close()
|
152
|
-
# except Exception as e:
|
153
|
-
# logger.warning(e)
|
154
|
-
# finally:
|
155
|
-
# logger.info("Cleaned up MCP clients on shutdown.")
|
156
|
-
#
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# import asyncio
|
2
|
-
#
|
3
|
-
# from mcp import ClientSession
|
4
|
-
# from mcp.client.sse import sse_client
|
5
|
-
#
|
6
|
-
# from letta.functions.mcp_client.base_client import BaseAsyncMCPClient, BaseMCPClient
|
7
|
-
# from letta.functions.mcp_client.types import SSEServerConfig
|
8
|
-
# from letta.log import get_logger
|
9
|
-
#
|
10
|
-
## see: https://modelcontextprotocol.io/quickstart/user
|
11
|
-
#
|
12
|
-
# logger = get_logger(__name__)
|
13
|
-
|
14
|
-
|
15
|
-
# class SSEMCPClient(BaseMCPClient):
|
16
|
-
# def _initialize_connection(self, server_config: SSEServerConfig, timeout: float) -> bool:
|
17
|
-
# try:
|
18
|
-
# sse_cm = sse_client(url=server_config.server_url)
|
19
|
-
# sse_transport = self.loop.run_until_complete(asyncio.wait_for(sse_cm.__aenter__(), timeout=timeout))
|
20
|
-
# self.stdio, self.write = sse_transport
|
21
|
-
# self.cleanup_funcs.append(lambda: self.loop.run_until_complete(sse_cm.__aexit__(None, None, None)))
|
22
|
-
#
|
23
|
-
# session_cm = ClientSession(self.stdio, self.write)
|
24
|
-
# self.session = self.loop.run_until_complete(asyncio.wait_for(session_cm.__aenter__(), timeout=timeout))
|
25
|
-
# self.cleanup_funcs.append(lambda: self.loop.run_until_complete(session_cm.__aexit__(None, None, None)))
|
26
|
-
# return True
|
27
|
-
# except asyncio.TimeoutError:
|
28
|
-
# logger.error(f"Timed out while establishing SSE connection (timeout={timeout}s).")
|
29
|
-
# return False
|
30
|
-
# except Exception:
|
31
|
-
# logger.exception("Exception occurred while initializing SSE client session.")
|
32
|
-
# return False
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# class AsyncSSEMCPClient(BaseAsyncMCPClient):
|
36
|
-
#
|
37
|
-
# async def _initialize_connection(self, server_config: SSEServerConfig, timeout: float) -> bool:
|
38
|
-
# try:
|
39
|
-
# sse_cm = sse_client(url=server_config.server_url)
|
40
|
-
# sse_transport = await sse_cm.__aenter__()
|
41
|
-
# self.stdio, self.write = sse_transport
|
42
|
-
# self.cleanup_funcs.append(lambda: sse_cm.__aexit__(None, None, None))
|
43
|
-
#
|
44
|
-
# session_cm = ClientSession(self.stdio, self.write)
|
45
|
-
# self.session = await session_cm.__aenter__()
|
46
|
-
# self.cleanup_funcs.append(lambda: session_cm.__aexit__(None, None, None))
|
47
|
-
# return True
|
48
|
-
# except Exception:
|
49
|
-
# logger.exception("Exception occurred while initializing SSE client session.")
|
50
|
-
# return False
|
51
|
-
#
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# import asyncio
|
2
|
-
# import sys
|
3
|
-
# from contextlib import asynccontextmanager
|
4
|
-
#
|
5
|
-
# import anyio
|
6
|
-
# import anyio.lowlevel
|
7
|
-
# import mcp.types as types
|
8
|
-
# from anyio.streams.text import TextReceiveStream
|
9
|
-
# from mcp import ClientSession, StdioServerParameters
|
10
|
-
# from mcp.client.stdio import get_default_environment
|
11
|
-
#
|
12
|
-
# from letta.functions.mcp_client.base_client import BaseMCPClient
|
13
|
-
# from letta.functions.mcp_client.types import StdioServerConfig
|
14
|
-
# from letta.log import get_logger
|
15
|
-
#
|
16
|
-
# logger = get_logger(__name__)
|
17
|
-
|
18
|
-
|
19
|
-
# class StdioMCPClient(BaseMCPClient):
|
20
|
-
# def _initialize_connection(self, server_config: StdioServerConfig, timeout: float) -> bool:
|
21
|
-
# try:
|
22
|
-
# server_params = StdioServerParameters(command=server_config.command, args=server_config.args, env=server_config.env)
|
23
|
-
# stdio_cm = forked_stdio_client(server_params)
|
24
|
-
# stdio_transport = self.loop.run_until_complete(asyncio.wait_for(stdio_cm.__aenter__(), timeout=timeout))
|
25
|
-
# self.stdio, self.write = stdio_transport
|
26
|
-
# self.cleanup_funcs.append(lambda: self.loop.run_until_complete(stdio_cm.__aexit__(None, None, None)))
|
27
|
-
#
|
28
|
-
# session_cm = ClientSession(self.stdio, self.write)
|
29
|
-
# self.session = self.loop.run_until_complete(asyncio.wait_for(session_cm.__aenter__(), timeout=timeout))
|
30
|
-
# self.cleanup_funcs.append(lambda: self.loop.run_until_complete(session_cm.__aexit__(None, None, None)))
|
31
|
-
# return True
|
32
|
-
# except asyncio.TimeoutError:
|
33
|
-
# logger.error(f"Timed out while establishing stdio connection (timeout={timeout}s).")
|
34
|
-
# return False
|
35
|
-
# except Exception:
|
36
|
-
# logger.exception("Exception occurred while initializing stdio client session.")
|
37
|
-
# return False
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# @asynccontextmanager
|
41
|
-
# async def forked_stdio_client(server: StdioServerParameters):
|
42
|
-
# """
|
43
|
-
# Client transport for stdio: this will connect to a server by spawning a
|
44
|
-
# process and communicating with it over stdin/stdout.
|
45
|
-
# """
|
46
|
-
# read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
47
|
-
# write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
48
|
-
#
|
49
|
-
# try:
|
50
|
-
# process = await anyio.open_process(
|
51
|
-
# [server.command, *server.args],
|
52
|
-
# env=server.env or get_default_environment(),
|
53
|
-
# stderr=sys.stderr, # Consider logging stderr somewhere instead of silencing it
|
54
|
-
# )
|
55
|
-
# except OSError as exc:
|
56
|
-
# raise RuntimeError(f"Failed to spawn process: {server.command} {server.args}") from exc
|
57
|
-
#
|
58
|
-
# async def stdout_reader():
|
59
|
-
# assert process.stdout, "Opened process is missing stdout"
|
60
|
-
# buffer = ""
|
61
|
-
# try:
|
62
|
-
# async with read_stream_writer:
|
63
|
-
# async for chunk in TextReceiveStream(
|
64
|
-
# process.stdout,
|
65
|
-
# encoding=server.encoding,
|
66
|
-
# errors=server.encoding_error_handler,
|
67
|
-
# ):
|
68
|
-
# lines = (buffer + chunk).split("\n")
|
69
|
-
# buffer = lines.pop()
|
70
|
-
# for line in lines:
|
71
|
-
# try:
|
72
|
-
# message = types.JSONRPCMessage.model_validate_json(line)
|
73
|
-
# except Exception as exc:
|
74
|
-
# await read_stream_writer.send(exc)
|
75
|
-
# continue
|
76
|
-
# await read_stream_writer.send(message)
|
77
|
-
# except anyio.ClosedResourceError:
|
78
|
-
# await anyio.lowlevel.checkpoint()
|
79
|
-
#
|
80
|
-
# async def stdin_writer():
|
81
|
-
# assert process.stdin, "Opened process is missing stdin"
|
82
|
-
# try:
|
83
|
-
# async with write_stream_reader:
|
84
|
-
# async for message in write_stream_reader:
|
85
|
-
# json = message.model_dump_json(by_alias=True, exclude_none=True)
|
86
|
-
# await process.stdin.send(
|
87
|
-
# (json + "\n").encode(
|
88
|
-
# encoding=server.encoding,
|
89
|
-
# errors=server.encoding_error_handler,
|
90
|
-
# )
|
91
|
-
# )
|
92
|
-
# except anyio.ClosedResourceError:
|
93
|
-
# await anyio.lowlevel.checkpoint()
|
94
|
-
#
|
95
|
-
# async def watch_process_exit():
|
96
|
-
# returncode = await process.wait()
|
97
|
-
# if returncode != 0:
|
98
|
-
# raise RuntimeError(f"Subprocess exited with code {returncode}. Command: {server.command} {server.args}")
|
99
|
-
#
|
100
|
-
# async with anyio.create_task_group() as tg, process:
|
101
|
-
# tg.start_soon(stdout_reader)
|
102
|
-
# tg.start_soon(stdin_writer)
|
103
|
-
# tg.start_soon(watch_process_exit)
|
104
|
-
#
|
105
|
-
# with anyio.move_on_after(0.2):
|
106
|
-
# await anyio.sleep_forever()
|
107
|
-
#
|
108
|
-
# yield read_stream, write_stream
|
109
|
-
#
|
File without changes
|
File without changes
|
{letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/entry_points.txt
RENAMED
File without changes
|