flowllm 0.1.1__py3-none-any.whl → 0.1.2__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 (87) hide show
  1. flowllm/__init__.py +15 -6
  2. flowllm/app.py +4 -14
  3. flowllm/client/__init__.py +25 -0
  4. flowllm/client/async_http_client.py +81 -0
  5. flowllm/client/http_client.py +81 -0
  6. flowllm/client/mcp_client.py +133 -0
  7. flowllm/client/sync_mcp_client.py +116 -0
  8. flowllm/config/__init__.py +1 -0
  9. flowllm/config/{default_config.yaml → default.yaml} +3 -8
  10. flowllm/config/empty.yaml +37 -0
  11. flowllm/config/pydantic_config_parser.py +17 -17
  12. flowllm/context/base_context.py +27 -7
  13. flowllm/context/flow_context.py +6 -18
  14. flowllm/context/registry.py +5 -1
  15. flowllm/context/service_context.py +81 -37
  16. flowllm/embedding_model/__init__.py +1 -1
  17. flowllm/embedding_model/base_embedding_model.py +91 -0
  18. flowllm/embedding_model/openai_compatible_embedding_model.py +63 -5
  19. flowllm/flow/__init__.py +1 -0
  20. flowllm/flow/base_flow.py +72 -0
  21. flowllm/flow/base_tool_flow.py +15 -0
  22. flowllm/flow/gallery/__init__.py +8 -0
  23. flowllm/flow/gallery/cmd_flow.py +11 -0
  24. flowllm/flow/gallery/code_tool_flow.py +30 -0
  25. flowllm/flow/gallery/dashscope_search_tool_flow.py +34 -0
  26. flowllm/flow/gallery/deepsearch_tool_flow.py +39 -0
  27. flowllm/flow/gallery/expression_tool_flow.py +18 -0
  28. flowllm/flow/gallery/mock_tool_flow.py +67 -0
  29. flowllm/flow/gallery/tavily_search_tool_flow.py +30 -0
  30. flowllm/flow/gallery/terminate_tool_flow.py +30 -0
  31. flowllm/flow/parser/__init__.py +0 -0
  32. flowllm/{flow_engine/simple_flow_engine.py → flow/parser/expression_parser.py} +25 -67
  33. flowllm/llm/__init__.py +2 -1
  34. flowllm/llm/base_llm.py +94 -4
  35. flowllm/llm/litellm_llm.py +455 -0
  36. flowllm/llm/openai_compatible_llm.py +205 -5
  37. flowllm/op/__init__.py +11 -3
  38. flowllm/op/agent/__init__.py +0 -0
  39. flowllm/op/agent/react_op.py +83 -0
  40. flowllm/op/agent/react_prompt.yaml +28 -0
  41. flowllm/op/akshare/__init__.py +3 -0
  42. flowllm/op/akshare/get_ak_a_code_op.py +14 -22
  43. flowllm/op/akshare/get_ak_a_info_op.py +17 -20
  44. flowllm/op/{llm_base_op.py → base_llm_op.py} +6 -5
  45. flowllm/op/base_op.py +14 -35
  46. flowllm/op/base_ray_op.py +313 -0
  47. flowllm/op/code/__init__.py +1 -0
  48. flowllm/op/code/execute_code_op.py +42 -0
  49. flowllm/op/gallery/__init__.py +2 -0
  50. flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
  51. flowllm/op/gallery/terminate_op.py +29 -0
  52. flowllm/op/parallel_op.py +2 -9
  53. flowllm/op/search/__init__.py +3 -0
  54. flowllm/op/search/dashscope_deep_research_op.py +260 -0
  55. flowllm/op/search/dashscope_search_op.py +179 -0
  56. flowllm/op/search/dashscope_search_prompt.yaml +13 -0
  57. flowllm/op/search/tavily_search_op.py +102 -0
  58. flowllm/op/sequential_op.py +1 -9
  59. flowllm/schema/flow_request.py +12 -0
  60. flowllm/schema/service_config.py +12 -16
  61. flowllm/schema/tool_call.py +13 -5
  62. flowllm/schema/vector_node.py +1 -0
  63. flowllm/service/__init__.py +3 -2
  64. flowllm/service/base_service.py +50 -41
  65. flowllm/service/cmd_service.py +15 -0
  66. flowllm/service/http_service.py +34 -42
  67. flowllm/service/mcp_service.py +13 -11
  68. flowllm/storage/cache/__init__.py +1 -0
  69. flowllm/storage/cache/cache_data_handler.py +104 -0
  70. flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
  71. flowllm/storage/vector_store/__init__.py +3 -3
  72. flowllm/storage/vector_store/es_vector_store.py +1 -2
  73. flowllm/storage/vector_store/local_vector_store.py +0 -1
  74. flowllm/utils/common_utils.py +9 -21
  75. flowllm/utils/fetch_url.py +16 -12
  76. flowllm/utils/llm_utils.py +28 -0
  77. flowllm/utils/ridge_v2.py +54 -0
  78. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/METADATA +43 -390
  79. flowllm-0.1.2.dist-info/RECORD +99 -0
  80. flowllm-0.1.2.dist-info/entry_points.txt +2 -0
  81. flowllm/flow_engine/__init__.py +0 -1
  82. flowllm/flow_engine/base_flow_engine.py +0 -34
  83. flowllm-0.1.1.dist-info/RECORD +0 -62
  84. flowllm-0.1.1.dist-info/entry_points.txt +0 -4
  85. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/WHEEL +0 -0
  86. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/licenses/LICENSE +0 -0
  87. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/top_level.txt +0 -0
flowllm/__init__.py CHANGED
@@ -1,12 +1,21 @@
1
+ import os
2
+
3
+ from flowllm.utils.common_utils import load_env
4
+
5
+ load_env()
6
+
1
7
  from flowllm import embedding_model
2
- from flowllm import flow_engine
3
8
  from flowllm import llm
4
- from flowllm import op
5
- from flowllm import service
6
9
  from flowllm import storage
7
10
 
8
- from .app import main
11
+ if not os.environ.get("FLOW_USE_FRAMEWORK", "").lower() == "true":
12
+ from flowllm import flow
13
+ from flowllm import op
14
+
15
+ from flowllm import service
16
+
17
+ from flowllm.context.service_context import C
18
+ from flowllm.op import BaseOp, BaseRayOp, BaseLLMOp
9
19
 
10
- __version__ = "0.1.1"
20
+ __version__ = "0.1.2"
11
21
 
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
- config_parser = PydanticConfigParser(ServiceConfig)
15
- service_config: ServiceConfig = config_parser.parse_args(*sys.argv[1:])
16
- service_cls = C.resolve_service(service_config.backend)
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))
@@ -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: 8
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
- mock_flow:
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 not config.endswith(".yaml"):
192
- config += ".yaml"
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
- # load pre-built configs
195
- config_path = Path(__file__).parent / config
196
- if not config_path.exists():
197
- config_path = Path(config)
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
- yaml_config = self.load_from_yaml(config_path)
200
- logger.info(f"flowllm using config={config_path}")
201
- configs_to_merge.append(yaml_config)
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")
@@ -1,13 +1,20 @@
1
- from typing import List
2
-
3
-
4
1
  class BaseContext:
5
2
  def __init__(self, **kwargs):
6
3
  self._data = {**kwargs}
7
4
 
8
5
  def __getattr__(self, name: str):
9
- if name in self._data:
10
- return self._data[name]
6
+ # Avoid infinite recursion when _data is not yet initialized
7
+ if name == '_data':
8
+ raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
9
+
10
+ # Use object.__getattribute__ to safely access _data
11
+ try:
12
+ data = object.__getattribute__(self, '_data')
13
+ except AttributeError:
14
+ raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
15
+
16
+ if name in data:
17
+ return data[name]
11
18
 
12
19
  raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
13
20
 
@@ -34,13 +41,26 @@ class BaseContext:
34
41
  def dump(self) -> dict:
35
42
  return self._data
36
43
 
44
+ def get(self, key: str, default=None):
45
+ return self._data.get(key, default)
46
+
37
47
  @property
38
- def keys(self) -> List[str]:
39
- return sorted(self._data.keys())
48
+ def keys(self):
49
+ return self._data.keys()
40
50
 
41
51
  def update(self, **kwargs):
42
52
  self._data.update(kwargs)
43
53
 
54
+ def items(self):
55
+ return self._data.items()
56
+
57
+ def __getstate__(self):
58
+ """Support for pickle serialization"""
59
+ return {'_data': self._data}
60
+
61
+ def __setstate__(self, state):
62
+ """Support for pickle deserialization"""
63
+ self._data = state['_data']
44
64
 
45
65
  if __name__ == "__main__":
46
66
  ctx = BaseContext(**{"name": "Alice", "age": 30, "city": "New York"})