flowllm 0.1.2__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. flowllm/__init__.py +8 -3
  2. flowllm/app.py +1 -1
  3. flowllm/config/base.yaml +75 -0
  4. flowllm/config/fin_supply.yaml +39 -0
  5. flowllm/config/pydantic_config_parser.py +16 -1
  6. flowllm/context/__init__.py +2 -0
  7. flowllm/context/base_context.py +10 -20
  8. flowllm/context/flow_context.py +45 -2
  9. flowllm/context/service_context.py +73 -12
  10. flowllm/embedding_model/openai_compatible_embedding_model.py +1 -2
  11. flowllm/enumeration/chunk_enum.py +1 -0
  12. flowllm/flow/__init__.py +9 -0
  13. flowllm/flow/base_flow.py +44 -11
  14. flowllm/flow/expression/__init__.py +1 -0
  15. flowllm/flow/{parser → expression}/expression_parser.py +5 -2
  16. flowllm/flow/expression/expression_tool_flow.py +25 -0
  17. flowllm/flow/gallery/__init__.py +1 -8
  18. flowllm/flow/gallery/mock_tool_flow.py +46 -33
  19. flowllm/flow/tool_op_flow.py +97 -0
  20. flowllm/llm/base_llm.py +0 -2
  21. flowllm/llm/litellm_llm.py +2 -1
  22. flowllm/op/__init__.py +3 -3
  23. flowllm/op/akshare/get_ak_a_code_op.py +1 -1
  24. flowllm/op/akshare/get_ak_a_info_op.py +1 -1
  25. flowllm/op/base_llm_op.py +3 -2
  26. flowllm/op/base_op.py +258 -25
  27. flowllm/op/base_tool_op.py +47 -0
  28. flowllm/op/gallery/__init__.py +0 -1
  29. flowllm/op/gallery/mock_op.py +13 -7
  30. flowllm/op/llm/__init__.py +3 -0
  31. flowllm/op/llm/react_llm_op.py +105 -0
  32. flowllm/op/{agent/react_prompt.yaml → llm/react_llm_prompt.yaml} +17 -10
  33. flowllm/op/llm/simple_llm_op.py +48 -0
  34. flowllm/op/llm/stream_llm_op.py +61 -0
  35. flowllm/op/mcp/__init__.py +2 -0
  36. flowllm/op/mcp/ant_op.py +42 -0
  37. flowllm/op/mcp/base_sse_mcp_op.py +28 -0
  38. flowllm/op/parallel_op.py +5 -1
  39. flowllm/op/search/__init__.py +1 -2
  40. flowllm/op/search/dashscope_search_op.py +73 -121
  41. flowllm/op/search/tavily_search_op.py +69 -80
  42. flowllm/op/sequential_op.py +4 -0
  43. flowllm/schema/flow_stream_chunk.py +11 -0
  44. flowllm/schema/message.py +2 -0
  45. flowllm/schema/service_config.py +8 -3
  46. flowllm/schema/tool_call.py +53 -4
  47. flowllm/service/__init__.py +0 -1
  48. flowllm/service/base_service.py +31 -14
  49. flowllm/service/http_service.py +46 -37
  50. flowllm/service/mcp_service.py +17 -23
  51. flowllm/storage/vector_store/__init__.py +1 -0
  52. flowllm/storage/vector_store/base_vector_store.py +99 -12
  53. flowllm/storage/vector_store/chroma_vector_store.py +250 -8
  54. flowllm/storage/vector_store/es_vector_store.py +291 -35
  55. flowllm/storage/vector_store/local_vector_store.py +206 -9
  56. flowllm/storage/vector_store/memory_vector_store.py +509 -0
  57. flowllm/utils/common_utils.py +54 -0
  58. flowllm/utils/logger_utils.py +28 -0
  59. flowllm/utils/miner_u_pdf_processor.py +726 -0
  60. {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/METADATA +7 -6
  61. flowllm-0.1.5.dist-info/RECORD +98 -0
  62. flowllm/config/default.yaml +0 -77
  63. flowllm/config/empty.yaml +0 -37
  64. flowllm/flow/gallery/cmd_flow.py +0 -11
  65. flowllm/flow/gallery/code_tool_flow.py +0 -30
  66. flowllm/flow/gallery/dashscope_search_tool_flow.py +0 -34
  67. flowllm/flow/gallery/deepsearch_tool_flow.py +0 -39
  68. flowllm/flow/gallery/expression_tool_flow.py +0 -18
  69. flowllm/flow/gallery/tavily_search_tool_flow.py +0 -30
  70. flowllm/flow/gallery/terminate_tool_flow.py +0 -30
  71. flowllm/flow/parser/__init__.py +0 -0
  72. flowllm/op/agent/__init__.py +0 -0
  73. flowllm/op/agent/react_op.py +0 -83
  74. flowllm/op/base_ray_op.py +0 -313
  75. flowllm/op/code/__init__.py +0 -1
  76. flowllm/op/code/execute_code_op.py +0 -42
  77. flowllm/op/gallery/terminate_op.py +0 -29
  78. flowllm/op/search/dashscope_deep_research_op.py +0 -260
  79. flowllm/service/cmd_service.py +0 -15
  80. flowllm-0.1.2.dist-info/RECORD +0 -99
  81. {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/WHEEL +0 -0
  82. {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/entry_points.txt +0 -0
  83. {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/licenses/LICENSE +0 -0
  84. {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,17 @@
1
+ import asyncio
1
2
  import os
2
- import time
3
3
  from typing import Dict, Any, List
4
4
 
5
5
  import dashscope
6
6
  from loguru import logger
7
7
 
8
- from flowllm.context.flow_context import FlowContext
9
- from flowllm.context.service_context import C
10
- from flowllm.op.base_llm_op import BaseLLMOp
11
- from flowllm.storage.cache.data_cache import DataCache
8
+ from flowllm.context import FlowContext, C
9
+ from flowllm.op.base_tool_op import BaseToolOp
10
+ from flowllm.schema.tool_call import ToolCall
12
11
 
13
12
 
14
13
  @C.register_op()
15
- class DashscopeSearchOp(BaseLLMOp):
14
+ class DashscopeSearchOp(BaseToolOp):
16
15
  file_path: str = __file__
17
16
 
18
17
  """
@@ -24,29 +23,31 @@ class DashscopeSearchOp(BaseLLMOp):
24
23
 
25
24
  def __init__(self,
26
25
  model: str = "qwen-plus",
27
- enable_print: bool = True,
28
- enable_cache: bool = False,
29
- cache_path: str = "./dashscope_search_cache",
30
- cache_expire_hours: float = 0.1,
31
- max_retries: int = 3,
32
26
  search_strategy: str = "max",
33
- return_only_content: bool = True,
34
27
  enable_role_prompt: bool = True,
28
+ save_answer: bool = False,
35
29
  **kwargs):
36
30
  super().__init__(**kwargs)
37
31
 
38
- self.model = model
39
- self.enable_print = enable_print
40
- self.enable_cache = enable_cache
41
- self.cache_expire_hours = cache_expire_hours
42
- self.max_retries = max_retries
43
- self.search_strategy = search_strategy
44
- self.return_only_content = return_only_content
45
- self.enable_role_prompt = enable_role_prompt
46
-
47
- # Ensure API key is available
48
- self.api_key = os.getenv("FLOW_DASHSCOPE_API_KEY")
49
- self.cache = DataCache(cache_path) if self.enable_cache else None
32
+ self.model: str = model
33
+ self.search_strategy: str = search_strategy
34
+ self.enable_role_prompt: bool = enable_role_prompt
35
+ self.save_answer: bool = save_answer
36
+
37
+ self.api_key = os.getenv("FLOW_DASHSCOPE_API_KEY", "")
38
+
39
+ def build_tool_call(self) -> ToolCall:
40
+ return ToolCall(**{
41
+ "name": "web_search",
42
+ "description": "Use search keywords to retrieve relevant information from the internet. If there are multiple search keywords, please use each keyword separately to call this tool.",
43
+ "input_schema": {
44
+ "query": {
45
+ "type": "str",
46
+ "description": "search keyword",
47
+ "required": True
48
+ }
49
+ }
50
+ })
50
51
 
51
52
  @staticmethod
52
53
  def format_search_results(search_results: List[Dict[str, Any]]) -> str:
@@ -58,122 +59,73 @@ class DashscopeSearchOp(BaseLLMOp):
58
59
 
59
60
  return "\n".join(formatted_results)
60
61
 
61
- def post_process(self, response_data: dict) -> dict:
62
- """Post-process the response and optionally print results"""
63
- if self.enable_print:
64
- # Print search information
65
- if "search_results" in response_data:
66
- search_info = self.format_search_results(response_data["search_results"])
67
- logger.info(f"Search Information:\n{search_info}")
68
-
69
- # Print response content
70
- if "response_content" in response_data:
71
- logger.info("=" * 20 + " Response Content " + "=" * 20)
72
- logger.info(response_data["response_content"])
73
-
74
- return response_data
62
+ def default_execute(self):
63
+ self.output_dict["dashscope_search_result"] = "dashscope search failed!"
75
64
 
76
- def execute(self):
77
- """Execute the Dashscope search operation"""
78
- # Get query from context - support multiple parameter names
79
- query = self.context.query
65
+ async def async_execute(self):
66
+ query: str = self.input_dict["query"]
80
67
 
81
- # Check cache first
82
68
  if self.enable_cache and self.cache:
83
69
  cached_result = self.cache.load(query)
84
70
  if cached_result:
85
- result = self.post_process(cached_result)
86
- if self.return_only_content:
87
- self.context.dashscope_search_result = result["response_content"]
88
- else:
89
- self.context.dashscope_search_result = result
90
-
71
+ self.output_dict["dashscope_search_result"] = cached_result["response_content"]
91
72
  return
92
73
 
93
74
  if self.enable_role_prompt:
94
75
  user_query = self.prompt_format(prompt_name="role_prompt", query=query)
95
76
  else:
96
77
  user_query = query
78
+ logger.info(f"user_query={user_query}")
97
79
  messages: list = [{"role": "user", "content": user_query}]
98
80
 
99
- # Retry logic for API calls
100
- for attempt in range(self.max_retries):
101
- try:
102
- # Call Dashscope Generation API with search enabled
103
- response = dashscope.Generation.call(
104
- api_key=self.api_key,
105
- model=self.model,
106
- messages=messages,
107
- enable_search=True, # Enable web search
108
- search_options={
109
- "forced_search": True, # Force web search
110
- "enable_source": True, # Include search source information
111
- "enable_citation": False, # Enable citation markers
112
- "search_strategy": self.search_strategy, # Search strategy
113
- },
114
- result_format="message",
115
- )
116
-
117
- # Extract search results and response content
118
- search_results = []
119
- response_content = ""
120
-
121
- if hasattr(response, 'output') and response.output:
122
- # Extract search information
123
- if hasattr(response.output, 'search_info') and response.output.search_info:
124
- search_results = response.output.search_info.get("search_results", [])
125
-
126
- # Extract response content
127
- if (hasattr(response.output, 'choices') and
128
- response.output.choices and
129
- len(response.output.choices) > 0):
130
- response_content = response.output.choices[0].message.content
131
-
132
- # Prepare final result
133
- final_result = {
134
- "query": query,
135
- "search_results": search_results,
136
- "response_content": response_content,
137
- "model": self.model,
138
- "search_strategy": self.search_strategy
139
- }
140
-
141
- # Cache the result if enabled
142
- if self.enable_cache and self.cache:
143
- self.cache.save(query, final_result, expire_hours=self.cache_expire_hours)
81
+ response = await dashscope.AioGeneration.call(
82
+ api_key=self.api_key,
83
+ model=self.model,
84
+ messages=messages,
85
+ enable_search=True, # Enable web search
86
+ search_options={
87
+ "forced_search": True, # Force web search
88
+ "enable_source": True, # Include search source information
89
+ "enable_citation": False, # Enable citation markers
90
+ "search_strategy": self.search_strategy, # Search strategy
91
+ },
92
+ result_format="message",
93
+ )
94
+
95
+ search_results = []
96
+ response_content = ""
97
+
98
+ if hasattr(response, "output") and response.output:
99
+ if hasattr(response.output, "search_info") and response.output.search_info:
100
+ search_results = response.output.search_info.get("search_results", [])
101
+
102
+ if hasattr(response.output, "choices") and response.output.choices and len(response.output.choices) > 0:
103
+ response_content = response.output.choices[0].message.content
104
+
105
+ final_result = {
106
+ "query": query,
107
+ "search_results": search_results,
108
+ "response_content": response_content,
109
+ "model": self.model,
110
+ "search_strategy": self.search_strategy
111
+ }
144
112
 
145
- # Post-process and set context
146
- result = self.post_process(final_result)
147
- if self.return_only_content:
148
- self.context.dashscope_search_result = result["response_content"]
149
- else:
150
- self.context.dashscope_search_result = result
151
-
152
- return
153
-
154
- except Exception as e:
155
- logger.warning(f"Dashscope search attempt {attempt + 1} failed for query='{query}': {e}")
156
- if attempt < self.max_retries - 1:
157
- time.sleep(attempt + 1) # Exponential backoff
158
- else:
159
- logger.error(f"All {self.max_retries} attempts failed for Dashscope search")
160
-
161
- self.context.dashscope_search_result = "dashscope_search failed"
162
-
163
-
164
- def main():
165
- from flowllm.utils.common_utils import load_env
113
+ if self.enable_cache and self.cache:
114
+ self.cache.save(query, final_result, expire_hours=self.cache_expire_hours)
166
115
 
167
- load_env()
116
+ self.output_dict["dashscope_search_result"] = final_result["response_content"]
117
+ if self.save_answer:
118
+ self.context.response.answer = final_result["response_content"]
168
119
 
169
- C.set_default_service_config().init_by_service_config()
170
120
 
171
- op = DashscopeSearchOp(enable_print=True, enable_cache=False)
121
+ async def async_main():
122
+ C.set_service_config().init_by_service_config()
172
123
 
173
- context = FlowContext(query="杭州明天天气")
174
- op(context=context)
124
+ op = DashscopeSearchOp()
125
+ context = FlowContext(query="what is AI?")
126
+ await op.async_call(context=context)
175
127
  print(context.dashscope_search_result)
176
128
 
177
129
 
178
130
  if __name__ == "__main__":
179
- main()
131
+ asyncio.run(async_main())
@@ -1,102 +1,91 @@
1
+ import asyncio
1
2
  import json
2
3
  import os
3
- import time
4
- from typing import Literal
4
+ from functools import partial
5
+ from typing import Union, List
5
6
 
6
- from loguru import logger
7
7
  from tavily import TavilyClient
8
8
 
9
- from flowllm.context.flow_context import FlowContext
10
- from flowllm.context.service_context import C
11
- from flowllm.op.base_op import BaseOp
12
- from flowllm.storage.cache.data_cache import DataCache
9
+ from flowllm.context import FlowContext, C
10
+ from flowllm.op.base_tool_op import BaseToolOp
11
+ from flowllm.schema.tool_call import ToolCall
13
12
 
14
13
 
15
14
  @C.register_op()
16
- class TavilySearchOp(BaseOp):
17
- def __init__(self,
18
- enable_print: bool = True,
19
- enable_cache: bool = False,
20
- cache_path: str = "./web_search_cache",
21
- cache_expire_hours: float = 0.1,
22
- topic: Literal["general", "news", "finance"] = "general",
23
- max_retries: int = 3,
24
- return_only_content: bool = True,
25
- **kwargs):
15
+ class TavilySearchOp(BaseToolOp):
16
+ def __init__(self, save_answer: bool = False, **kwargs):
26
17
  super().__init__(**kwargs)
18
+ self.save_answer = save_answer
19
+ self._client: TavilyClient | None = None
20
+
21
+ def build_tool_call(self) -> ToolCall:
22
+ return ToolCall(**{
23
+ "name": "web_search",
24
+ "description": "Use search keywords to retrieve relevant information from the internet. If there are multiple search keywords, please use each keyword separately to call this tool.",
25
+ "input_schema": {
26
+ "query": {
27
+ "type": "str",
28
+ "description": "search keyword",
29
+ "required": True
30
+ }
31
+ }
32
+ })
33
+
34
+ @property
35
+ def client(self):
36
+ if self._client is None:
37
+ self._client = TavilyClient(api_key=os.environ["FLOW_TAVILY_API_KEY"])
38
+ return self._client
39
+
40
+ def default_execute(self):
41
+ self.output_dict["tavily_search_result"] = "tavily search failed!"
42
+
43
+ async def search(self, query: str):
44
+ loop = asyncio.get_event_loop()
45
+ func = partial(self.client.search, query=query)
46
+ task = loop.run_in_executor(executor=C.thread_pool, func=func) # noqa
47
+ return await task
48
+
49
+ async def extract(self, urls: Union[List[str], str]):
50
+ loop = asyncio.get_event_loop()
51
+ func = partial(self.client.extract, urls=urls, format="text")
52
+ task = loop.run_in_executor(executor=C.thread_pool, func=func) # noqa
53
+ return await task
54
+
55
+ async def async_execute(self):
56
+ query: str = self.input_dict["query"]
27
57
 
28
- self.enable_print = enable_print
29
- self.enable_cache = enable_cache
30
- self.cache_expire_hours = cache_expire_hours
31
- self.topic = topic
32
- self.max_retries = max_retries
33
- self.return_only_content = return_only_content
34
-
35
- # Initialize DataCache if caching is enabled
36
- self.cache = DataCache(cache_path) if self.enable_cache else None
37
- self._client = TavilyClient(api_key=os.getenv("FLOW_TAVILY_API_KEY", ""))
38
-
39
- def post_process(self, response):
40
- if self.enable_print:
41
- logger.info("response=\n" + json.dumps(response, indent=2, ensure_ascii=False))
42
-
43
- return response
44
-
45
- def execute(self):
46
- # Get query from context
47
- query: str = self.context.query
48
-
49
- # Check cache first
50
58
  if self.enable_cache and self.cache:
51
59
  cached_result = self.cache.load(query)
52
60
  if cached_result:
53
- final_result = self.post_process(cached_result)
54
- if self.return_only_content:
55
- self.context.tavily_search_result = json.dumps(final_result, ensure_ascii=False, indent=2)
56
- else:
57
- self.context.tavily_search_result = final_result
61
+ self.output_dict["tavily_search_result"] = json.dumps(cached_result, ensure_ascii=False, indent=2)
58
62
  return
59
63
 
60
- for i in range(self.max_retries):
61
- try:
62
- response = self._client.search(query=query, topic=self.topic)
63
- url_info_dict = {item["url"]: item for item in response["results"]}
64
- response_extract = self._client.extract(urls=[item["url"] for item in response["results"]],
65
- format="text")
66
-
67
- final_result = {}
68
- for item in response_extract["results"]:
69
- url = item["url"]
70
- final_result[url] = url_info_dict[url]
71
- final_result[url]["raw_content"] = item["raw_content"]
72
-
73
- # Cache the result if enabled
74
- if self.enable_cache and self.cache:
75
- self.cache.save(query, final_result, expire_hours=self.cache_expire_hours)
76
-
77
- final_result = self.post_process(final_result)
78
-
79
- if self.return_only_content:
80
- self.context.tavily_search_result = json.dumps(final_result, ensure_ascii=False, indent=2)
81
- else:
82
- self.context.tavily_search_result = final_result
83
- return
64
+ response = await self.search(query=query)
65
+ url_info_dict = {item["url"]: item for item in response["results"]}
66
+ response_extract = await self.extract(urls=[item["url"] for item in response["results"]])
84
67
 
85
- except Exception as e:
86
- logger.exception(f"tavily search with query={query} encounter error with e={e.args}")
87
- time.sleep(i + 1)
68
+ final_result = {}
69
+ for item in response_extract["results"]:
70
+ url = item["url"]
71
+ final_result[url] = url_info_dict[url]
72
+ final_result[url]["raw_content"] = item["raw_content"]
88
73
 
89
- self.context.tavily_search_result = "tavily search failed!"
74
+ if self.enable_cache and self.cache is not None:
75
+ self.cache.save(query, final_result, expire_hours=self.cache_expire_hours)
90
76
 
77
+ self.output_dict["tavily_search_result"] = json.dumps(final_result, ensure_ascii=False, indent=2)
78
+ if self.save_answer:
79
+ self.context.response.answer = self.output_dict["tavily_search_result"]
91
80
 
92
- if __name__ == "__main__":
93
- from flowllm.utils.common_utils import load_env
81
+ async def async_main():
82
+ C.set_service_config().init_by_service_config()
94
83
 
95
- load_env()
84
+ op = TavilySearchOp()
85
+ context = FlowContext(query="what is AI?")
86
+ await op.async_call(context=context)
87
+ print(context.tavily_search_result)
96
88
 
97
- C.set_default_service_config().init_by_service_config()
98
89
 
99
- op = TavilySearchOp(enable_cache=True)
100
- context = FlowContext(query="A股医药为什么一直涨")
101
- op(context=context)
102
- print(context.tavily_search_result)
90
+ if __name__ == "__main__":
91
+ asyncio.run(async_main())
@@ -13,6 +13,10 @@ class SequentialOp(BaseOp):
13
13
  for op in self.ops:
14
14
  op.__call__(self.context)
15
15
 
16
+ async def async_execute(self):
17
+ for op in self.ops:
18
+ await op.async_call(self.context)
19
+
16
20
  def __rshift__(self, op: BaseOp):
17
21
  if isinstance(op, SequentialOp):
18
22
  self.ops.extend(op.ops)
@@ -0,0 +1,11 @@
1
+ from pydantic import Field, BaseModel
2
+
3
+ from flowllm.enumeration.chunk_enum import ChunkEnum
4
+
5
+
6
+ class FlowStreamChunk(BaseModel):
7
+ flow_id: str = Field(default="")
8
+ chunk_type: ChunkEnum = Field(default=ChunkEnum.ANSWER)
9
+ chunk: str | bytes = Field(default="")
10
+ done: bool = Field(default=False)
11
+ metadata: dict = Field(default_factory=dict)
flowllm/schema/message.py CHANGED
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  from typing import List
2
3
 
3
4
  from pydantic import BaseModel, Field
@@ -12,6 +13,7 @@ class Message(BaseModel):
12
13
  reasoning_content: str = Field(default="")
13
14
  tool_calls: List[ToolCall] = Field(default_factory=list)
14
15
  tool_call_id: str = Field(default="")
16
+ time_created: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
15
17
  metadata: dict = Field(default_factory=dict)
16
18
 
17
19
  def simple_dump(self, add_reason_content: bool = True) -> dict:
@@ -1,4 +1,4 @@
1
- from typing import Dict
1
+ from typing import Dict, List
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
@@ -6,7 +6,7 @@ from flowllm.schema.tool_call import ToolCall
6
6
 
7
7
 
8
8
  class MCPConfig(BaseModel):
9
- transport: str = Field(default="", description="stdio/http/sse/streamable-http")
9
+ transport: str = Field(default="", description="stdio/http/sse")
10
10
  host: str = Field(default="0.0.0.0")
11
11
  port: int = Field(default=8001)
12
12
 
@@ -25,11 +25,14 @@ class CmdConfig(BaseModel):
25
25
 
26
26
  class FlowConfig(ToolCall):
27
27
  flow_content: str = Field(default="")
28
- service_type: str = Field(default="all", description="all/http/mcp/cmd")
28
+ use_async: bool = Field(default=True)
29
+ service_type: str = Field(default="http+mcp", description="http+mcp/http/mcp")
30
+ stream: bool = Field(default=False)
29
31
 
30
32
  class OpConfig(BaseModel):
31
33
  backend: str = Field(default="")
32
34
  language: str = Field(default="")
35
+ max_retries: int = Field(default=1)
33
36
  raise_exception: bool = Field(default=True)
34
37
  prompt_path: str = Field(default="")
35
38
  llm: str = Field(default="")
@@ -61,6 +64,8 @@ class ServiceConfig(BaseModel):
61
64
  language: str = Field(default="")
62
65
  thread_pool_max_workers: int = Field(default=16)
63
66
  ray_max_workers: int = Field(default=1)
67
+ import_config: str = Field(default="", description="Import the configuration in the same path as the base")
68
+ disabled_flows: List[str] = Field(default_factory=list)
64
69
 
65
70
  cmd: CmdConfig = Field(default_factory=CmdConfig)
66
71
  mcp: MCPConfig = Field(default_factory=MCPConfig)
@@ -1,7 +1,8 @@
1
1
  import json
2
2
  from typing import Dict, List
3
3
 
4
- from pydantic import BaseModel, Field
4
+ from mcp.types import Tool
5
+ from pydantic import BaseModel, Field, model_validator
5
6
 
6
7
 
7
8
  class ParamAttrs(BaseModel):
@@ -58,12 +59,27 @@ class ToolCall(BaseModel):
58
59
  type: str = Field(default="function")
59
60
  name: str = Field(default="")
60
61
 
61
- arguments: dict = Field(default_factory=dict, description="tool execution arguments")
62
+ arguments: str = Field(default="", description="tool execution arguments")
62
63
 
63
64
  description: str = Field(default="")
64
65
  input_schema: Dict[str, ParamAttrs] = Field(default_factory=dict)
65
66
  output_schema: Dict[str, ParamAttrs] = Field(default_factory=dict)
66
67
 
68
+ @model_validator(mode="before")
69
+ @classmethod
70
+ def init_tool_call(cls, data: dict):
71
+ tool_type = data.get("type", "")
72
+ tool_type_dict = data.get(tool_type, {})
73
+
74
+ for key in ["name", "arguments"]:
75
+ if key not in data:
76
+ data[key] = tool_type_dict.get(key, "")
77
+ return data
78
+
79
+ @property
80
+ def argument_dict(self) -> dict:
81
+ return json.loads(self.arguments)
82
+
67
83
  def simple_input_dump(self, version: str = "v1") -> dict:
68
84
  if version == "v1":
69
85
  required_list = [name for name, tool_param in self.input_schema.items() if tool_param.required]
@@ -91,7 +107,7 @@ class ToolCall(BaseModel):
91
107
  "index": self.index,
92
108
  "id": self.id,
93
109
  self.type: {
94
- "arguments": json.dumps(self.arguments, ensure_ascii=False),
110
+ "arguments": self.arguments,
95
111
  "name": self.name
96
112
  },
97
113
  "type": self.type,
@@ -111,8 +127,41 @@ class ToolCall(BaseModel):
111
127
  if name:
112
128
  self.name = name
113
129
  if arguments:
114
- self.arguments = json.loads(arguments)
130
+ self.arguments = arguments
115
131
  else:
116
132
  raise NotImplementedError(f"version {version} not supported")
117
133
 
118
134
  return self
135
+
136
+ @classmethod
137
+ def from_mcp_tool(cls, tool: Tool) -> "ToolCall":
138
+ input_schema = {}
139
+ properties = tool.inputSchema["properties"]
140
+ required = tool.inputSchema["required"]
141
+ for name, attr_dict in properties.items():
142
+ param_attrs = ParamAttrs()
143
+
144
+ if name in required:
145
+ param_attrs.required = True
146
+ param_attrs.type = attr_dict.get("type", "str")
147
+ param_attrs.description = attr_dict.get("description", "")
148
+ if "enum" in attr_dict:
149
+ param_attrs.enum = attr_dict["enum"]
150
+ input_schema[name] = param_attrs
151
+
152
+ return cls(name=tool.name,
153
+ description=tool.description,
154
+ input_schema=input_schema)
155
+
156
+
157
+ if __name__ == "__main__":
158
+ tool_call = ToolCall(**{
159
+ "id": "call_0fb6077ad56f4647b0b04a",
160
+ "function": {
161
+ "arguments": "{\"symbol\": \"ZETA\"}",
162
+ "name": "get_stock_info"
163
+ },
164
+ "type": "function",
165
+ "index": 0
166
+ })
167
+ print(tool_call.simple_output_dump())
@@ -1,3 +1,2 @@
1
- from .cmd_service import CmdService
2
1
  from .http_service import HttpService
3
2
  from .mcp_service import MCPService