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
@@ -1,5 +1,6 @@
1
1
  from typing import List
2
2
  from uuid import uuid4
3
+
3
4
  from pydantic import BaseModel, Field
4
5
 
5
6
 
@@ -1,2 +1,3 @@
1
- from flowllm.service.mcp_service import MCPService
2
- from flowllm.service.http_service import HttpService
1
+ from .cmd_service import CmdService
2
+ from .http_service import HttpService
3
+ from .mcp_service import MCPService
@@ -1,59 +1,68 @@
1
- from concurrent.futures import ThreadPoolExecutor
2
- from typing import Dict
1
+ from abc import abstractmethod, ABC
2
+ from typing import Dict, Optional
3
3
 
4
4
  from loguru import logger
5
+ from pydantic import create_model, Field
5
6
 
6
- from flowllm.context.flow_context import FlowContext
7
+ from flowllm.config.pydantic_config_parser import PydanticConfigParser
7
8
  from flowllm.context.service_context import C
8
- from flowllm.flow_engine.base_flow_engine import BaseFlowEngine
9
- from flowllm.schema.flow_response import FlowResponse
10
- from flowllm.schema.service_config import ServiceConfig, EmbeddingModelConfig, FlowConfig
9
+ from flowllm.schema.flow_request import FlowRequest
10
+ from flowllm.schema.service_config import ServiceConfig
11
+ from flowllm.schema.tool_call import ParamAttrs
12
+ from flowllm.utils.common_utils import snake_to_camel
11
13
 
12
14
 
13
- class BaseService:
15
+ class BaseService(ABC):
16
+ TYPE_MAPPING = {
17
+ "str": str,
18
+ "int": int,
19
+ "float": float,
20
+ "bool": bool,
21
+ "list": list,
22
+ "dict": dict,
23
+ }
14
24
 
15
25
  def __init__(self, service_config: ServiceConfig):
16
26
  self.service_config = service_config
17
27
 
18
- C.language = self.service_config.language
19
- C.thread_pool = ThreadPoolExecutor(max_workers=self.service_config.thread_pool_max_workers)
20
- for name, config in self.service_config.vector_store.items():
21
- vector_store_cls = C.resolve_vector_store(config.backend)
22
- embedding_model_config: EmbeddingModelConfig = self.service_config.embedding_model[config.embedding_model]
23
- embedding_model_cls = C.resolve_embedding_model(embedding_model_config.backend)
24
- embedding_model = embedding_model_cls(model_name=embedding_model_config.model_name,
25
- **embedding_model_config.params)
26
- C.set_vector_store(name, vector_store_cls(embedding_model=embedding_model, **config.params))
27
-
28
- self.flow_engine_config = self.service_config.flow_engine
29
- self.flow_engine_cls = C.resolve_flow_engine(self.flow_engine_config.backend)
30
- self.flow_config_dict: Dict[str, FlowConfig] = \
31
- {name: config.set_name(name) for name, config in self.service_config.flow.items()}
32
-
33
28
  self.mcp_config = self.service_config.mcp
34
29
  self.http_config = self.service_config.http
30
+ C.init_by_service_config(self.service_config)
31
+
32
+ @classmethod
33
+ def get_service(cls, *args, parser: type[PydanticConfigParser] = PydanticConfigParser) -> "BaseService":
34
+ config_parser = parser(ServiceConfig)
35
+ service_config: ServiceConfig = config_parser.parse_args(*args)
36
+ service_cls = C.resolve_service(service_config.backend)
37
+ return service_cls(service_config)
38
+
39
+ def _create_pydantic_model(self, flow_name: str, input_schema: Dict[str, ParamAttrs]):
40
+ fields = {}
41
+
42
+ for param_name, param_config in input_schema.items():
43
+ field_type = self.TYPE_MAPPING.get(param_config.type, str)
44
+
45
+ if not param_config.required:
46
+ fields[param_name] = (Optional[field_type], Field(default=None, description=param_config.description))
47
+ else:
48
+ fields[param_name] = (field_type, Field(default=..., description=param_config.description))
49
+
50
+ return create_model(f"{snake_to_camel(flow_name)}Model", __base__=FlowRequest, **fields)
35
51
 
36
- def execute_flow(self, flow_name: str, **kwargs) -> FlowResponse:
37
- response = FlowResponse()
38
- try:
39
- logger.info(f"request.params={kwargs}")
40
- flow_context = FlowContext(**kwargs,
41
- response=response,
42
- service_config=self.service_config.model_copy(deep=True))
52
+ def integrate_tool_flow(self, tool_flow_name: str):
53
+ ...
43
54
 
44
- flow_config = self.flow_config_dict[flow_name]
45
- flow_engine: BaseFlowEngine = self.flow_engine_cls(flow_name=flow_name,
46
- flow_content=flow_config.flow_content,
47
- flow_context=flow_context,
48
- **self.flow_engine_config.params)
49
- flow_engine()
55
+ def integrate_tool_flows(self):
56
+ for tool_flow_name in C.tool_flow_names:
57
+ self.integrate_tool_flow(tool_flow_name)
58
+ logger.info(f"integrate flow_endpoint={tool_flow_name}")
50
59
 
51
- except Exception as e:
52
- logger.exception(f"flow_name={flow_name} encounter error={e.args}")
53
- response.success = False
54
- response.answer = str(e.args)
60
+ def __enter__(self):
61
+ return self
55
62
 
56
- return response
63
+ def __exit__(self, exc_type, exc_val, exc_tb):
64
+ ...
57
65
 
66
+ @abstractmethod
58
67
  def __call__(self):
59
- raise NotImplementedError
68
+ ...
@@ -0,0 +1,15 @@
1
+ from loguru import logger
2
+
3
+ from flowllm.context.service_context import C
4
+ from flowllm.flow.gallery import CmdFlow
5
+ from flowllm.service.base_service import BaseService
6
+
7
+
8
+ @C.register_service("cmd")
9
+ class CmdService(BaseService):
10
+
11
+ def __call__(self):
12
+ flow = CmdFlow(flow=self.service_config.cmd.flow)
13
+ response = flow.__call__(**self.service_config.cmd.params)
14
+ if response.answer:
15
+ logger.info(f"final_answer={response.answer}")
@@ -1,33 +1,24 @@
1
1
  import asyncio
2
2
  from functools import partial
3
- from typing import Dict, Optional
4
3
 
5
4
  import uvicorn
6
5
  from fastapi import FastAPI
7
6
  from fastapi.middleware.cors import CORSMiddleware
8
7
  from loguru import logger
9
- from pydantic import BaseModel, create_model, Field
10
8
 
11
9
  from flowllm.context.service_context import C
10
+ from flowllm.flow.base_tool_flow import BaseToolFlow
12
11
  from flowllm.schema.flow_response import FlowResponse
13
- from flowllm.schema.tool_call import ParamAttrs
14
12
  from flowllm.service.base_service import BaseService
15
- from flowllm.utils.common_utils import snake_to_camel
16
13
 
17
14
 
18
15
  @C.register_service("http")
19
16
  class HttpService(BaseService):
20
- TYPE_MAPPING = {
21
- "str": str,
22
- "int": int,
23
- "float": float,
24
- "bool": bool
25
- }
26
17
 
27
18
  def __init__(self, *args, **kwargs):
28
19
  super().__init__(*args, **kwargs)
29
20
  self.app = FastAPI(title="FlowLLM", description="HTTP API for FlowLLM")
30
-
21
+
31
22
  # Add CORS middleware
32
23
  self.app.add_middleware(
33
24
  CORSMiddleware,
@@ -36,50 +27,51 @@ class HttpService(BaseService):
36
27
  allow_methods=["*"],
37
28
  allow_headers=["*"],
38
29
  )
39
-
30
+
40
31
  # Add health check endpoint
41
32
  self.app.get("/health")(self.health_check)
42
33
 
43
34
  @staticmethod
44
35
  def health_check():
45
36
  return {"status": "healthy"}
46
-
47
- def _create_pydantic_model(self, flow_name: str, input_schema: Dict[str, ParamAttrs]) -> BaseModel:
48
- # Create a dynamic Pydantic model based on flow input schema
49
- fields = {}
50
-
51
- for param_name, param_config in input_schema.items():
52
- field_type = self.TYPE_MAPPING.get(param_config.type, str)
53
-
54
- if not param_config.required:
55
- fields[param_name] = (Optional[field_type], Field(default=None, description=param_config.description))
56
- else:
57
- fields[param_name] = (field_type, Field(default=..., description=param_config.description))
58
-
59
- return create_model(f"{snake_to_camel(flow_name)}Model", **fields)
60
-
61
- def register_flow(self, flow_name: str):
62
- """Register a flow as an HTTP endpoint"""
63
- flow_config = self.flow_config_dict[flow_name]
64
- request_model = self._create_pydantic_model(flow_name, flow_config.input_schema)
65
-
66
- async def execute_flow_endpoint(request: request_model) -> FlowResponse:
37
+
38
+ def integrate_tool_flow(self, tool_flow_name: str):
39
+ tool_flow: BaseToolFlow = C.get_tool_flow(tool_flow_name)
40
+ request_model = self._create_pydantic_model(tool_flow_name, tool_flow.tool_call.input_schema)
41
+
42
+ async def execute_endpoint(request: request_model) -> FlowResponse:
67
43
  loop = asyncio.get_event_loop()
68
44
  response: FlowResponse = await loop.run_in_executor(
69
45
  executor=C.thread_pool,
70
- func=partial(self.execute_flow, flow_name=flow_name, **request.model_dump())) # noqa
46
+ func=partial(tool_flow.__call__, **request.model_dump())) # noqa
71
47
 
72
48
  return response
73
49
 
74
- endpoint_path = f"/{flow_name}"
75
- self.app.post(endpoint_path, response_model=FlowResponse)(execute_flow_endpoint)
76
- logger.info(f"register flow={flow_name} endpoint={endpoint_path}")
77
-
50
+ endpoint_path = f"/{tool_flow.name}"
51
+ self.app.post(endpoint_path, response_model=FlowResponse)(execute_endpoint)
52
+
53
+ def integrate_tool_flows(self):
54
+ super().integrate_tool_flows()
55
+
56
+ async def execute_endpoint() -> list:
57
+ loop = asyncio.get_event_loop()
58
+
59
+ def list_tool_flows() -> list:
60
+ tool_flow_schemas = []
61
+ for name, tool_flow in C.tool_flow_dict.items():
62
+ assert isinstance(tool_flow, BaseToolFlow)
63
+ tool_flow_schemas.append(tool_flow.tool_call.simple_input_dump())
64
+ return tool_flow_schemas
65
+
66
+ return await loop.run_in_executor(executor=C.thread_pool, func=list_tool_flows) # noqa
67
+
68
+ endpoint_path = "/list"
69
+ self.app.get(endpoint_path, response_model=list)(execute_endpoint)
70
+ logger.info(f"integrate endpoint={endpoint_path}")
71
+
78
72
  def __call__(self):
79
- for flow_name in self.flow_config_dict:
80
- self.register_flow(flow_name)
81
-
82
- # Start the server
73
+ self.integrate_tool_flows()
74
+
83
75
  uvicorn.run(self.app,
84
76
  host=self.http_config.host,
85
77
  port=self.http_config.port,
@@ -3,9 +3,9 @@ from functools import partial
3
3
 
4
4
  from fastmcp import FastMCP
5
5
  from fastmcp.tools import FunctionTool
6
- from loguru import logger
7
6
 
8
7
  from flowllm.context.service_context import C
8
+ from flowllm.flow.base_tool_flow import BaseToolFlow
9
9
  from flowllm.service.base_service import BaseService
10
10
 
11
11
 
@@ -14,31 +14,33 @@ class MCPService(BaseService):
14
14
 
15
15
  def __init__(self, *args, **kwargs):
16
16
  super().__init__(*args, **kwargs)
17
- self.mcp = FastMCP("FlowLLM")
17
+ self.mcp = FastMCP(name="FlowLLM")
18
18
 
19
- def register_flow(self, flow_name: str):
20
- flow_config = self.flow_config_dict[flow_name]
19
+ def integrate_tool_flow(self, tool_flow_name: str):
20
+ tool_flow: BaseToolFlow = C.get_tool_flow(tool_flow_name)
21
+ request_model = self._create_pydantic_model(tool_flow_name, tool_flow.tool_call.input_schema)
21
22
 
22
23
  async def execute_flow_async(**kwargs) -> str:
24
+ request: request_model = request_model(**kwargs)
23
25
  loop = asyncio.get_event_loop()
24
26
  response = await loop.run_in_executor(
25
27
  executor=C.thread_pool,
26
- func=partial(self.execute_flow, flow_name=flow_name, **kwargs)) # noqa
28
+ func=partial(tool_flow.__call__, **request.model_dump())) # noqa
27
29
  return response.answer
28
30
 
29
- tool = FunctionTool(name=flow_name, # noqa
30
- description=flow_config.description, # noqa
31
+ tool = FunctionTool(name=tool_flow.name, # noqa
32
+ description=tool_flow.tool_call.description, # noqa
31
33
  fn=execute_flow_async,
32
- parameters=flow_config.input_schema)
34
+ parameters=tool_flow.tool_call.input_schema)
33
35
  self.mcp.add_tool(tool)
34
- logger.info(f"register flow={flow_name}")
35
36
 
36
37
  def __call__(self):
37
- for flow_name in self.flow_config_dict:
38
- self.register_flow(flow_name)
38
+ self.integrate_tool_flows()
39
39
 
40
40
  if self.mcp_config.transport == "sse":
41
41
  self.mcp.run(transport="sse", host=self.mcp_config.host, port=self.mcp_config.port)
42
+ if self.mcp_config.transport == "http":
43
+ self.mcp.run(transport="http", host=self.mcp_config.host, port=self.mcp_config.port)
42
44
  elif self.mcp_config.transport == "stdio":
43
45
  self.mcp.run(transport="stdio")
44
46
  else:
@@ -0,0 +1 @@
1
+ from .data_cache import DataCache
@@ -0,0 +1,104 @@
1
+ import json
2
+ from abc import ABC, abstractmethod
3
+ from pathlib import Path
4
+ from typing import Dict, Any
5
+
6
+ import pandas as pd
7
+
8
+
9
+ class CacheDataHandler(ABC):
10
+ """Abstract base class for data type handlers"""
11
+
12
+ @abstractmethod
13
+ def save(self, data: Any, file_path: Path, **kwargs) -> Dict[str, Any]:
14
+ """
15
+ Save data to file and return metadata
16
+
17
+ Args:
18
+ data: Data to save
19
+ file_path: File path to save to
20
+ **kwargs: Additional parameters
21
+
22
+ Returns:
23
+ Dict containing metadata about the saved data
24
+ """
25
+ pass
26
+
27
+ @abstractmethod
28
+ def load(self, file_path: Path, **kwargs) -> Any:
29
+ """
30
+ Load data from file
31
+
32
+ Args:
33
+ file_path: File path to load from
34
+ **kwargs: Additional parameters
35
+
36
+ Returns:
37
+ Loaded data
38
+ """
39
+ pass
40
+
41
+ @abstractmethod
42
+ def get_file_extension(self) -> str:
43
+ """Get the file extension for this data type"""
44
+ pass
45
+
46
+
47
+ class DataFrameHandler(CacheDataHandler):
48
+ """Handler for pandas DataFrame data type"""
49
+
50
+ def save(self, data: pd.DataFrame, file_path: Path, **kwargs) -> Dict[str, Any]:
51
+ """Save DataFrame as CSV"""
52
+ csv_params = {
53
+ "index": False,
54
+ "encoding": "utf-8"
55
+ }
56
+ csv_params.update(kwargs)
57
+
58
+ data.to_csv(file_path, **csv_params)
59
+
60
+ return {
61
+ 'row_count': len(data),
62
+ 'column_count': len(data.columns),
63
+ 'file_size': file_path.stat().st_size
64
+ }
65
+
66
+ def load(self, file_path: Path, **kwargs) -> pd.DataFrame:
67
+ """Load DataFrame from CSV"""
68
+ csv_params = {
69
+ 'encoding': 'utf-8'
70
+ }
71
+ csv_params.update(kwargs)
72
+
73
+ return pd.read_csv(file_path, **csv_params)
74
+
75
+ def get_file_extension(self) -> str:
76
+ return ".csv"
77
+
78
+
79
+ class DictHandler(CacheDataHandler):
80
+ """Handler for dict data type"""
81
+
82
+ def save(self, data: dict, file_path: Path, **kwargs) -> Dict[str, Any]:
83
+ """Save dict as JSON"""
84
+ json_params = {
85
+ "ensure_ascii": False,
86
+ "indent": 2
87
+ }
88
+ json_params.update(kwargs)
89
+
90
+ with open(file_path, 'w', encoding='utf-8') as f:
91
+ json.dump(data, f, **json_params)
92
+
93
+ return {
94
+ 'key_count': len(data),
95
+ 'file_size': file_path.stat().st_size
96
+ }
97
+
98
+ def load(self, file_path: Path, **kwargs) -> dict:
99
+ """Load dict from JSON"""
100
+ with open(file_path, 'r', encoding='utf-8') as f:
101
+ return json.load(f)
102
+
103
+ def get_file_extension(self) -> str:
104
+ return ".json"