langfun 0.1.2.dev202511040805__py3-none-any.whl → 0.1.2.dev202511050805__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 langfun might be problematic. Click here for more details.
- langfun/core/agentic/action.py +76 -9
- langfun/core/agentic/action_eval.py +9 -2
- langfun/core/async_support.py +32 -3
- langfun/core/coding/python/correction.py +19 -9
- langfun/core/coding/python/execution.py +14 -12
- langfun/core/coding/python/generation.py +21 -16
- langfun/core/coding/python/sandboxing.py +23 -3
- langfun/core/component.py +42 -3
- langfun/core/concurrent.py +70 -6
- langfun/core/console.py +1 -1
- langfun/core/data/conversion/anthropic.py +10 -3
- langfun/core/data/conversion/gemini.py +9 -2
- langfun/core/data/conversion/openai.py +17 -7
- langfun/core/eval/base.py +46 -42
- langfun/core/eval/matching.py +5 -2
- langfun/core/eval/patching.py +3 -3
- langfun/core/eval/scoring.py +4 -3
- langfun/core/eval/v2/checkpointing.py +30 -4
- langfun/core/eval/v2/evaluation.py +59 -13
- langfun/core/eval/v2/example.py +22 -11
- langfun/core/eval/v2/experiment.py +51 -8
- langfun/core/eval/v2/metric_values.py +23 -3
- langfun/core/eval/v2/metrics.py +33 -4
- langfun/core/eval/v2/progress.py +9 -1
- langfun/core/eval/v2/reporting.py +15 -1
- langfun/core/eval/v2/runners.py +27 -7
- langfun/core/langfunc.py +45 -130
- langfun/core/language_model.py +88 -10
- langfun/core/llms/anthropic.py +27 -2
- langfun/core/llms/azure_openai.py +29 -17
- langfun/core/llms/cache/base.py +22 -2
- langfun/core/llms/cache/in_memory.py +48 -7
- langfun/core/llms/compositional.py +25 -1
- langfun/core/llms/deepseek.py +29 -1
- langfun/core/llms/fake.py +32 -1
- langfun/core/llms/gemini.py +9 -1
- langfun/core/llms/google_genai.py +29 -1
- langfun/core/llms/groq.py +27 -2
- langfun/core/llms/llama_cpp.py +22 -3
- langfun/core/llms/openai.py +29 -1
- langfun/core/llms/openai_compatible.py +18 -6
- langfun/core/llms/rest.py +12 -1
- langfun/core/llms/vertexai.py +39 -6
- langfun/core/logging.py +1 -1
- langfun/core/mcp/client.py +77 -22
- langfun/core/mcp/session.py +90 -10
- langfun/core/mcp/tool.py +83 -23
- langfun/core/memory.py +1 -0
- langfun/core/message.py +59 -12
- langfun/core/message_test.py +3 -0
- langfun/core/modalities/audio.py +21 -1
- langfun/core/modalities/image.py +19 -1
- langfun/core/modalities/mime.py +45 -2
- langfun/core/modalities/pdf.py +19 -1
- langfun/core/modalities/video.py +21 -1
- langfun/core/modality.py +66 -5
- langfun/core/natural_language.py +1 -1
- langfun/core/sampling.py +4 -4
- langfun/core/structured/completion.py +32 -37
- langfun/core/structured/description.py +54 -50
- langfun/core/structured/function_generation.py +29 -12
- langfun/core/structured/mapping.py +70 -15
- langfun/core/structured/parsing.py +90 -74
- langfun/core/structured/querying.py +201 -130
- langfun/core/structured/schema.py +70 -10
- langfun/core/structured/schema_generation.py +33 -14
- langfun/core/structured/scoring.py +45 -34
- langfun/core/structured/tokenization.py +24 -9
- langfun/core/subscription.py +2 -2
- langfun/core/template.py +132 -35
- langfun/core/template_test.py +22 -0
- {langfun-0.1.2.dev202511040805.dist-info → langfun-0.1.2.dev202511050805.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202511040805.dist-info → langfun-0.1.2.dev202511050805.dist-info}/RECORD +76 -76
- {langfun-0.1.2.dev202511040805.dist-info → langfun-0.1.2.dev202511050805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202511040805.dist-info → langfun-0.1.2.dev202511050805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202511040805.dist-info → langfun-0.1.2.dev202511050805.dist-info}/top_level.txt +0 -0
langfun/core/llms/vertexai.py
CHANGED
|
@@ -44,13 +44,32 @@ except ImportError:
|
|
|
44
44
|
|
|
45
45
|
@pg.use_init_args(['api_endpoint'])
|
|
46
46
|
class VertexAI(rest.REST):
|
|
47
|
-
"""Base class for
|
|
47
|
+
"""Base class for models served on Vertex AI.
|
|
48
48
|
|
|
49
|
-
This class handles
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
This class handles authentication for Vertex AI models. Subclasses,
|
|
50
|
+
such as `VertexAIGemini`, `VertexAIAnthropic`, and `VertexAILlama`,
|
|
51
|
+
provide specific implementations for different model families hosted
|
|
52
|
+
on Vertex AI.
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
**Quick Start:**
|
|
55
|
+
|
|
56
|
+
If you are using Langfun from a Google Cloud environment (e.g., GCE, GKE)
|
|
57
|
+
that has service account credentials, authentication is handled automatically.
|
|
58
|
+
Otherwise, you might need to set up credentials:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gcloud auth application-default login
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then you can use a Vertex AI model:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import langfun as lf
|
|
68
|
+
|
|
69
|
+
lm = lf.llms.VertexAIGemini25Flash(project='my-project', location='global')
|
|
70
|
+
r = lm('Who are you?')
|
|
71
|
+
print(r)
|
|
72
|
+
```
|
|
54
73
|
"""
|
|
55
74
|
|
|
56
75
|
model: pg.typing.Annotated[
|
|
@@ -158,7 +177,21 @@ class VertexAI(rest.REST):
|
|
|
158
177
|
@pg.use_init_args(['model'])
|
|
159
178
|
@pg.members([('api_endpoint', pg.typing.Str().freeze(''))])
|
|
160
179
|
class VertexAIGemini(VertexAI, gemini.Gemini):
|
|
161
|
-
"""Gemini models served
|
|
180
|
+
"""Gemini models served on Vertex AI.
|
|
181
|
+
|
|
182
|
+
**Quick Start:**
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import langfun as lf
|
|
186
|
+
|
|
187
|
+
# Call Gemini 1.5 Flash on Vertex AI.
|
|
188
|
+
# If project and location are not specified, they will be read from
|
|
189
|
+
# environment variables 'VERTEXAI_PROJECT' and 'VERTEXAI_LOCATION'.
|
|
190
|
+
lm = lf.llms.VertexAIGemini25Flash(project='my-project', location='global')
|
|
191
|
+
r = lm('Who are you?')
|
|
192
|
+
print(r)
|
|
193
|
+
```
|
|
194
|
+
"""
|
|
162
195
|
|
|
163
196
|
# Set default location to us-central1.
|
|
164
197
|
location = 'us-central1'
|
langfun/core/logging.py
CHANGED
|
@@ -310,7 +310,7 @@ def warning(
|
|
|
310
310
|
console: bool = False,
|
|
311
311
|
**kwargs
|
|
312
312
|
) -> LogEntry:
|
|
313
|
-
"""Logs
|
|
313
|
+
"""Logs a warning message to the session."""
|
|
314
314
|
return log('warning', message, indent=indent, console=console, **kwargs)
|
|
315
315
|
|
|
316
316
|
|
langfun/core/mcp/client.py
CHANGED
|
@@ -23,33 +23,53 @@ import pyglove as pg
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class McpClient(pg.Object):
|
|
26
|
-
"""
|
|
26
|
+
"""Interface for Model Context Protocol (MCP) client.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
An MCP client serves as a bridge to an MCP server, enabling users to interact
|
|
29
|
+
with tools hosted on the server. It provides methods for listing available
|
|
30
|
+
tools and creating sessions for tool interaction.
|
|
31
|
+
|
|
32
|
+
There are three types of MCP clients:
|
|
33
|
+
|
|
34
|
+
* **Stdio-based client**: Ideal for interacting with tools exposed as
|
|
35
|
+
command-line executables through stdin/stdout.
|
|
36
|
+
Created by `lf.mcp.McpClient.from_command`.
|
|
37
|
+
* **HTTP-based client**: Designed for tools accessible via HTTP,
|
|
38
|
+
supporting Server-Sent Events (SSE) for streaming.
|
|
39
|
+
Created by `lf.mcp.McpClient.from_url`.
|
|
40
|
+
* **In-memory client**: Useful for testing or embedding MCP servers
|
|
41
|
+
within the same process.
|
|
42
|
+
Created by `lf.mcp.McpClient.from_fastmcp`.
|
|
43
|
+
|
|
44
|
+
**Example Usage:**
|
|
29
45
|
|
|
30
46
|
```python
|
|
47
|
+
import langfun as lf
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
# Example 1: Stdio-based client
|
|
50
|
+
client = lf.mcp.McpClient.from_command('<MCP_CMD>', ['<ARG1>', 'ARG2'])
|
|
51
|
+
tools = client.list_tools()
|
|
52
|
+
tool_cls = tools['<TOOL_NAME>']
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
# Print the Python definition of the tool.
|
|
55
|
+
print(tool_cls.python_definition())
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
with client.session() as session:
|
|
58
|
+
result = tool_cls(x=1, y=2)(session)
|
|
59
|
+
print(result)
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
# Example 2: HTTP-based client (async)
|
|
62
|
+
async def main():
|
|
44
63
|
client = lf.mcp.McpClient.from_url('http://localhost:8000/mcp')
|
|
45
64
|
tools = client.list_tools()
|
|
46
65
|
tool_cls = tools['<TOOL_NAME>']
|
|
47
66
|
|
|
48
|
-
# Print the
|
|
67
|
+
# Print the Python definition of the tool.
|
|
49
68
|
print(tool_cls.python_definition())
|
|
50
69
|
|
|
51
70
|
async with client.session() as session:
|
|
52
|
-
|
|
71
|
+
result = await tool_cls(x=1, y=2).acall(session)
|
|
72
|
+
print(result)
|
|
53
73
|
```
|
|
54
74
|
"""
|
|
55
75
|
|
|
@@ -60,7 +80,15 @@ class McpClient(pg.Object):
|
|
|
60
80
|
def list_tools(
|
|
61
81
|
self, refresh: bool = False
|
|
62
82
|
) -> dict[str, Type[mcp_tool.McpTool]]:
|
|
63
|
-
"""Lists all MCP
|
|
83
|
+
"""Lists all available tools on the MCP server.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
refresh: If True, forces a refresh of the tool list from the server.
|
|
87
|
+
Otherwise, a cached list may be returned.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A dictionary mapping tool names to their corresponding `McpTool` classes.
|
|
91
|
+
"""
|
|
64
92
|
if self._tools is None or refresh:
|
|
65
93
|
with self.session() as session:
|
|
66
94
|
self._tools = session.list_tools()
|
|
@@ -68,11 +96,23 @@ class McpClient(pg.Object):
|
|
|
68
96
|
|
|
69
97
|
@abc.abstractmethod
|
|
70
98
|
def session(self) -> mcp_session.McpSession:
|
|
71
|
-
"""Creates a MCP
|
|
99
|
+
"""Creates a new session for interacting with MCP tools.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
An `McpSession` object.
|
|
103
|
+
"""
|
|
72
104
|
|
|
73
105
|
@classmethod
|
|
74
106
|
def from_command(cls, command: str, args: list[str]) -> 'McpClient':
|
|
75
|
-
"""Creates
|
|
107
|
+
"""Creates an MCP client from a command-line executable.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
command: The command to execute.
|
|
111
|
+
args: A list of arguments to pass to the command.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A `McpClient` instance that communicates via stdin/stdout.
|
|
115
|
+
"""
|
|
76
116
|
return _StdioMcpClient(command=command, args=args)
|
|
77
117
|
|
|
78
118
|
@classmethod
|
|
@@ -81,12 +121,27 @@ class McpClient(pg.Object):
|
|
|
81
121
|
url: str,
|
|
82
122
|
headers: dict[str, str] | None = None
|
|
83
123
|
) -> 'McpClient':
|
|
84
|
-
"""Creates
|
|
124
|
+
"""Creates an MCP client from an HTTP URL.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
url: The URL of the MCP server.
|
|
128
|
+
headers: An optional dictionary of HTTP headers to include in requests.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
A `McpClient` instance that communicates via HTTP.
|
|
132
|
+
"""
|
|
85
133
|
return _HttpMcpClient(url=url, headers=headers or {})
|
|
86
134
|
|
|
87
135
|
@classmethod
|
|
88
136
|
def from_fastmcp(cls, fastmcp: fastmcp_lib.FastMCP) -> 'McpClient':
|
|
89
|
-
"""Creates
|
|
137
|
+
"""Creates an MCP client from an in-memory FastMCP instance.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
fastmcp: An instance of `fastmcp_lib.FastMCP`.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
A `McpClient` instance that communicates with the in-memory server.
|
|
144
|
+
"""
|
|
90
145
|
return _InMemoryFastMcpClient(fastmcp=fastmcp)
|
|
91
146
|
|
|
92
147
|
|
|
@@ -97,18 +152,18 @@ class _StdioMcpClient(McpClient):
|
|
|
97
152
|
args: Annotated[list[str], 'Arguments to pass to the command.']
|
|
98
153
|
|
|
99
154
|
def session(self) -> mcp_session.McpSession:
|
|
100
|
-
"""Creates
|
|
155
|
+
"""Creates an McpSession from command."""
|
|
101
156
|
return mcp_session.McpSession.from_command(self.command, self.args)
|
|
102
157
|
|
|
103
158
|
|
|
104
159
|
class _HttpMcpClient(McpClient):
|
|
105
|
-
"""
|
|
160
|
+
"""HTTP-based MCP client."""
|
|
106
161
|
|
|
107
162
|
url: Annotated[str, 'URL to connect to.']
|
|
108
163
|
headers: Annotated[dict[str, str], 'Headers to send with the request.'] = {}
|
|
109
164
|
|
|
110
165
|
def session(self) -> mcp_session.McpSession:
|
|
111
|
-
"""Creates
|
|
166
|
+
"""Creates an McpSession from URL."""
|
|
112
167
|
return mcp_session.McpSession.from_url(self.url, self.headers)
|
|
113
168
|
|
|
114
169
|
|
|
@@ -118,5 +173,5 @@ class _InMemoryFastMcpClient(McpClient):
|
|
|
118
173
|
fastmcp: Annotated[fastmcp_lib.FastMCP, 'MCP server to connect to.']
|
|
119
174
|
|
|
120
175
|
def session(self) -> mcp_session.McpSession:
|
|
121
|
-
"""Creates
|
|
176
|
+
"""Creates an McpSession from an in-memory FastMCP instance."""
|
|
122
177
|
return mcp_session.McpSession.from_fastmcp(self.fastmcp)
|
langfun/core/mcp/session.py
CHANGED
|
@@ -26,10 +26,37 @@ from mcp.shared import memory
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class McpSession:
|
|
29
|
-
"""
|
|
29
|
+
"""Represents a session for interacting with an MCP server.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
`McpSession` provides the context for making calls to tools hosted on an
|
|
32
|
+
MCP server. It wraps the standard `mcp.ClientSession` to offer both
|
|
33
|
+
synchronous and asynchronous usage patterns.
|
|
34
|
+
|
|
35
|
+
Sessions are created using `lf.mcp.McpClient.session()` and should be used
|
|
36
|
+
as context managers (either sync or async) to ensure proper initialization
|
|
37
|
+
and teardown of the connection to the server.
|
|
38
|
+
|
|
39
|
+
**Example Sync Usage:**
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import langfun as lf
|
|
43
|
+
|
|
44
|
+
client = lf.mcp.McpClient.from_command(...)
|
|
45
|
+
with client.session() as session:
|
|
46
|
+
tools = session.list_tools()
|
|
47
|
+
result = tools['my_tool'](x=1)(session)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Example Async Usage:**
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import langfun as lf
|
|
54
|
+
|
|
55
|
+
client = lf.mcp.McpClient.from_url(...)
|
|
56
|
+
async with client.session() as session:
|
|
57
|
+
tools = await session.alist_tools()
|
|
58
|
+
result = await tools['my_tool'](x=1).acall(session)
|
|
59
|
+
```
|
|
33
60
|
"""
|
|
34
61
|
|
|
35
62
|
def __init__(self, stream) -> None:
|
|
@@ -74,11 +101,19 @@ class McpSession:
|
|
|
74
101
|
self._session = None
|
|
75
102
|
|
|
76
103
|
def list_tools(self) -> dict[str, Type[mcp_tool.McpTool]]:
|
|
77
|
-
"""Lists all
|
|
104
|
+
"""Lists all available tools on the MCP server synchronously.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A dictionary mapping tool names to their corresponding `McpTool` classes.
|
|
108
|
+
"""
|
|
78
109
|
return async_support.invoke_sync(self.alist_tools)
|
|
79
110
|
|
|
80
111
|
async def alist_tools(self) -> dict[str, Type[mcp_tool.McpTool]]:
|
|
81
|
-
"""Lists all
|
|
112
|
+
"""Lists all available tools on the MCP server asynchronously.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
A dictionary mapping tool names to their corresponding `McpTool` classes.
|
|
116
|
+
"""
|
|
82
117
|
assert self._session is not None, 'MCP session is not entered.'
|
|
83
118
|
return {
|
|
84
119
|
t.name: mcp_tool.McpTool.make_class(t)
|
|
@@ -91,7 +126,16 @@ class McpSession:
|
|
|
91
126
|
*,
|
|
92
127
|
returns_message: bool = False
|
|
93
128
|
) -> Any:
|
|
94
|
-
"""Calls
|
|
129
|
+
"""Calls an MCP tool synchronously.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
tool: The `McpTool` instance to call.
|
|
133
|
+
returns_message: If True, the tool call will return an `mcp.Message`
|
|
134
|
+
object; otherwise, it returns the tool's direct result.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The result of the tool call.
|
|
138
|
+
"""
|
|
95
139
|
return tool(self, returns_message=returns_message)
|
|
96
140
|
|
|
97
141
|
async def acall_tool(
|
|
@@ -100,7 +144,16 @@ class McpSession:
|
|
|
100
144
|
*,
|
|
101
145
|
returns_message: bool = False
|
|
102
146
|
) -> Any:
|
|
103
|
-
"""Calls
|
|
147
|
+
"""Calls an MCP tool asynchronously.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
tool: The `McpTool` instance to call.
|
|
151
|
+
returns_message: If True, the tool call will return an `mcp.Message`
|
|
152
|
+
object; otherwise, it returns the tool's direct result.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The result of the tool call.
|
|
156
|
+
"""
|
|
104
157
|
return await tool.acall(self, returns_message=returns_message)
|
|
105
158
|
|
|
106
159
|
@classmethod
|
|
@@ -109,7 +162,15 @@ class McpSession:
|
|
|
109
162
|
command: str,
|
|
110
163
|
args: list[str] | None = None
|
|
111
164
|
) -> 'McpSession':
|
|
112
|
-
"""Creates
|
|
165
|
+
"""Creates an MCP session from a command-line executable.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
command: The command to execute.
|
|
169
|
+
args: An optional list of arguments to pass to the command.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
An `McpSession` instance.
|
|
173
|
+
"""
|
|
113
174
|
return cls(
|
|
114
175
|
mcp.stdio_client(
|
|
115
176
|
mcp.StdioServerParameters(command=command, args=args or [])
|
|
@@ -122,7 +183,18 @@ class McpSession:
|
|
|
122
183
|
url: str,
|
|
123
184
|
headers: dict[str, str] | None = None
|
|
124
185
|
) -> 'McpSession':
|
|
125
|
-
"""Creates
|
|
186
|
+
"""Creates an MCP session from an HTTP URL.
|
|
187
|
+
|
|
188
|
+
The transport protocol (e.g., 'mcp' or 'sse') is inferred from the
|
|
189
|
+
last part of the URL path.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
url: The URL of the MCP server.
|
|
193
|
+
headers: An optional dictionary of HTTP headers to include in requests.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
An `McpSession` instance.
|
|
197
|
+
"""
|
|
126
198
|
transport = url.removesuffix('/').split('/')[-1].lower()
|
|
127
199
|
if transport == 'mcp':
|
|
128
200
|
return cls(streamable_http.streamablehttp_client(url, headers or {}))
|
|
@@ -136,12 +208,20 @@ class McpSession:
|
|
|
136
208
|
cls,
|
|
137
209
|
fastmcp: fastmcp_lib.FastMCP
|
|
138
210
|
):
|
|
211
|
+
"""Creates an MCP session from an in-memory FastMCP instance.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
fastmcp: An instance of `fastmcp_lib.FastMCP`.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
An `McpSession` instance.
|
|
218
|
+
"""
|
|
139
219
|
return cls(_client_streams_from_fastmcp(fastmcp))
|
|
140
220
|
|
|
141
221
|
|
|
142
222
|
@contextlib.asynccontextmanager
|
|
143
223
|
async def _client_streams_from_fastmcp(fastmcp: fastmcp_lib.FastMCP):
|
|
144
|
-
"""Creates client streams from
|
|
224
|
+
"""Creates client streams from an in-memory FastMCP instance."""
|
|
145
225
|
server = fastmcp._mcp_server # pylint: disable=protected-access
|
|
146
226
|
async with memory.create_client_server_memory_streams(
|
|
147
227
|
) as (client_streams, server_streams):
|
langfun/core/mcp/tool.py
CHANGED
|
@@ -31,7 +31,31 @@ class _McpToolMeta(pg.symbolic.ObjectMeta):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
34
|
-
"""
|
|
34
|
+
"""Represents a tool available on an MCP server.
|
|
35
|
+
|
|
36
|
+
`McpTool` is the base class for all tool proxies generated from an MCP
|
|
37
|
+
server's tool definitions. Users do not typically subclass `McpTool` directly.
|
|
38
|
+
Instead, tool classes are obtained by calling `lf.mcp.McpClient.list_tools()`
|
|
39
|
+
or `lf.mcp.McpSession.list_tools()`.
|
|
40
|
+
|
|
41
|
+
Once a tool class is obtained, it can be instantiated with input parameters
|
|
42
|
+
and called via an `McpSession` to execute the tool on the server.
|
|
43
|
+
|
|
44
|
+
**Example Usage:**
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import langfun as lf
|
|
48
|
+
|
|
49
|
+
client = lf.mcp.McpClient.from_command(...)
|
|
50
|
+
with client.session() as session:
|
|
51
|
+
# List tools and get the 'math' tool class.
|
|
52
|
+
math_tool_cls = session.list_tools()['math']
|
|
53
|
+
|
|
54
|
+
# Instantiate the tool with parameters and call it.
|
|
55
|
+
result = math_tool_cls(x=1, y=2, op='+')(session)
|
|
56
|
+
print(result)
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
35
59
|
|
|
36
60
|
TOOL_NAME: Annotated[
|
|
37
61
|
ClassVar[str],
|
|
@@ -40,7 +64,17 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
40
64
|
|
|
41
65
|
@classmethod
|
|
42
66
|
def python_definition(cls, markdown: bool = True) -> str:
|
|
43
|
-
"""Returns the Python definition of
|
|
67
|
+
"""Returns the Python definition of this tool's input schema.
|
|
68
|
+
|
|
69
|
+
This is useful for generating prompts that instruct a language model
|
|
70
|
+
on how to use the tool.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
markdown: If True, formats the output as a Markdown code block.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A string containing the Python definition of the tool's input schema.
|
|
77
|
+
"""
|
|
44
78
|
return lf_schema.Schema.from_value(cls).schema_str(
|
|
45
79
|
protocol='python', markdown=markdown
|
|
46
80
|
)
|
|
@@ -49,16 +83,17 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
49
83
|
def result_to_message(
|
|
50
84
|
cls, result: mcp.types.CallToolResult
|
|
51
85
|
) -> lf_message.ToolMessage:
|
|
52
|
-
"""Converts
|
|
86
|
+
"""Converts an `mcp.types.CallToolResult` to an `lf.ToolMessage`.
|
|
53
87
|
|
|
54
|
-
This method
|
|
55
|
-
Langfun ToolMessage
|
|
88
|
+
This method translates results from the MCP protocol, including text,
|
|
89
|
+
image, and audio content, into a Langfun `ToolMessage`, making it easy
|
|
90
|
+
to integrate tool results into Langfun workflows.
|
|
56
91
|
|
|
57
92
|
Args:
|
|
58
|
-
result: The
|
|
93
|
+
result: The `mcp.types.CallToolResult` object to convert.
|
|
59
94
|
|
|
60
95
|
Returns:
|
|
61
|
-
|
|
96
|
+
An `lf.ToolMessage` instance representing the tool result.
|
|
62
97
|
"""
|
|
63
98
|
chunks = []
|
|
64
99
|
for item in result.content:
|
|
@@ -81,16 +116,18 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
81
116
|
session,
|
|
82
117
|
*,
|
|
83
118
|
returns_message: bool = False) -> Any:
|
|
84
|
-
"""Calls
|
|
119
|
+
"""Calls the MCP tool synchronously within a given session.
|
|
85
120
|
|
|
86
121
|
Args:
|
|
87
|
-
session:
|
|
88
|
-
returns_message: If True,
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
session: An `McpSession` object.
|
|
123
|
+
returns_message: If True, the raw `lf.ToolMessage` is returned.
|
|
124
|
+
If False(default), the method attempts to return a more specific result:
|
|
125
|
+
- The `result` field of the `ToolMessage` if it's populated.
|
|
126
|
+
- The `ToolMessage` itself if it contains multi-modal content.
|
|
127
|
+
- The `text` field of the `ToolMessage` otherwise.
|
|
91
128
|
|
|
92
129
|
Returns:
|
|
93
|
-
The
|
|
130
|
+
The result of the tool call, processed according to `returns_message`.
|
|
94
131
|
"""
|
|
95
132
|
return async_support.invoke_sync(
|
|
96
133
|
self.acall,
|
|
@@ -104,16 +141,18 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
104
141
|
*,
|
|
105
142
|
returns_message: bool = False
|
|
106
143
|
) -> Any:
|
|
107
|
-
"""Calls
|
|
144
|
+
"""Calls the MCP tool asynchronously within a given session.
|
|
108
145
|
|
|
109
146
|
Args:
|
|
110
|
-
session: McpSession or mcp.ClientSession
|
|
111
|
-
returns_message: If True,
|
|
112
|
-
|
|
113
|
-
|
|
147
|
+
session: An `McpSession` object or an `mcp.ClientSession`.
|
|
148
|
+
returns_message: If True, the raw `lf.ToolMessage` is returned.
|
|
149
|
+
If False(default), the method attempts to return a more specific result:
|
|
150
|
+
- The `result` field of the `ToolMessage` if it's populated.
|
|
151
|
+
- The `ToolMessage` itself if it contains multi-modal content.
|
|
152
|
+
- The `text` field of the `ToolMessage` otherwise.
|
|
114
153
|
|
|
115
154
|
Returns:
|
|
116
|
-
The
|
|
155
|
+
The result of the tool call, processed according to `returns_message`.
|
|
117
156
|
"""
|
|
118
157
|
if not isinstance(session, mcp.ClientSession):
|
|
119
158
|
session = getattr(session, '_session', None)
|
|
@@ -131,7 +170,12 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
131
170
|
return message.text
|
|
132
171
|
|
|
133
172
|
def input_parameters(self) -> dict[str, Any]:
|
|
134
|
-
"""Returns the input parameters
|
|
173
|
+
"""Returns the input parameters for the tool call.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
A dictionary containing the input parameters, formatted for an
|
|
177
|
+
MCP `call_tool` request.
|
|
178
|
+
"""
|
|
135
179
|
# Optional fields are represented as fields with default values. Therefore,
|
|
136
180
|
# we need to remove the default values from the JSON representation of the
|
|
137
181
|
# tool.
|
|
@@ -147,7 +191,15 @@ class McpTool(pg.Object, metaclass=_McpToolMeta):
|
|
|
147
191
|
|
|
148
192
|
@classmethod
|
|
149
193
|
def make_class(cls, tool_definition: mcp.Tool) -> type['McpTool']:
|
|
150
|
-
"""
|
|
194
|
+
"""Creates an `McpTool` subclass from an MCP tool definition.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
tool_definition: An `mcp.Tool` object containing the tool's metadata.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
A dynamically generated class that inherits from `McpTool` and
|
|
201
|
+
represents the defined tool.
|
|
202
|
+
"""
|
|
151
203
|
|
|
152
204
|
class _McpTool(cls):
|
|
153
205
|
auto_schema = False
|
|
@@ -170,11 +222,19 @@ class _McpToolInputMeta(pg.symbolic.ObjectMeta):
|
|
|
170
222
|
|
|
171
223
|
|
|
172
224
|
class McpToolInput(pg.Object, metaclass=_McpToolInputMeta):
|
|
173
|
-
"""Base class for MCP tool
|
|
225
|
+
"""Base class for generated MCP tool input schemas."""
|
|
174
226
|
|
|
175
227
|
@classmethod
|
|
176
228
|
def make_class(cls, name: str, schema: pg.Schema):
|
|
177
|
-
"""
|
|
229
|
+
"""Creates an `McpToolInput` subclass from a schema.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
name: The name of the input class to generate.
|
|
233
|
+
schema: A `pg.Schema` object defining the input fields.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
A dynamically generated class that inherits from `McpToolInput`.
|
|
237
|
+
"""
|
|
178
238
|
|
|
179
239
|
class _McpToolInput(cls):
|
|
180
240
|
pass
|