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.
Files changed (68) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +9 -3
  3. letta/agents/base_agent.py +2 -2
  4. letta/agents/letta_agent.py +56 -45
  5. letta/agents/voice_agent.py +2 -2
  6. letta/data_sources/redis_client.py +146 -1
  7. letta/errors.py +4 -0
  8. letta/functions/function_sets/files.py +2 -2
  9. letta/functions/mcp_client/types.py +30 -6
  10. letta/functions/schema_generator.py +46 -1
  11. letta/functions/schema_validator.py +17 -2
  12. letta/functions/types.py +1 -1
  13. letta/helpers/tool_execution_helper.py +0 -2
  14. letta/llm_api/anthropic_client.py +27 -5
  15. letta/llm_api/deepseek_client.py +97 -0
  16. letta/llm_api/groq_client.py +79 -0
  17. letta/llm_api/helpers.py +0 -1
  18. letta/llm_api/llm_api_tools.py +2 -113
  19. letta/llm_api/llm_client.py +21 -0
  20. letta/llm_api/llm_client_base.py +11 -9
  21. letta/llm_api/openai_client.py +3 -0
  22. letta/llm_api/xai_client.py +85 -0
  23. letta/prompts/prompt_generator.py +190 -0
  24. letta/schemas/agent_file.py +17 -2
  25. letta/schemas/file.py +24 -1
  26. letta/schemas/job.py +2 -0
  27. letta/schemas/letta_message.py +2 -0
  28. letta/schemas/letta_request.py +22 -0
  29. letta/schemas/message.py +10 -1
  30. letta/schemas/providers/bedrock.py +1 -0
  31. letta/server/rest_api/redis_stream_manager.py +300 -0
  32. letta/server/rest_api/routers/v1/agents.py +129 -7
  33. letta/server/rest_api/routers/v1/folders.py +15 -5
  34. letta/server/rest_api/routers/v1/runs.py +101 -11
  35. letta/server/rest_api/routers/v1/sources.py +21 -53
  36. letta/server/rest_api/routers/v1/telemetry.py +14 -4
  37. letta/server/rest_api/routers/v1/tools.py +2 -2
  38. letta/server/rest_api/streaming_response.py +3 -24
  39. letta/server/server.py +0 -1
  40. letta/services/agent_manager.py +2 -2
  41. letta/services/agent_serialization_manager.py +129 -32
  42. letta/services/file_manager.py +111 -6
  43. letta/services/file_processor/file_processor.py +5 -2
  44. letta/services/files_agents_manager.py +60 -0
  45. letta/services/helpers/agent_manager_helper.py +4 -205
  46. letta/services/helpers/tool_parser_helper.py +6 -3
  47. letta/services/mcp/base_client.py +7 -1
  48. letta/services/mcp/sse_client.py +7 -2
  49. letta/services/mcp/stdio_client.py +5 -0
  50. letta/services/mcp/streamable_http_client.py +11 -2
  51. letta/services/mcp_manager.py +31 -30
  52. letta/services/source_manager.py +26 -1
  53. letta/services/summarizer/summarizer.py +21 -10
  54. letta/services/tool_executor/files_tool_executor.py +13 -9
  55. letta/services/tool_executor/mcp_tool_executor.py +3 -0
  56. letta/services/tool_executor/tool_execution_manager.py +13 -0
  57. letta/services/tool_manager.py +43 -20
  58. letta/settings.py +1 -0
  59. letta/utils.py +37 -0
  60. {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/METADATA +2 -2
  61. {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/RECORD +64 -63
  62. letta/functions/mcp_client/__init__.py +0 -0
  63. letta/functions/mcp_client/base_client.py +0 -156
  64. letta/functions/mcp_client/sse_client.py +0 -51
  65. letta/functions/mcp_client/stdio_client.py +0 -109
  66. {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/LICENSE +0 -0
  67. {letta_nightly-0.11.4.dev20250825104222.dist-info → letta_nightly-0.11.5.dist-info}/WHEEL +0 -0
  68. {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
- #