flowllm 0.1.3__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 +4 -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 +69 -10
- 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 -13
- 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 -28
- flowllm/flow/tool_op_flow.py +97 -0
- flowllm/llm/base_llm.py +0 -2
- flowllm/op/__init__.py +3 -4
- 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_op.py +232 -16
- 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/{agent/react_v2_op.py → llm/react_llm_op.py} +43 -24
- 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 -128
- flowllm/op/search/tavily_search_op.py +64 -82
- flowllm/op/sequential_op.py +4 -0
- flowllm/schema/flow_stream_chunk.py +11 -0
- flowllm/schema/service_config.py +8 -3
- flowllm/schema/tool_call.py +46 -1
- flowllm/service/__init__.py +0 -1
- flowllm/service/base_service.py +31 -14
- flowllm/service/http_service.py +45 -36
- flowllm/service/mcp_service.py +17 -23
- flowllm/storage/vector_store/__init__.py +1 -0
- flowllm/storage/vector_store/base_vector_store.py +99 -15
- flowllm/storage/vector_store/chroma_vector_store.py +250 -8
- flowllm/storage/vector_store/es_vector_store.py +288 -32
- 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/miner_u_pdf_processor.py +726 -0
- {flowllm-0.1.3.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 -1
- flowllm/op/agent/react_v1_op.py +0 -109
- flowllm/op/agent/react_v1_prompt.yaml +0 -54
- 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 -267
- flowllm/service/cmd_service.py +0 -15
- flowllm-0.1.3.dist-info/RECORD +0 -102
- /flowllm/op/{agent/react_v2_prompt.yaml → llm/react_llm_prompt.yaml} +0 -0
- {flowllm-0.1.3.dist-info → flowllm-0.1.5.dist-info}/WHEEL +0 -0
- {flowllm-0.1.3.dist-info → flowllm-0.1.5.dist-info}/entry_points.txt +0 -0
- {flowllm-0.1.3.dist-info → flowllm-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {flowllm-0.1.3.dist-info → flowllm-0.1.5.dist-info}/top_level.txt +0 -0
@@ -7,56 +7,74 @@ from flowllm.schema.tool_call import ToolCall, ParamAttrs
|
|
7
7
|
@C.register_tool_flow()
|
8
8
|
class MockToolFlow(BaseToolFlow):
|
9
9
|
|
10
|
+
def __init__(self,
|
11
|
+
use_async: bool = False,
|
12
|
+
stream: bool = False,
|
13
|
+
service_type: str = "http+mcp",
|
14
|
+
**kwargs):
|
15
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
16
|
+
|
10
17
|
def build_flow(self):
|
11
18
|
mock1_op = Mock1Op()
|
12
19
|
mock2_op = Mock2Op()
|
13
20
|
mock3_op = Mock3Op()
|
14
|
-
mock4_op = Mock4Op()
|
15
|
-
mock5_op = Mock5Op()
|
16
|
-
mock6_op = Mock6Op()
|
17
21
|
|
18
|
-
op = mock1_op >> ((
|
22
|
+
op = mock1_op >> ((mock2_op >> mock3_op) | mock1_op) >> (mock2_op | mock3_op)
|
19
23
|
return op
|
20
24
|
|
21
25
|
def build_tool_call(self) -> ToolCall:
|
22
26
|
return ToolCall(**{
|
23
|
-
"index": 0,
|
24
|
-
"id": "call_mock_tool_12345",
|
25
27
|
"type": "function",
|
26
|
-
"name": "
|
28
|
+
"name": "mock_data",
|
27
29
|
"description": "A mock tool that processes data through multiple operations and returns structured results",
|
28
30
|
"input_schema": {
|
29
|
-
"
|
30
|
-
type="
|
31
|
+
"a": ParamAttrs(
|
32
|
+
type="str",
|
31
33
|
description="The input data to be processed",
|
32
34
|
required=True
|
33
35
|
),
|
34
|
-
"
|
36
|
+
"b": ParamAttrs(
|
35
37
|
type="string",
|
36
38
|
description="Processing mode: basic, advanced, or expert",
|
37
39
|
required=False
|
38
40
|
),
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
}
|
42
|
+
})
|
43
|
+
|
44
|
+
|
45
|
+
@C.register_tool_flow()
|
46
|
+
class MockAsyncToolFlow(BaseToolFlow):
|
47
|
+
|
48
|
+
def __init__(self,
|
49
|
+
use_async: bool = True,
|
50
|
+
stream: bool = False,
|
51
|
+
service_type: str = "http+mcp",
|
52
|
+
**kwargs):
|
53
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
54
|
+
|
55
|
+
def build_flow(self):
|
56
|
+
mock4_op = Mock4Op()
|
57
|
+
mock5_op = Mock5Op()
|
58
|
+
mock6_op = Mock6Op()
|
59
|
+
|
60
|
+
op = mock4_op >> ((mock5_op >> mock6_op) | mock4_op) >> (mock5_op | mock6_op)
|
61
|
+
return op
|
62
|
+
|
63
|
+
def build_tool_call(self) -> ToolCall:
|
64
|
+
return ToolCall(**{
|
65
|
+
"type": "function",
|
66
|
+
"name": "mock_data",
|
67
|
+
"description": "A mock tool that processes data through multiple operations and returns structured results",
|
68
|
+
"input_schema": {
|
69
|
+
"a": ParamAttrs(
|
70
|
+
type="str",
|
71
|
+
description="The input data to be processed",
|
49
72
|
required=True
|
50
73
|
),
|
51
|
-
"
|
74
|
+
"b": ParamAttrs(
|
52
75
|
type="string",
|
53
|
-
description="Processing
|
54
|
-
required=True
|
55
|
-
),
|
56
|
-
"metadata": ParamAttrs(
|
57
|
-
type="object",
|
58
|
-
description="Additional metadata about the processing",
|
76
|
+
description="Processing mode: basic, advanced, or expert",
|
59
77
|
required=False
|
60
|
-
)
|
78
|
+
),
|
61
79
|
}
|
62
80
|
})
|
@@ -0,0 +1,97 @@
|
|
1
|
+
from flowllm.context.service_context import C
|
2
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
3
|
+
from flowllm.op.llm import SimpleLLMOp, ReactLLMOp, StreamLLMOp
|
4
|
+
from flowllm.op.search import DashscopeSearchOp
|
5
|
+
from flowllm.op.search import TavilySearchOp
|
6
|
+
from flowllm.schema.tool_call import ToolCall
|
7
|
+
|
8
|
+
|
9
|
+
@C.register_tool_flow()
|
10
|
+
class TavilySearchToolFlow(BaseToolFlow):
|
11
|
+
|
12
|
+
def __init__(self,
|
13
|
+
use_async: bool = True,
|
14
|
+
stream: bool = False,
|
15
|
+
service_type: str = "http+mcp",
|
16
|
+
**kwargs):
|
17
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
18
|
+
|
19
|
+
def build_flow(self):
|
20
|
+
return TavilySearchOp()
|
21
|
+
|
22
|
+
def build_tool_call(self) -> ToolCall:
|
23
|
+
return self.flow_op.tool_call
|
24
|
+
|
25
|
+
def after_flow(self, context):
|
26
|
+
context.response.answer = context.tavily_search_result
|
27
|
+
|
28
|
+
|
29
|
+
@C.register_tool_flow()
|
30
|
+
class DashscopeSearchToolFlow(BaseToolFlow):
|
31
|
+
|
32
|
+
def __init__(self,
|
33
|
+
use_async: bool = True,
|
34
|
+
stream: bool = False,
|
35
|
+
service_type: str = "http+mcp",
|
36
|
+
**kwargs):
|
37
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
38
|
+
|
39
|
+
def build_flow(self):
|
40
|
+
return DashscopeSearchOp()
|
41
|
+
|
42
|
+
def build_tool_call(self) -> ToolCall:
|
43
|
+
return self.flow_op.tool_call
|
44
|
+
|
45
|
+
def after_flow(self, context):
|
46
|
+
context.response.answer = context.dashscope_search_result
|
47
|
+
|
48
|
+
|
49
|
+
@C.register_tool_flow(name="stream_llm_tool_flow")
|
50
|
+
class StreamLLMToolFlow(BaseToolFlow):
|
51
|
+
|
52
|
+
def __init__(self,
|
53
|
+
use_async: bool = True,
|
54
|
+
stream: bool = True,
|
55
|
+
service_type: str = "http",
|
56
|
+
**kwargs):
|
57
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
58
|
+
|
59
|
+
def build_flow(self):
|
60
|
+
return StreamLLMOp()
|
61
|
+
|
62
|
+
def build_tool_call(self) -> ToolCall:
|
63
|
+
return self.flow_op.tool_call
|
64
|
+
|
65
|
+
|
66
|
+
@C.register_tool_flow(name="simple_llm_tool_flow")
|
67
|
+
class SimpleLLMToolFlow(BaseToolFlow):
|
68
|
+
|
69
|
+
def __init__(self,
|
70
|
+
use_async: bool = True,
|
71
|
+
stream: bool = False,
|
72
|
+
service_type: str = "http",
|
73
|
+
**kwargs):
|
74
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
75
|
+
|
76
|
+
def build_flow(self):
|
77
|
+
return SimpleLLMOp()
|
78
|
+
|
79
|
+
def build_tool_call(self) -> ToolCall:
|
80
|
+
return self.flow_op.tool_call
|
81
|
+
|
82
|
+
|
83
|
+
@C.register_tool_flow(name="react_llm_tool_flow")
|
84
|
+
class ReactLLMToolFlow(BaseToolFlow):
|
85
|
+
|
86
|
+
def __init__(self,
|
87
|
+
use_async: bool = True,
|
88
|
+
stream: bool = False,
|
89
|
+
service_type: str = "http",
|
90
|
+
**kwargs):
|
91
|
+
super().__init__(use_async=use_async, stream=stream, service_type=service_type, **kwargs)
|
92
|
+
|
93
|
+
def build_flow(self):
|
94
|
+
return ReactLLMOp()
|
95
|
+
|
96
|
+
def build_tool_call(self) -> ToolCall:
|
97
|
+
return self.flow_op.tool_call
|
flowllm/llm/base_llm.py
CHANGED
@@ -155,10 +155,8 @@ class BaseLLM(BaseModel, ABC):
|
|
155
155
|
except Exception as e:
|
156
156
|
logger.exception(f"chat with model={self.model_name} encounter error with e={e.args}")
|
157
157
|
|
158
|
-
# Exponential backoff: wait longer after each failure
|
159
158
|
time.sleep(1 + i)
|
160
159
|
|
161
|
-
# Handle final retry failure
|
162
160
|
if i == self.max_retries - 1:
|
163
161
|
if self.raise_exception:
|
164
162
|
raise e
|
flowllm/op/__init__.py
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
from .base_llm_op import BaseLLMOp
|
2
2
|
from .base_op import BaseOp
|
3
|
-
from .
|
3
|
+
from .base_tool_op import BaseToolOp
|
4
4
|
|
5
5
|
"""
|
6
|
-
op folder
|
7
6
|
"""
|
8
7
|
from . import akshare
|
9
|
-
from . import code
|
10
8
|
from . import gallery
|
9
|
+
from . import llm
|
11
10
|
from . import search
|
12
|
-
from . import
|
11
|
+
from . import mcp
|
@@ -101,7 +101,7 @@ class GetAkACodeOp(BaseLLMOp):
|
|
101
101
|
|
102
102
|
|
103
103
|
if __name__ == "__main__":
|
104
|
-
C.
|
104
|
+
C.set_service_config().init_by_service_config()
|
105
105
|
context = FlowContext(query="茅台和五粮现在价格多少?")
|
106
106
|
|
107
107
|
op = GetAkACodeOp()
|
@@ -124,7 +124,7 @@ class MergeAkAInfoOp(BaseOp):
|
|
124
124
|
|
125
125
|
|
126
126
|
if __name__ == "__main__":
|
127
|
-
C.
|
127
|
+
C.set_service_config().init_by_service_config()
|
128
128
|
|
129
129
|
code_infos = {"000858": {}, "600519": {}}
|
130
130
|
context = FlowContext(code_infos=code_infos, query="茅台和五粮现在价格多少?")
|
flowllm/op/base_op.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
import asyncio
|
2
|
+
import copy
|
3
|
+
from abc import ABC
|
2
4
|
from concurrent.futures import Future
|
3
|
-
from typing import List
|
5
|
+
from typing import List, Any, Optional, Callable
|
4
6
|
|
5
7
|
from loguru import logger
|
6
8
|
from tqdm import tqdm
|
@@ -13,41 +15,123 @@ from flowllm.utils.timer import Timer
|
|
13
15
|
|
14
16
|
class BaseOp(ABC):
|
15
17
|
|
18
|
+
def __new__(cls, *args, **kwargs):
|
19
|
+
instance = super().__new__(cls)
|
20
|
+
instance._init_args = copy.copy(args)
|
21
|
+
instance._init_kwargs = copy.copy(kwargs)
|
22
|
+
return instance
|
23
|
+
|
16
24
|
def __init__(self,
|
17
25
|
name: str = "",
|
26
|
+
max_retries: int = 1,
|
18
27
|
raise_exception: bool = True,
|
19
28
|
enable_multithread: bool = True,
|
20
29
|
**kwargs):
|
21
30
|
super().__init__()
|
22
31
|
|
23
32
|
self.name: str = name or camel_to_snake(self.__class__.__name__)
|
33
|
+
self.max_retries: int = max_retries
|
24
34
|
self.raise_exception: bool = raise_exception
|
25
35
|
self.enable_multithread: bool = enable_multithread
|
26
36
|
self.op_params: dict = kwargs
|
27
37
|
|
28
38
|
self.task_list: List[Future] = []
|
29
|
-
self.
|
39
|
+
self.async_task_list: List = []
|
40
|
+
self.ray_task_list: List = []
|
30
41
|
self.timer = Timer(name=self.name)
|
31
42
|
self.context: FlowContext | None = None
|
43
|
+
self.sub_op: Optional["BaseOp"] = None
|
44
|
+
|
45
|
+
def before_execute(self):
|
46
|
+
...
|
47
|
+
|
48
|
+
def after_execute(self):
|
49
|
+
...
|
32
50
|
|
33
|
-
@abstractmethod
|
34
51
|
def execute(self):
|
35
52
|
...
|
36
53
|
|
54
|
+
def default_execute(self):
|
55
|
+
...
|
56
|
+
|
57
|
+
async def async_execute(self):
|
58
|
+
...
|
59
|
+
|
37
60
|
def __call__(self, context: FlowContext = None):
|
38
61
|
self.context = context
|
39
62
|
with self.timer:
|
40
|
-
if self.raise_exception:
|
63
|
+
if self.max_retries == 1 and self.raise_exception:
|
64
|
+
self.before_execute()
|
41
65
|
self.execute()
|
66
|
+
self.after_execute()
|
42
67
|
|
43
68
|
else:
|
69
|
+
for i in range(self.max_retries):
|
70
|
+
try:
|
71
|
+
self.before_execute()
|
72
|
+
self.execute()
|
73
|
+
self.after_execute()
|
74
|
+
|
75
|
+
except Exception as e:
|
76
|
+
logger.exception(f"op={self.name} execute failed, error={e.args}")
|
77
|
+
|
78
|
+
if i == self.max_retries - 1:
|
79
|
+
if self.raise_exception:
|
80
|
+
raise e
|
81
|
+
else:
|
82
|
+
self.default_execute()
|
83
|
+
|
84
|
+
if self.context is not None and self.context.response is not None:
|
85
|
+
return self.context.response
|
86
|
+
return None
|
87
|
+
|
88
|
+
async def async_call(self, context: FlowContext = None) -> Any:
|
89
|
+
self.context = context
|
90
|
+
with self.timer:
|
91
|
+
if self.max_retries == 1 and self.raise_exception:
|
92
|
+
self.before_execute()
|
93
|
+
await self.async_execute()
|
94
|
+
self.after_execute()
|
44
95
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
96
|
+
else:
|
97
|
+
for i in range(self.max_retries):
|
98
|
+
try:
|
99
|
+
self.before_execute()
|
100
|
+
await self.async_execute()
|
101
|
+
self.after_execute()
|
102
|
+
|
103
|
+
except Exception as e:
|
104
|
+
logger.exception(f"op={self.name} async execute failed, error={e.args}")
|
105
|
+
|
106
|
+
if i == self.max_retries:
|
107
|
+
if self.raise_exception:
|
108
|
+
raise e
|
109
|
+
else:
|
110
|
+
self.default_execute()
|
111
|
+
|
112
|
+
if self.context is not None and self.context.response is not None:
|
113
|
+
return self.context.response
|
114
|
+
return None
|
115
|
+
|
116
|
+
def submit_async_task(self, fn: Callable, *args, **kwargs):
|
117
|
+
if asyncio.iscoroutinefunction(fn):
|
118
|
+
task = asyncio.create_task(fn(*args, **kwargs))
|
119
|
+
self.async_task_list.append(task)
|
120
|
+
else:
|
121
|
+
logger.warning("submit_async_task failed, fn is not a coroutine function!")
|
122
|
+
|
123
|
+
async def join_async_task(self):
|
124
|
+
result = []
|
125
|
+
for task in self.async_task_list:
|
126
|
+
t_result = await task
|
127
|
+
if t_result:
|
128
|
+
if isinstance(t_result, list):
|
129
|
+
result.extend(t_result)
|
130
|
+
else:
|
131
|
+
result.append(t_result)
|
49
132
|
|
50
|
-
|
133
|
+
self.async_task_list.clear()
|
134
|
+
return result
|
51
135
|
|
52
136
|
def submit_task(self, fn, *args, **kwargs):
|
53
137
|
if self.enable_multithread:
|
@@ -92,6 +176,9 @@ class BaseOp(ABC):
|
|
92
176
|
sequential_op.ops.append(op)
|
93
177
|
return sequential_op
|
94
178
|
|
179
|
+
def __lshift__(self, op: "BaseOp"):
|
180
|
+
self.sub_op = op
|
181
|
+
|
95
182
|
def __or__(self, op: "BaseOp"):
|
96
183
|
from flowllm.op.parallel_op import ParallelOp
|
97
184
|
|
@@ -104,6 +191,9 @@ class BaseOp(ABC):
|
|
104
191
|
|
105
192
|
return parallel_op
|
106
193
|
|
194
|
+
def copy(self) -> "BaseOp":
|
195
|
+
return self.__class__(*self._init_args, **self._init_kwargs)
|
196
|
+
|
107
197
|
|
108
198
|
def run1():
|
109
199
|
"""Basic test"""
|
@@ -112,6 +202,9 @@ def run1():
|
|
112
202
|
def execute(self):
|
113
203
|
logger.info(f"op={self.name} execute")
|
114
204
|
|
205
|
+
async def async_execute(self):
|
206
|
+
logger.info(f"op={self.name} async_execute")
|
207
|
+
|
115
208
|
mock_op = MockOp()
|
116
209
|
mock_op()
|
117
210
|
|
@@ -129,6 +222,12 @@ def run2():
|
|
129
222
|
logger.info(f"Executing {op_result}")
|
130
223
|
return op_result
|
131
224
|
|
225
|
+
async def async_execute(self):
|
226
|
+
await asyncio.sleep(0.1)
|
227
|
+
op_result = f"{self.name}"
|
228
|
+
logger.info(f"Async executing {op_result}")
|
229
|
+
return op_result
|
230
|
+
|
132
231
|
# Create service_context for parallel execution
|
133
232
|
C["thread_pool"] = ThreadPoolExecutor(max_workers=4)
|
134
233
|
|
@@ -148,18 +247,135 @@ def run2():
|
|
148
247
|
result = parallel()
|
149
248
|
logger.info(f"Parallel result: {result}")
|
150
249
|
|
151
|
-
|
250
|
+
|
251
|
+
async def async_run1():
|
252
|
+
"""Basic async test"""
|
253
|
+
|
254
|
+
class MockOp(BaseOp):
|
255
|
+
def execute(self):
|
256
|
+
logger.info(f"op={self.name} execute")
|
257
|
+
|
258
|
+
async def async_execute(self):
|
259
|
+
logger.info(f"op={self.name} async_execute")
|
260
|
+
|
261
|
+
mock_op = MockOp()
|
262
|
+
await mock_op.async_call()
|
263
|
+
|
264
|
+
|
265
|
+
async def async_run2():
|
266
|
+
"""Test async operator overloading functionality"""
|
267
|
+
from concurrent.futures import ThreadPoolExecutor
|
268
|
+
import time
|
269
|
+
|
270
|
+
class TestOp(BaseOp):
|
271
|
+
|
272
|
+
def execute(self):
|
273
|
+
time.sleep(0.1)
|
274
|
+
op_result = f"{self.name}"
|
275
|
+
logger.info(f"Executing {op_result}")
|
276
|
+
return op_result
|
277
|
+
|
278
|
+
async def async_execute(self):
|
279
|
+
await asyncio.sleep(0.1)
|
280
|
+
op_result = f"{self.name}"
|
281
|
+
logger.info(f"Async executing {op_result}")
|
282
|
+
return op_result
|
283
|
+
|
284
|
+
# Create service_context for parallel execution
|
285
|
+
C["thread_pool"] = ThreadPoolExecutor(max_workers=4)
|
286
|
+
|
287
|
+
# Create test operations
|
288
|
+
op1 = TestOp("op1")
|
289
|
+
op2 = TestOp("op2")
|
290
|
+
op3 = TestOp("op3")
|
291
|
+
op4 = TestOp("op4")
|
292
|
+
|
293
|
+
logger.info("=== Testing async sequential execution op1 >> op2 ===")
|
294
|
+
sequential = op1 >> op2
|
295
|
+
result = await sequential.async_call()
|
296
|
+
logger.info(f"Async sequential result: {result}")
|
297
|
+
|
298
|
+
logger.info("=== Testing async parallel execution op1 | op2 ===")
|
299
|
+
parallel = op1 | op2
|
300
|
+
result = await parallel.async_call()
|
301
|
+
logger.info(f"Async parallel result: {result}")
|
302
|
+
|
303
|
+
logger.info("=== Testing async mixed calls op1 >> (op2 | op3) >> op4 ===")
|
152
304
|
mixed = op1 >> (op2 | op3) >> op4
|
153
|
-
result = mixed()
|
154
|
-
logger.info(f"
|
305
|
+
result = await mixed.async_call()
|
306
|
+
logger.info(f"Async mixed result: {result}")
|
155
307
|
|
156
|
-
logger.info("=== Testing complex mixed calls op1 >> (op1 | (op2 >> op3)) >> op4 ===")
|
308
|
+
logger.info("=== Testing async complex mixed calls op1 >> (op1 | (op2 >> op3)) >> op4 ===")
|
157
309
|
complex_mixed = op1 >> (op1 | (op2 >> op3)) >> op4
|
158
|
-
result = complex_mixed()
|
159
|
-
logger.info(f"
|
310
|
+
result = await complex_mixed.async_call()
|
311
|
+
logger.info(f"Async complex mixed result: {result}")
|
312
|
+
|
313
|
+
|
314
|
+
def test_copy():
|
315
|
+
class TestOp(BaseOp):
|
316
|
+
def __init__(self, name="", custom_param="default", **kwargs):
|
317
|
+
super().__init__(name=name, **kwargs)
|
318
|
+
self.custom_param = custom_param
|
319
|
+
|
320
|
+
def execute(self):
|
321
|
+
logger.info(f"TestOp {self.name} executing with custom_param={self.custom_param}")
|
322
|
+
|
323
|
+
class AdvancedOp(TestOp):
|
324
|
+
def __init__(self, name="", custom_param="default", advanced_param=42, **kwargs):
|
325
|
+
super().__init__(name=name, custom_param=custom_param, **kwargs)
|
326
|
+
self.advanced_param = advanced_param
|
327
|
+
|
328
|
+
def execute(self):
|
329
|
+
logger.info(
|
330
|
+
f"AdvancedOp {self.name} executing with custom_param={self.custom_param}, advanced_param={self.advanced_param}")
|
331
|
+
|
332
|
+
logger.info("=== Testing copy functionality ===")
|
333
|
+
|
334
|
+
original_op = TestOp(name="test_op", custom_param="custom_value", max_retries=3, enable_multithread=False)
|
335
|
+
copied_op = original_op.copy()
|
336
|
+
|
337
|
+
logger.info(
|
338
|
+
f"Original: name={original_op.name}, custom_param={original_op.custom_param}, max_retries={original_op.max_retries}")
|
339
|
+
logger.info(
|
340
|
+
f"Copied: name={copied_op.name}, custom_param={copied_op.custom_param}, max_retries={copied_op.max_retries}")
|
341
|
+
logger.info(f"Same object? {original_op is copied_op}")
|
342
|
+
logger.info(f"Same class? {type(original_op) == type(copied_op)}")
|
343
|
+
|
344
|
+
original_advanced = AdvancedOp(
|
345
|
+
name="advanced_op",
|
346
|
+
custom_param="advanced_custom",
|
347
|
+
advanced_param=100,
|
348
|
+
max_retries=5,
|
349
|
+
raise_exception=False
|
350
|
+
)
|
351
|
+
copied_advanced = original_advanced.copy()
|
352
|
+
|
353
|
+
logger.info(
|
354
|
+
f"Advanced Original: name={original_advanced.name}, custom_param={original_advanced.custom_param}, advanced_param={original_advanced.advanced_param}")
|
355
|
+
logger.info(
|
356
|
+
f"Advanced Copied: name={copied_advanced.name}, custom_param={copied_advanced.custom_param}, advanced_param={copied_advanced.advanced_param}")
|
357
|
+
logger.info(f"Advanced Same object? {original_advanced is copied_advanced}")
|
358
|
+
logger.info(f"Advanced Same class? {type(original_advanced) == type(copied_advanced)}")
|
359
|
+
|
360
|
+
copied_op.name = "modified_name"
|
361
|
+
logger.info(f"After modifying copy - Original name: {original_op.name}, Copied name: {copied_op.name}")
|
160
362
|
|
161
363
|
|
162
364
|
if __name__ == "__main__":
|
163
365
|
run1()
|
164
366
|
print("\n" + "=" * 50 + "\n")
|
165
367
|
run2()
|
368
|
+
print("\n" + "=" * 50 + "\n")
|
369
|
+
test_copy()
|
370
|
+
|
371
|
+
print("\n" + "=" * 50 + "\n")
|
372
|
+
print("Running async tests:")
|
373
|
+
|
374
|
+
|
375
|
+
async def main():
|
376
|
+
await async_run1()
|
377
|
+
print("\n" + "=" * 50 + "\n")
|
378
|
+
await async_run2()
|
379
|
+
|
380
|
+
|
381
|
+
asyncio.run(main())
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
from flowllm.op.base_llm_op import BaseLLMOp
|
6
|
+
from flowllm.schema.tool_call import ToolCall
|
7
|
+
from flowllm.storage.cache import DataCache
|
8
|
+
|
9
|
+
|
10
|
+
class BaseToolOp(BaseLLMOp, ABC):
|
11
|
+
|
12
|
+
def __init__(self,
|
13
|
+
enable_cache: bool = False,
|
14
|
+
cache_path: str = "cache",
|
15
|
+
cache_expire_hours: float = 0.1,
|
16
|
+
enable_print_output: bool = True,
|
17
|
+
**kwargs):
|
18
|
+
super().__init__(**kwargs)
|
19
|
+
|
20
|
+
self.enable_cache = enable_cache
|
21
|
+
self.cache_path: str = cache_path
|
22
|
+
self.cache_expire_hours: float = cache_expire_hours
|
23
|
+
self.enable_print_output: bool = enable_print_output
|
24
|
+
self._cache: DataCache | None = None
|
25
|
+
|
26
|
+
self.tool_call: ToolCall = self.build_tool_call()
|
27
|
+
self.input_dict: dict = {}
|
28
|
+
self.output_dict: dict = {}
|
29
|
+
|
30
|
+
@property
|
31
|
+
def cache(self):
|
32
|
+
if self.enable_cache and self._cache is None:
|
33
|
+
self._cache = DataCache(f"{self.cache_path}/{self.name}")
|
34
|
+
return self._cache
|
35
|
+
|
36
|
+
@abstractmethod
|
37
|
+
def build_tool_call(self) -> ToolCall:
|
38
|
+
...
|
39
|
+
|
40
|
+
def before_execute(self):
|
41
|
+
for key in self.tool_call.input_schema.keys():
|
42
|
+
self.input_dict[key] = self.context.get(key)
|
43
|
+
|
44
|
+
def after_execute(self):
|
45
|
+
self.context.update(self.output_dict)
|
46
|
+
if self.enable_print_output:
|
47
|
+
logger.info(f"{self.name}.output_dict={self.output_dict}")
|
flowllm/op/gallery/__init__.py
CHANGED
flowllm/op/gallery/mock_op.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
import asyncio
|
1
2
|
import time
|
2
3
|
|
3
4
|
from loguru import logger
|
4
5
|
|
5
|
-
from flowllm.context
|
6
|
+
from flowllm.context import C
|
6
7
|
from flowllm.op.base_llm_op import BaseLLMOp
|
7
8
|
|
8
9
|
|
@@ -10,8 +11,8 @@ from flowllm.op.base_llm_op import BaseLLMOp
|
|
10
11
|
class Mock1Op(BaseLLMOp):
|
11
12
|
def execute(self):
|
12
13
|
time.sleep(1)
|
13
|
-
a = self.context.a
|
14
|
-
b = self.context.b
|
14
|
+
a = self.context.get("a", 1)
|
15
|
+
b = self.context.get("b", 2)
|
15
16
|
logger.info(f"enter class={self.name}. a={a} b={b}")
|
16
17
|
|
17
18
|
self.context.response.answer = f"{self.name} {a} {b} answer=47"
|
@@ -28,15 +29,20 @@ class Mock3Op(Mock1Op):
|
|
28
29
|
|
29
30
|
|
30
31
|
@C.register_op()
|
31
|
-
class Mock4Op(
|
32
|
-
|
32
|
+
class Mock4Op(BaseLLMOp):
|
33
|
+
async def async_execute(self):
|
34
|
+
await asyncio.sleep(1)
|
35
|
+
a = self.context.get("a", 1)
|
36
|
+
b = self.context.get("b", 2)
|
37
|
+
logger.info(f"enter class={self.name}. a={a} b={b}")
|
38
|
+
self.context.response.answer = f"{self.name} {a} {b} answer=47"
|
33
39
|
|
34
40
|
|
35
41
|
@C.register_op()
|
36
|
-
class Mock5Op(
|
42
|
+
class Mock5Op(Mock4Op):
|
37
43
|
...
|
38
44
|
|
39
45
|
|
40
46
|
@C.register_op()
|
41
|
-
class Mock6Op(
|
47
|
+
class Mock6Op(Mock4Op):
|
42
48
|
...
|