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.
- flowllm/__init__.py +8 -3
- flowllm/app.py +1 -1
- flowllm/config/base.yaml +75 -0
- flowllm/config/fin_supply.yaml +39 -0
- flowllm/config/pydantic_config_parser.py +16 -1
- flowllm/context/__init__.py +2 -0
- flowllm/context/base_context.py +10 -20
- flowllm/context/flow_context.py +45 -2
- flowllm/context/service_context.py +73 -12
- flowllm/embedding_model/openai_compatible_embedding_model.py +1 -2
- flowllm/enumeration/chunk_enum.py +1 -0
- flowllm/flow/__init__.py +9 -0
- flowllm/flow/base_flow.py +44 -11
- flowllm/flow/expression/__init__.py +1 -0
- flowllm/flow/{parser → expression}/expression_parser.py +5 -2
- flowllm/flow/expression/expression_tool_flow.py +25 -0
- flowllm/flow/gallery/__init__.py +1 -8
- flowllm/flow/gallery/mock_tool_flow.py +46 -33
- flowllm/flow/tool_op_flow.py +97 -0
- flowllm/llm/base_llm.py +0 -2
- flowllm/llm/litellm_llm.py +2 -1
- flowllm/op/__init__.py +3 -3
- flowllm/op/akshare/get_ak_a_code_op.py +1 -1
- flowllm/op/akshare/get_ak_a_info_op.py +1 -1
- flowllm/op/base_llm_op.py +3 -2
- flowllm/op/base_op.py +258 -25
- flowllm/op/base_tool_op.py +47 -0
- flowllm/op/gallery/__init__.py +0 -1
- flowllm/op/gallery/mock_op.py +13 -7
- flowllm/op/llm/__init__.py +3 -0
- flowllm/op/llm/react_llm_op.py +105 -0
- flowllm/op/{agent/react_prompt.yaml → llm/react_llm_prompt.yaml} +17 -10
- flowllm/op/llm/simple_llm_op.py +48 -0
- flowllm/op/llm/stream_llm_op.py +61 -0
- flowllm/op/mcp/__init__.py +2 -0
- flowllm/op/mcp/ant_op.py +42 -0
- flowllm/op/mcp/base_sse_mcp_op.py +28 -0
- flowllm/op/parallel_op.py +5 -1
- flowllm/op/search/__init__.py +1 -2
- flowllm/op/search/dashscope_search_op.py +73 -121
- flowllm/op/search/tavily_search_op.py +69 -80
- flowllm/op/sequential_op.py +4 -0
- flowllm/schema/flow_stream_chunk.py +11 -0
- flowllm/schema/message.py +2 -0
- flowllm/schema/service_config.py +8 -3
- flowllm/schema/tool_call.py +53 -4
- flowllm/service/__init__.py +0 -1
- flowllm/service/base_service.py +31 -14
- flowllm/service/http_service.py +46 -37
- flowllm/service/mcp_service.py +17 -23
- flowllm/storage/vector_store/__init__.py +1 -0
- flowllm/storage/vector_store/base_vector_store.py +99 -12
- flowllm/storage/vector_store/chroma_vector_store.py +250 -8
- flowllm/storage/vector_store/es_vector_store.py +291 -35
- flowllm/storage/vector_store/local_vector_store.py +206 -9
- flowllm/storage/vector_store/memory_vector_store.py +509 -0
- flowllm/utils/common_utils.py +54 -0
- flowllm/utils/logger_utils.py +28 -0
- flowllm/utils/miner_u_pdf_processor.py +726 -0
- {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/METADATA +7 -6
- flowllm-0.1.5.dist-info/RECORD +98 -0
- flowllm/config/default.yaml +0 -77
- flowllm/config/empty.yaml +0 -37
- flowllm/flow/gallery/cmd_flow.py +0 -11
- flowllm/flow/gallery/code_tool_flow.py +0 -30
- flowllm/flow/gallery/dashscope_search_tool_flow.py +0 -34
- flowllm/flow/gallery/deepsearch_tool_flow.py +0 -39
- flowllm/flow/gallery/expression_tool_flow.py +0 -18
- flowllm/flow/gallery/tavily_search_tool_flow.py +0 -30
- flowllm/flow/gallery/terminate_tool_flow.py +0 -30
- flowllm/flow/parser/__init__.py +0 -0
- flowllm/op/agent/__init__.py +0 -0
- flowllm/op/agent/react_op.py +0 -83
- flowllm/op/base_ray_op.py +0 -313
- flowllm/op/code/__init__.py +0 -1
- flowllm/op/code/execute_code_op.py +0 -42
- flowllm/op/gallery/terminate_op.py +0 -29
- flowllm/op/search/dashscope_deep_research_op.py +0 -260
- flowllm/service/cmd_service.py +0 -15
- flowllm-0.1.2.dist-info/RECORD +0 -99
- {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/WHEEL +0 -0
- {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/entry_points.txt +0 -0
- {flowllm-0.1.2.dist-info → flowllm-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {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
|
9
|
-
from flowllm.
|
10
|
-
from flowllm.
|
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(
|
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.
|
40
|
-
self.
|
41
|
-
self.
|
42
|
-
|
43
|
-
self.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
62
|
-
""
|
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
|
77
|
-
|
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
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
search_results = []
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
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
|
-
|
121
|
+
async def async_main():
|
122
|
+
C.set_service_config().init_by_service_config()
|
172
123
|
|
173
|
-
|
174
|
-
|
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
|
-
|
131
|
+
asyncio.run(async_main())
|
@@ -1,102 +1,91 @@
|
|
1
|
+
import asyncio
|
1
2
|
import json
|
2
3
|
import os
|
3
|
-
import
|
4
|
-
from typing import
|
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
|
10
|
-
from flowllm.
|
11
|
-
from flowllm.
|
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(
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
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.
|
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
|
-
|
93
|
-
|
81
|
+
async def async_main():
|
82
|
+
C.set_service_config().init_by_service_config()
|
94
83
|
|
95
|
-
|
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
|
-
|
100
|
-
|
101
|
-
op(context=context)
|
102
|
-
print(context.tavily_search_result)
|
90
|
+
if __name__ == "__main__":
|
91
|
+
asyncio.run(async_main())
|
flowllm/op/sequential_op.py
CHANGED
@@ -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:
|
flowllm/schema/service_config.py
CHANGED
@@ -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
|
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
|
-
|
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)
|
flowllm/schema/tool_call.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import json
|
2
2
|
from typing import Dict, List
|
3
3
|
|
4
|
-
from
|
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:
|
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":
|
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 =
|
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())
|
flowllm/service/__init__.py
CHANGED