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
@@ -7,61 +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
|
-
"arguments": {
|
29
|
-
"input_data": "sample_data",
|
30
|
-
"processing_mode": "advanced",
|
31
|
-
"output_format": "json"
|
32
|
-
},
|
33
30
|
"input_schema": {
|
34
|
-
"
|
35
|
-
type="
|
31
|
+
"a": ParamAttrs(
|
32
|
+
type="str",
|
36
33
|
description="The input data to be processed",
|
37
34
|
required=True
|
38
35
|
),
|
39
|
-
"
|
36
|
+
"b": ParamAttrs(
|
40
37
|
type="string",
|
41
38
|
description="Processing mode: basic, advanced, or expert",
|
42
39
|
required=False
|
43
40
|
),
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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",
|
54
72
|
required=True
|
55
73
|
),
|
56
|
-
"
|
74
|
+
"b": ParamAttrs(
|
57
75
|
type="string",
|
58
|
-
description="Processing
|
59
|
-
required=True
|
60
|
-
),
|
61
|
-
"metadata": ParamAttrs(
|
62
|
-
type="object",
|
63
|
-
description="Additional metadata about the processing",
|
76
|
+
description="Processing mode: basic, advanced, or expert",
|
64
77
|
required=False
|
65
|
-
)
|
78
|
+
),
|
66
79
|
}
|
67
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/llm/litellm_llm.py
CHANGED
@@ -2,7 +2,6 @@ import asyncio
|
|
2
2
|
import os
|
3
3
|
from typing import List, Dict
|
4
4
|
|
5
|
-
from litellm import completion, acompletion
|
6
5
|
from loguru import logger
|
7
6
|
from pydantic import Field, PrivateAttr, model_validator
|
8
7
|
|
@@ -98,6 +97,7 @@ class LiteLLMBaseLLM(BaseLLM):
|
|
98
97
|
Yields:
|
99
98
|
Tuple of (chunk_content, ChunkEnum) for each streaming piece
|
100
99
|
"""
|
100
|
+
from litellm import completion
|
101
101
|
for i in range(self.max_retries):
|
102
102
|
try:
|
103
103
|
# Prepare parameters for LiteLLM
|
@@ -200,6 +200,7 @@ class LiteLLMBaseLLM(BaseLLM):
|
|
200
200
|
Yields:
|
201
201
|
Tuple of (chunk_content, ChunkEnum) for each streaming piece
|
202
202
|
"""
|
203
|
+
from litellm import acompletion
|
203
204
|
for i in range(self.max_retries):
|
204
205
|
try:
|
205
206
|
# Prepare parameters for LiteLLM
|
flowllm/op/__init__.py
CHANGED
@@ -1,11 +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
|
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_llm_op.py
CHANGED
@@ -23,8 +23,9 @@ class BaseLLMOp(BaseOp, ABC):
|
|
23
23
|
super().__init__(**kwargs)
|
24
24
|
|
25
25
|
self.language: str = language or C.language
|
26
|
-
|
27
|
-
|
26
|
+
default_prompt_path = self.file_path.replace("op.py", "prompt.yaml")
|
27
|
+
self.prompt_path: Path = Path(prompt_path) if prompt_path else default_prompt_path
|
28
|
+
|
28
29
|
self._llm: BaseLLM | str = llm
|
29
30
|
self._embedding_model: BaseEmbeddingModel | str = embedding_model
|
30
31
|
self._vector_store: BaseVectorStore | str = vector_store
|
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,54 +15,153 @@ 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,
|
28
|
+
enable_multithread: bool = True,
|
19
29
|
**kwargs):
|
20
30
|
super().__init__()
|
21
31
|
|
22
32
|
self.name: str = name or camel_to_snake(self.__class__.__name__)
|
33
|
+
self.max_retries: int = max_retries
|
23
34
|
self.raise_exception: bool = raise_exception
|
35
|
+
self.enable_multithread: bool = enable_multithread
|
24
36
|
self.op_params: dict = kwargs
|
25
37
|
|
26
38
|
self.task_list: List[Future] = []
|
27
|
-
self.
|
39
|
+
self.async_task_list: List = []
|
40
|
+
self.ray_task_list: List = []
|
28
41
|
self.timer = Timer(name=self.name)
|
29
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
|
+
...
|
30
50
|
|
31
|
-
@abstractmethod
|
32
51
|
def execute(self):
|
33
52
|
...
|
34
53
|
|
54
|
+
def default_execute(self):
|
55
|
+
...
|
56
|
+
|
57
|
+
async def async_execute(self):
|
58
|
+
...
|
59
|
+
|
35
60
|
def __call__(self, context: FlowContext = None):
|
36
61
|
self.context = context
|
37
62
|
with self.timer:
|
38
|
-
if self.raise_exception:
|
63
|
+
if self.max_retries == 1 and self.raise_exception:
|
64
|
+
self.before_execute()
|
39
65
|
self.execute()
|
66
|
+
self.after_execute()
|
40
67
|
|
41
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()
|
42
95
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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!")
|
54
122
|
|
55
|
-
def
|
123
|
+
async def join_async_task(self):
|
56
124
|
result = []
|
57
|
-
for task in
|
58
|
-
t_result = task
|
125
|
+
for task in self.async_task_list:
|
126
|
+
t_result = await task
|
59
127
|
if t_result:
|
60
128
|
if isinstance(t_result, list):
|
61
129
|
result.extend(t_result)
|
62
130
|
else:
|
63
131
|
result.append(t_result)
|
132
|
+
|
133
|
+
self.async_task_list.clear()
|
134
|
+
return result
|
135
|
+
|
136
|
+
def submit_task(self, fn, *args, **kwargs):
|
137
|
+
if self.enable_multithread:
|
138
|
+
task = C.thread_pool.submit(fn, *args, **kwargs)
|
139
|
+
self.task_list.append(task)
|
140
|
+
|
141
|
+
else:
|
142
|
+
result = fn(*args, **kwargs)
|
143
|
+
if result:
|
144
|
+
if isinstance(result, list):
|
145
|
+
result.extend(result)
|
146
|
+
else:
|
147
|
+
result.append(result)
|
148
|
+
|
149
|
+
return self
|
150
|
+
|
151
|
+
def join_task(self, task_desc: str = None) -> list:
|
152
|
+
result = []
|
153
|
+
if self.enable_multithread:
|
154
|
+
for task in tqdm(self.task_list, desc=task_desc or self.name):
|
155
|
+
t_result = task.result()
|
156
|
+
if t_result:
|
157
|
+
if isinstance(t_result, list):
|
158
|
+
result.extend(t_result)
|
159
|
+
else:
|
160
|
+
result.append(t_result)
|
161
|
+
|
162
|
+
else:
|
163
|
+
result.extend(self.task_list)
|
164
|
+
|
64
165
|
self.task_list.clear()
|
65
166
|
return result
|
66
167
|
|
@@ -75,6 +176,9 @@ class BaseOp(ABC):
|
|
75
176
|
sequential_op.ops.append(op)
|
76
177
|
return sequential_op
|
77
178
|
|
179
|
+
def __lshift__(self, op: "BaseOp"):
|
180
|
+
self.sub_op = op
|
181
|
+
|
78
182
|
def __or__(self, op: "BaseOp"):
|
79
183
|
from flowllm.op.parallel_op import ParallelOp
|
80
184
|
|
@@ -87,6 +191,9 @@ class BaseOp(ABC):
|
|
87
191
|
|
88
192
|
return parallel_op
|
89
193
|
|
194
|
+
def copy(self) -> "BaseOp":
|
195
|
+
return self.__class__(*self._init_args, **self._init_kwargs)
|
196
|
+
|
90
197
|
|
91
198
|
def run1():
|
92
199
|
"""Basic test"""
|
@@ -95,6 +202,9 @@ def run1():
|
|
95
202
|
def execute(self):
|
96
203
|
logger.info(f"op={self.name} execute")
|
97
204
|
|
205
|
+
async def async_execute(self):
|
206
|
+
logger.info(f"op={self.name} async_execute")
|
207
|
+
|
98
208
|
mock_op = MockOp()
|
99
209
|
mock_op()
|
100
210
|
|
@@ -112,6 +222,12 @@ def run2():
|
|
112
222
|
logger.info(f"Executing {op_result}")
|
113
223
|
return op_result
|
114
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
|
+
|
115
231
|
# Create service_context for parallel execution
|
116
232
|
C["thread_pool"] = ThreadPoolExecutor(max_workers=4)
|
117
233
|
|
@@ -131,18 +247,135 @@ def run2():
|
|
131
247
|
result = parallel()
|
132
248
|
logger.info(f"Parallel result: {result}")
|
133
249
|
|
134
|
-
|
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 ===")
|
135
304
|
mixed = op1 >> (op2 | op3) >> op4
|
136
|
-
result = mixed()
|
137
|
-
logger.info(f"
|
305
|
+
result = await mixed.async_call()
|
306
|
+
logger.info(f"Async mixed result: {result}")
|
138
307
|
|
139
|
-
logger.info("=== Testing complex mixed calls op1 >> (op1 | (op2 >> op3)) >> op4 ===")
|
308
|
+
logger.info("=== Testing async complex mixed calls op1 >> (op1 | (op2 >> op3)) >> op4 ===")
|
140
309
|
complex_mixed = op1 >> (op1 | (op2 >> op3)) >> op4
|
141
|
-
result = complex_mixed()
|
142
|
-
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}")
|
143
362
|
|
144
363
|
|
145
364
|
if __name__ == "__main__":
|
146
365
|
run1()
|
147
366
|
print("\n" + "=" * 50 + "\n")
|
148
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())
|