flowllm 0.1.1__py3-none-any.whl → 0.1.3__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.
- flowllm/__init__.py +19 -6
- flowllm/app.py +4 -14
- flowllm/client/__init__.py +25 -0
- flowllm/client/async_http_client.py +81 -0
- flowllm/client/http_client.py +81 -0
- flowllm/client/mcp_client.py +133 -0
- flowllm/client/sync_mcp_client.py +116 -0
- flowllm/config/__init__.py +1 -0
- flowllm/config/{default_config.yaml → default.yaml} +3 -8
- flowllm/config/empty.yaml +37 -0
- flowllm/config/pydantic_config_parser.py +17 -17
- flowllm/context/base_context.py +27 -7
- flowllm/context/flow_context.py +6 -18
- flowllm/context/registry.py +5 -1
- flowllm/context/service_context.py +83 -37
- flowllm/embedding_model/__init__.py +1 -1
- flowllm/embedding_model/base_embedding_model.py +91 -0
- flowllm/embedding_model/openai_compatible_embedding_model.py +63 -5
- flowllm/flow/__init__.py +1 -0
- flowllm/flow/base_flow.py +74 -0
- flowllm/flow/base_tool_flow.py +15 -0
- flowllm/flow/gallery/__init__.py +8 -0
- flowllm/flow/gallery/cmd_flow.py +11 -0
- flowllm/flow/gallery/code_tool_flow.py +30 -0
- flowllm/flow/gallery/dashscope_search_tool_flow.py +34 -0
- flowllm/flow/gallery/deepsearch_tool_flow.py +39 -0
- flowllm/flow/gallery/expression_tool_flow.py +18 -0
- flowllm/flow/gallery/mock_tool_flow.py +62 -0
- flowllm/flow/gallery/tavily_search_tool_flow.py +30 -0
- flowllm/flow/gallery/terminate_tool_flow.py +30 -0
- flowllm/flow/parser/__init__.py +0 -0
- flowllm/{flow_engine/simple_flow_engine.py → flow/parser/expression_parser.py} +25 -67
- flowllm/llm/__init__.py +2 -1
- flowllm/llm/base_llm.py +94 -4
- flowllm/llm/litellm_llm.py +456 -0
- flowllm/llm/openai_compatible_llm.py +205 -5
- flowllm/op/__init__.py +12 -3
- flowllm/op/agent/__init__.py +1 -0
- flowllm/op/agent/react_v1_op.py +109 -0
- flowllm/op/agent/react_v1_prompt.yaml +54 -0
- flowllm/op/agent/react_v2_op.py +86 -0
- flowllm/op/agent/react_v2_prompt.yaml +35 -0
- flowllm/op/akshare/__init__.py +3 -0
- flowllm/op/akshare/get_ak_a_code_op.py +14 -22
- flowllm/op/akshare/get_ak_a_info_op.py +17 -20
- flowllm/op/{llm_base_op.py → base_llm_op.py} +7 -5
- flowllm/op/base_op.py +40 -44
- flowllm/op/base_ray_op.py +313 -0
- flowllm/op/code/__init__.py +1 -0
- flowllm/op/code/execute_code_op.py +42 -0
- flowllm/op/gallery/__init__.py +2 -0
- flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
- flowllm/op/gallery/terminate_op.py +29 -0
- flowllm/op/parallel_op.py +2 -9
- flowllm/op/search/__init__.py +3 -0
- flowllm/op/search/dashscope_deep_research_op.py +267 -0
- flowllm/op/search/dashscope_search_op.py +186 -0
- flowllm/op/search/dashscope_search_prompt.yaml +13 -0
- flowllm/op/search/tavily_search_op.py +109 -0
- flowllm/op/sequential_op.py +1 -9
- flowllm/schema/flow_request.py +12 -0
- flowllm/schema/message.py +2 -0
- flowllm/schema/service_config.py +12 -16
- flowllm/schema/tool_call.py +20 -8
- flowllm/schema/vector_node.py +1 -0
- flowllm/service/__init__.py +3 -2
- flowllm/service/base_service.py +50 -41
- flowllm/service/cmd_service.py +15 -0
- flowllm/service/http_service.py +34 -42
- flowllm/service/mcp_service.py +13 -11
- flowllm/storage/cache/__init__.py +1 -0
- flowllm/storage/cache/cache_data_handler.py +104 -0
- flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
- flowllm/storage/vector_store/__init__.py +3 -3
- flowllm/storage/vector_store/base_vector_store.py +3 -0
- flowllm/storage/vector_store/es_vector_store.py +4 -5
- flowllm/storage/vector_store/local_vector_store.py +0 -1
- flowllm/utils/common_utils.py +9 -21
- flowllm/utils/fetch_url.py +16 -12
- flowllm/utils/llm_utils.py +28 -0
- flowllm/utils/logger_utils.py +28 -0
- flowllm/utils/ridge_v2.py +54 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/METADATA +43 -390
- flowllm-0.1.3.dist-info/RECORD +102 -0
- flowllm-0.1.3.dist-info/entry_points.txt +2 -0
- flowllm/flow_engine/__init__.py +0 -1
- flowllm/flow_engine/base_flow_engine.py +0 -34
- flowllm-0.1.1.dist-info/RECORD +0 -62
- flowllm-0.1.1.dist-info/entry_points.txt +0 -4
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/WHEEL +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/top_level.txt +0 -0
flowllm/__init__.py
CHANGED
@@ -1,12 +1,25 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from flowllm.utils.logger_utils import init_logger
|
4
|
+
|
5
|
+
init_logger()
|
6
|
+
|
7
|
+
from flowllm.utils.common_utils import load_env
|
8
|
+
|
9
|
+
load_env()
|
10
|
+
|
1
11
|
from flowllm import embedding_model
|
2
|
-
from flowllm import flow_engine
|
3
12
|
from flowllm import llm
|
4
|
-
from flowllm import op
|
5
|
-
from flowllm import service
|
6
13
|
from flowllm import storage
|
7
14
|
|
8
|
-
|
15
|
+
if not os.environ.get("FLOW_USE_FRAMEWORK", "").lower() == "true":
|
16
|
+
from flowllm import flow
|
17
|
+
from flowllm import op
|
18
|
+
|
19
|
+
from flowllm import service
|
20
|
+
|
21
|
+
from flowllm.context.service_context import C
|
22
|
+
from flowllm.op import BaseOp, BaseRayOp, BaseLLMOp
|
9
23
|
|
10
|
-
__version__ = "0.1.
|
24
|
+
__version__ = "0.1.3"
|
11
25
|
|
12
|
-
__all__ = ["main"]
|
flowllm/app.py
CHANGED
@@ -1,25 +1,15 @@
|
|
1
1
|
import sys
|
2
2
|
|
3
3
|
from flowllm.service.base_service import BaseService
|
4
|
-
from flowllm.utils.common_utils import load_env
|
5
|
-
|
6
|
-
load_env()
|
7
|
-
|
8
|
-
from flowllm.config.pydantic_config_parser import PydanticConfigParser
|
9
|
-
from flowllm.schema.service_config import ServiceConfig
|
10
|
-
from flowllm.context.service_context import C
|
11
4
|
|
12
5
|
|
13
6
|
def main():
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
service: BaseService = service_cls(service_config)
|
18
|
-
service()
|
7
|
+
with BaseService.get_service(*sys.argv[1:]) as service:
|
8
|
+
service()
|
9
|
+
|
19
10
|
|
20
11
|
if __name__ == "__main__":
|
21
12
|
main()
|
22
13
|
|
23
|
-
|
24
14
|
# python -m build
|
25
|
-
# twine upload dist/*
|
15
|
+
# twine upload dist/*
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""
|
2
|
+
FlowLLM service clients module
|
3
|
+
|
4
|
+
This module provides various client implementations for interacting with FlowLLM services:
|
5
|
+
|
6
|
+
- HttpClient: Synchronous HTTP client for FlowLLM HTTP service
|
7
|
+
- AsyncHttpClient: Asynchronous HTTP client for FlowLLM HTTP service
|
8
|
+
- MCPClient: Asynchronous client for FlowLLM MCP (Model Context Protocol) service
|
9
|
+
- SyncMCPClient: Synchronous wrapper around MCPClient for easier synchronous usage
|
10
|
+
|
11
|
+
Each client provides methods to execute tool flows, list available flows, and perform
|
12
|
+
health checks on the respective services.
|
13
|
+
"""
|
14
|
+
|
15
|
+
from .async_http_client import AsyncHttpClient
|
16
|
+
from .http_client import HttpClient
|
17
|
+
from .mcp_client import MCPClient
|
18
|
+
from .sync_mcp_client import SyncMCPClient
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"HttpClient",
|
22
|
+
"AsyncHttpClient",
|
23
|
+
"MCPClient",
|
24
|
+
"SyncMCPClient"
|
25
|
+
]
|
@@ -0,0 +1,81 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from flowllm.schema.flow_response import FlowResponse
|
6
|
+
|
7
|
+
|
8
|
+
class AsyncHttpClient:
|
9
|
+
"""Async client for interacting with FlowLLM HTTP service"""
|
10
|
+
|
11
|
+
def __init__(self, base_url: str = "http://localhost:8001", timeout: float = 3600.0):
|
12
|
+
"""
|
13
|
+
Initialize async HTTP client
|
14
|
+
|
15
|
+
Args:
|
16
|
+
base_url: Base URL of the FlowLLM HTTP service
|
17
|
+
timeout: Request timeout in seconds
|
18
|
+
"""
|
19
|
+
self.base_url = base_url.rstrip('/') # Remove trailing slash for consistent URL formatting
|
20
|
+
self.timeout = timeout
|
21
|
+
self.client = httpx.AsyncClient(timeout=timeout) # Create async HTTP client with timeout
|
22
|
+
|
23
|
+
async def __aenter__(self):
|
24
|
+
"""Async context manager entry - returns self for 'async with' usage"""
|
25
|
+
return self
|
26
|
+
|
27
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
28
|
+
"""Async context manager exit - ensures proper cleanup of HTTP client"""
|
29
|
+
await self.client.aclose()
|
30
|
+
|
31
|
+
async def close(self):
|
32
|
+
"""Explicitly close the HTTP client connection"""
|
33
|
+
await self.client.aclose()
|
34
|
+
|
35
|
+
async def health_check(self) -> Dict[str, str]:
|
36
|
+
"""
|
37
|
+
Perform health check on the FlowLLM service
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Dict containing health status information from the service
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
httpx.HTTPStatusError: If the service is not healthy or unreachable
|
44
|
+
"""
|
45
|
+
response = await self.client.get(f"{self.base_url}/health")
|
46
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
47
|
+
return response.json()
|
48
|
+
|
49
|
+
async def execute_tool_flow(self, flow_name: str, **kwargs) -> FlowResponse:
|
50
|
+
"""
|
51
|
+
Execute a specific tool flow on the FlowLLM service
|
52
|
+
|
53
|
+
Args:
|
54
|
+
flow_name: Name of the tool flow to execute
|
55
|
+
**kwargs: Additional parameters to pass to the tool flow
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
FlowResponse object containing the execution results
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
httpx.HTTPStatusError: If the request fails or flow execution errors
|
62
|
+
"""
|
63
|
+
endpoint = f"{self.base_url}/{flow_name}"
|
64
|
+
response = await self.client.post(endpoint, json=kwargs) # Send flow parameters as JSON
|
65
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
66
|
+
result_data = response.json()
|
67
|
+
return FlowResponse(**result_data) # Parse response into FlowResponse schema
|
68
|
+
|
69
|
+
async def list_tool_flows(self) -> list:
|
70
|
+
"""
|
71
|
+
Get list of available tool flows from the FlowLLM service
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
List of available tool flow names and their metadata
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
httpx.HTTPStatusError: If the service is unreachable or returns an error
|
78
|
+
"""
|
79
|
+
response = await self.client.get(f"{self.base_url}/list")
|
80
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
81
|
+
return response.json()
|
@@ -0,0 +1,81 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from flowllm.schema.flow_response import FlowResponse
|
6
|
+
|
7
|
+
|
8
|
+
class HttpClient:
|
9
|
+
"""Client for interacting with FlowLLM HTTP service"""
|
10
|
+
|
11
|
+
def __init__(self, base_url: str = "http://localhost:8001", timeout: float = 3600.0):
|
12
|
+
"""
|
13
|
+
Initialize HTTP client
|
14
|
+
|
15
|
+
Args:
|
16
|
+
base_url: Base URL of the FlowLLM HTTP service
|
17
|
+
timeout: Request timeout in seconds
|
18
|
+
"""
|
19
|
+
self.base_url = base_url.rstrip('/') # Remove trailing slash for consistent URL formatting
|
20
|
+
self.timeout = timeout
|
21
|
+
self.client = httpx.Client(timeout=timeout) # Create synchronous HTTP client with timeout
|
22
|
+
|
23
|
+
def __enter__(self):
|
24
|
+
"""Context manager entry - returns self for 'with' usage"""
|
25
|
+
return self
|
26
|
+
|
27
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
28
|
+
"""Context manager exit - ensures proper cleanup of HTTP client"""
|
29
|
+
self.client.close()
|
30
|
+
|
31
|
+
def close(self):
|
32
|
+
"""Explicitly close the HTTP client connection"""
|
33
|
+
self.client.close()
|
34
|
+
|
35
|
+
def health_check(self) -> Dict[str, str]:
|
36
|
+
"""
|
37
|
+
Perform health check on the FlowLLM service
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Dict containing health status information from the service
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
httpx.HTTPStatusError: If the service is not healthy or unreachable
|
44
|
+
"""
|
45
|
+
response = self.client.get(f"{self.base_url}/health")
|
46
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
47
|
+
return response.json()
|
48
|
+
|
49
|
+
def execute_tool_flow(self, flow_name: str, **kwargs) -> FlowResponse:
|
50
|
+
"""
|
51
|
+
Execute a specific tool flow on the FlowLLM service
|
52
|
+
|
53
|
+
Args:
|
54
|
+
flow_name: Name of the tool flow to execute
|
55
|
+
**kwargs: Additional parameters to pass to the tool flow
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
FlowResponse object containing the execution results
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
httpx.HTTPStatusError: If the request fails or flow execution errors
|
62
|
+
"""
|
63
|
+
endpoint = f"{self.base_url}/{flow_name}"
|
64
|
+
response = self.client.post(endpoint, json=kwargs) # Send flow parameters as JSON
|
65
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
66
|
+
result_data = response.json()
|
67
|
+
return FlowResponse(**result_data) # Parse response into FlowResponse schema
|
68
|
+
|
69
|
+
def list_tool_flows(self) -> list:
|
70
|
+
"""
|
71
|
+
Get list of available tool flows from the FlowLLM service
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
List of available tool flow names and their metadata
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
httpx.HTTPStatusError: If the service is unreachable or returns an error
|
78
|
+
"""
|
79
|
+
response = self.client.get(f"{self.base_url}/list")
|
80
|
+
response.raise_for_status() # Raise exception for HTTP error status codes
|
81
|
+
return response.json()
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
2
|
+
|
3
|
+
from fastmcp import Client
|
4
|
+
from loguru import logger
|
5
|
+
from mcp.types import CallToolResult, Tool
|
6
|
+
|
7
|
+
|
8
|
+
class MCPClient:
|
9
|
+
"""Client for interacting with FlowLLM MCP service"""
|
10
|
+
|
11
|
+
def __init__(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8001):
|
12
|
+
"""
|
13
|
+
Initialize MCP client
|
14
|
+
|
15
|
+
Args:
|
16
|
+
transport: Transport type ("sse" or "stdio")
|
17
|
+
host: Host address for SSE transport
|
18
|
+
port: Port number for SSE transport
|
19
|
+
"""
|
20
|
+
self.transport = transport
|
21
|
+
self.host = host
|
22
|
+
self.port = port
|
23
|
+
|
24
|
+
# Configure connection URL based on transport type
|
25
|
+
if transport == "sse":
|
26
|
+
# Server-Sent Events transport over HTTP
|
27
|
+
self.connection_url = f"http://{host}:{port}/sse/"
|
28
|
+
elif transport == "stdio":
|
29
|
+
# Standard input/output transport for local processes
|
30
|
+
self.connection_url = "stdio"
|
31
|
+
else:
|
32
|
+
raise ValueError(f"Unsupported transport: {transport}")
|
33
|
+
|
34
|
+
self.client: Client | None = None # MCP client instance, initialized on connect
|
35
|
+
|
36
|
+
async def __aenter__(self):
|
37
|
+
"""Async context manager entry - automatically connects to MCP service"""
|
38
|
+
await self.connect()
|
39
|
+
return self
|
40
|
+
|
41
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
42
|
+
"""Async context manager exit - ensures proper cleanup and disconnection"""
|
43
|
+
await self.disconnect()
|
44
|
+
|
45
|
+
async def connect(self):
|
46
|
+
"""
|
47
|
+
Establish connection to the MCP service
|
48
|
+
|
49
|
+
Creates and initializes the MCP client based on the configured transport type.
|
50
|
+
For stdio transport, connects to a local process. For SSE transport, connects
|
51
|
+
to the HTTP endpoint.
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
ConnectionError: If unable to connect to the MCP service
|
55
|
+
"""
|
56
|
+
if self.transport == "stdio":
|
57
|
+
self.client = Client("stdio") # Connect to stdio-based MCP server
|
58
|
+
else:
|
59
|
+
self.client = Client(self.connection_url) # Connect to HTTP-based MCP server
|
60
|
+
|
61
|
+
await self.client.__aenter__() # Initialize the client connection
|
62
|
+
logger.info(f"Connected to MCP service at {self.connection_url}")
|
63
|
+
|
64
|
+
async def disconnect(self):
|
65
|
+
"""
|
66
|
+
Disconnect from the MCP service and clean up resources
|
67
|
+
|
68
|
+
Safely closes the MCP client connection and resets the client instance.
|
69
|
+
"""
|
70
|
+
if self.client:
|
71
|
+
await self.client.__aexit__(None, None, None) # Properly close the client connection
|
72
|
+
self.client = None # Reset client reference
|
73
|
+
|
74
|
+
async def list_tools(self) -> List[Tool]:
|
75
|
+
"""
|
76
|
+
Retrieve list of available tools from the MCP service
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
List of Tool objects representing available MCP tools
|
80
|
+
|
81
|
+
Raises:
|
82
|
+
RuntimeError: If client is not connected
|
83
|
+
ConnectionError: If communication with MCP service fails
|
84
|
+
"""
|
85
|
+
if not self.client:
|
86
|
+
raise RuntimeError("Client not connected. Call connect() first or use context manager.")
|
87
|
+
|
88
|
+
tools = await self.client.list_tools()
|
89
|
+
logger.info(f"Found {len(tools)} available tools")
|
90
|
+
return tools
|
91
|
+
|
92
|
+
async def get_tool(self, tool_name: str) -> Optional[Tool]:
|
93
|
+
"""
|
94
|
+
Get a specific tool by name from the MCP service
|
95
|
+
|
96
|
+
Args:
|
97
|
+
tool_name: Name of the tool to retrieve
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Tool object if found, None otherwise
|
101
|
+
|
102
|
+
Raises:
|
103
|
+
RuntimeError: If client is not connected
|
104
|
+
ConnectionError: If communication with MCP service fails
|
105
|
+
"""
|
106
|
+
tools = await self.list_tools() # Get all available tools
|
107
|
+
|
108
|
+
# Search for the requested tool by name
|
109
|
+
for tool in tools:
|
110
|
+
if tool.name == tool_name:
|
111
|
+
return tool
|
112
|
+
return None # Tool not found
|
113
|
+
|
114
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
115
|
+
"""
|
116
|
+
Execute a tool on the MCP service with the provided arguments
|
117
|
+
|
118
|
+
Args:
|
119
|
+
tool_name: Name of the tool to execute
|
120
|
+
arguments: Dictionary of arguments to pass to the tool
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
CallToolResult containing the tool execution results
|
124
|
+
|
125
|
+
Raises:
|
126
|
+
RuntimeError: If client is not connected
|
127
|
+
ValueError: If tool_name is invalid or arguments are malformed
|
128
|
+
ConnectionError: If communication with MCP service fails
|
129
|
+
"""
|
130
|
+
if not self.client:
|
131
|
+
raise RuntimeError("Client not connected. Call connect() first or use context manager.")
|
132
|
+
|
133
|
+
return await self.client.call_tool(tool_name, arguments=arguments)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Dict, Any, List, Optional
|
3
|
+
|
4
|
+
from mcp.types import CallToolResult, Tool
|
5
|
+
|
6
|
+
from flowllm.client import MCPClient
|
7
|
+
|
8
|
+
|
9
|
+
class SyncMCPClient:
|
10
|
+
"""Synchronous wrapper for MCPClient"""
|
11
|
+
|
12
|
+
def __init__(self, transport: str = "sse", host: str = "0.0.0.0", port: int = 8001):
|
13
|
+
"""
|
14
|
+
Initialize synchronous MCP client
|
15
|
+
|
16
|
+
This client wraps the asynchronous MCPClient to provide a synchronous interface.
|
17
|
+
It manages its own event loop for executing async operations synchronously.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
transport: Transport type ("sse" or "stdio")
|
21
|
+
host: Host address for SSE transport
|
22
|
+
port: Port number for SSE transport
|
23
|
+
"""
|
24
|
+
self.async_client = MCPClient(transport, host, port) # Create underlying async client
|
25
|
+
self._loop: asyncio.AbstractEventLoop | None = None # Event loop for async operations
|
26
|
+
|
27
|
+
def __enter__(self):
|
28
|
+
"""
|
29
|
+
Context manager entry - sets up event loop and connects to MCP service
|
30
|
+
|
31
|
+
Creates a new event loop, establishes connection to the MCP service,
|
32
|
+
and returns self for use in 'with' statements.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
self for context manager usage
|
36
|
+
"""
|
37
|
+
self._loop = asyncio.new_event_loop() # Create new event loop for this client
|
38
|
+
asyncio.set_event_loop(self._loop) # Set as current event loop
|
39
|
+
self._loop.run_until_complete(self.async_client.connect()) # Connect synchronously
|
40
|
+
return self
|
41
|
+
|
42
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
43
|
+
"""
|
44
|
+
Context manager exit - disconnects from MCP service and cleans up event loop
|
45
|
+
|
46
|
+
Ensures proper cleanup by disconnecting from the MCP service and
|
47
|
+
closing the event loop.
|
48
|
+
"""
|
49
|
+
if self._loop:
|
50
|
+
self._loop.run_until_complete(self.async_client.disconnect()) # Disconnect synchronously
|
51
|
+
self._loop.close() # Close the event loop
|
52
|
+
self._loop = None # Reset loop reference
|
53
|
+
|
54
|
+
def _run_async(self, coro):
|
55
|
+
"""
|
56
|
+
Execute an async coroutine synchronously using the managed event loop
|
57
|
+
|
58
|
+
Args:
|
59
|
+
coro: Coroutine to execute
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Result of the coroutine execution
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
RuntimeError: If client is not connected (no event loop available)
|
66
|
+
"""
|
67
|
+
if not self._loop:
|
68
|
+
raise RuntimeError("Client not connected. Use context manager first.")
|
69
|
+
return self._loop.run_until_complete(coro) # Run coroutine to completion
|
70
|
+
|
71
|
+
def list_tools(self) -> List[Tool]:
|
72
|
+
"""
|
73
|
+
Synchronously retrieve list of available tools from the MCP service
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
List of Tool objects representing available MCP tools
|
77
|
+
|
78
|
+
Raises:
|
79
|
+
RuntimeError: If client is not connected
|
80
|
+
ConnectionError: If communication with MCP service fails
|
81
|
+
"""
|
82
|
+
return self._run_async(self.async_client.list_tools())
|
83
|
+
|
84
|
+
def get_tool(self, tool_name: str) -> Optional[Tool]:
|
85
|
+
"""
|
86
|
+
Synchronously get a specific tool by name from the MCP service
|
87
|
+
|
88
|
+
Args:
|
89
|
+
tool_name: Name of the tool to retrieve
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
Tool object if found, None otherwise
|
93
|
+
|
94
|
+
Raises:
|
95
|
+
RuntimeError: If client is not connected
|
96
|
+
ConnectionError: If communication with MCP service fails
|
97
|
+
"""
|
98
|
+
return self._run_async(self.async_client.get_tool(tool_name))
|
99
|
+
|
100
|
+
def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
101
|
+
"""
|
102
|
+
Synchronously execute a tool on the MCP service with the provided arguments
|
103
|
+
|
104
|
+
Args:
|
105
|
+
tool_name: Name of the tool to execute
|
106
|
+
arguments: Dictionary of arguments to pass to the tool
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
CallToolResult containing the tool execution results
|
110
|
+
|
111
|
+
Raises:
|
112
|
+
RuntimeError: If client is not connected
|
113
|
+
ValueError: If tool_name is invalid or arguments are malformed
|
114
|
+
ConnectionError: If communication with MCP service fails
|
115
|
+
"""
|
116
|
+
return self._run_async(self.async_client.call_tool(tool_name, arguments))
|
flowllm/config/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
from .pydantic_config_parser import PydanticConfigParser
|
@@ -2,7 +2,7 @@
|
|
2
2
|
backend: mcp
|
3
3
|
language: ""
|
4
4
|
thread_pool_max_workers: 32
|
5
|
-
ray_max_workers:
|
5
|
+
ray_max_workers: 1
|
6
6
|
|
7
7
|
mcp:
|
8
8
|
transport: sse
|
@@ -15,11 +15,6 @@ http:
|
|
15
15
|
timeout_keep_alive: 600
|
16
16
|
limit_concurrency: 64
|
17
17
|
|
18
|
-
|
19
|
-
flow_engine:
|
20
|
-
backend: simple
|
21
|
-
|
22
|
-
|
23
18
|
flow:
|
24
19
|
get_a_stock_infos:
|
25
20
|
flow_content: get_ak_a_code_op >> get_ak_a_info_op >> get_ak_a_spot_op >> get_ak_a_money_flow_op >> get_ak_a_financial_info_op >> merge_ak_a_info_op
|
@@ -37,7 +32,7 @@ flow:
|
|
37
32
|
type: "str"
|
38
33
|
description: "user question"
|
39
34
|
|
40
|
-
|
35
|
+
mock_expression_flow:
|
41
36
|
flow_content: mock1_op>>((mock4_op>>mock2_op)|mock5_op)>>(mock3_op|mock6_op)
|
42
37
|
description: "mock flow"
|
43
38
|
input_schema:
|
@@ -62,6 +57,7 @@ llm:
|
|
62
57
|
model_name: qwen3-30b-a3b-thinking-2507
|
63
58
|
params:
|
64
59
|
temperature: 0.6
|
60
|
+
|
65
61
|
qwen3_30b_instruct:
|
66
62
|
backend: openai_compatible
|
67
63
|
model_name: qwen3-30b-a3b-instruct-2507
|
@@ -79,4 +75,3 @@ vector_store:
|
|
79
75
|
embedding_model: default
|
80
76
|
params:
|
81
77
|
hosts: "http://localhost:9200"
|
82
|
-
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# default config.yaml
|
2
|
+
backend: http
|
3
|
+
language: ""
|
4
|
+
thread_pool_max_workers: 32
|
5
|
+
ray_max_workers: 1
|
6
|
+
|
7
|
+
mcp:
|
8
|
+
transport: sse
|
9
|
+
host: "0.0.0.0"
|
10
|
+
port: 8001
|
11
|
+
|
12
|
+
http:
|
13
|
+
host: "0.0.0.0"
|
14
|
+
port: 8001
|
15
|
+
timeout_keep_alive: 600
|
16
|
+
limit_concurrency: 64
|
17
|
+
|
18
|
+
llm:
|
19
|
+
default:
|
20
|
+
backend: openai_compatible
|
21
|
+
model_name: qwen3-30b-a3b-thinking-2507
|
22
|
+
params:
|
23
|
+
temperature: 0.6
|
24
|
+
|
25
|
+
embedding_model:
|
26
|
+
default:
|
27
|
+
backend: openai_compatible
|
28
|
+
model_name: text-embedding-v4
|
29
|
+
params:
|
30
|
+
dimensions: 1024
|
31
|
+
|
32
|
+
vector_store:
|
33
|
+
default:
|
34
|
+
backend: elasticsearch
|
35
|
+
embedding_model: default
|
36
|
+
params:
|
37
|
+
hosts: "http://localhost:9200"
|
@@ -11,6 +11,9 @@ T = TypeVar('T', bound=BaseModel)
|
|
11
11
|
|
12
12
|
|
13
13
|
class PydanticConfigParser(Generic[T]):
|
14
|
+
current_file: str = __file__
|
15
|
+
default_config_name: str = ""
|
16
|
+
|
14
17
|
"""
|
15
18
|
Pydantic Configuration Parser
|
16
19
|
|
@@ -187,18 +190,22 @@ class PydanticConfigParser(Generic[T]):
|
|
187
190
|
else:
|
188
191
|
filter_args.append(arg)
|
189
192
|
|
190
|
-
if config:
|
191
|
-
if
|
192
|
-
config
|
193
|
+
if not config:
|
194
|
+
if self.default_config_name:
|
195
|
+
config = self.default_config_name
|
196
|
+
assert config, "add `config=<config_file>` in cmd!"
|
197
|
+
|
198
|
+
if not config.endswith(".yaml"):
|
199
|
+
config += ".yaml"
|
193
200
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
201
|
+
# load pre-built configs
|
202
|
+
config_path = Path(self.current_file).parent / config
|
203
|
+
if not config_path.exists():
|
204
|
+
config_path = Path(config)
|
198
205
|
|
199
|
-
|
200
|
-
|
201
|
-
|
206
|
+
yaml_config = self.load_from_yaml(config_path)
|
207
|
+
logger.info(f"flowllm using config={config_path}")
|
208
|
+
configs_to_merge.append(yaml_config)
|
202
209
|
|
203
210
|
# 3. Command line override configuration
|
204
211
|
if args:
|
@@ -233,10 +240,3 @@ class PydanticConfigParser(Generic[T]):
|
|
233
240
|
final_config = self.merge_configs(copy.deepcopy(self.config_dict), override_config)
|
234
241
|
|
235
242
|
return self.config_class.model_validate(final_config)
|
236
|
-
|
237
|
-
|
238
|
-
def get_default_config():
|
239
|
-
from flowllm.schema.service_config import ServiceConfig
|
240
|
-
|
241
|
-
config_parser = PydanticConfigParser(ServiceConfig)
|
242
|
-
return config_parser.parse_args("config=default_config")
|