flowllm 0.1.1__py3-none-any.whl → 0.1.3__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 +19 -6
- flowllm/app.py +4 -14
- flowllm/client/__init__.py +25 -0
- flowllm/client/async_http_client.py +81 -0
- flowllm/client/http_client.py +81 -0
- flowllm/client/mcp_client.py +133 -0
- flowllm/client/sync_mcp_client.py +116 -0
- flowllm/config/__init__.py +1 -0
- flowllm/config/{default_config.yaml → default.yaml} +3 -8
- flowllm/config/empty.yaml +37 -0
- flowllm/config/pydantic_config_parser.py +17 -17
- flowllm/context/base_context.py +27 -7
- flowllm/context/flow_context.py +6 -18
- flowllm/context/registry.py +5 -1
- flowllm/context/service_context.py +83 -37
- flowllm/embedding_model/__init__.py +1 -1
- flowllm/embedding_model/base_embedding_model.py +91 -0
- flowllm/embedding_model/openai_compatible_embedding_model.py +63 -5
- flowllm/flow/__init__.py +1 -0
- flowllm/flow/base_flow.py +74 -0
- flowllm/flow/base_tool_flow.py +15 -0
- flowllm/flow/gallery/__init__.py +8 -0
- flowllm/flow/gallery/cmd_flow.py +11 -0
- flowllm/flow/gallery/code_tool_flow.py +30 -0
- flowllm/flow/gallery/dashscope_search_tool_flow.py +34 -0
- flowllm/flow/gallery/deepsearch_tool_flow.py +39 -0
- flowllm/flow/gallery/expression_tool_flow.py +18 -0
- flowllm/flow/gallery/mock_tool_flow.py +62 -0
- flowllm/flow/gallery/tavily_search_tool_flow.py +30 -0
- flowllm/flow/gallery/terminate_tool_flow.py +30 -0
- flowllm/flow/parser/__init__.py +0 -0
- flowllm/{flow_engine/simple_flow_engine.py → flow/parser/expression_parser.py} +25 -67
- flowllm/llm/__init__.py +2 -1
- flowllm/llm/base_llm.py +94 -4
- flowllm/llm/litellm_llm.py +456 -0
- flowllm/llm/openai_compatible_llm.py +205 -5
- flowllm/op/__init__.py +12 -3
- flowllm/op/agent/__init__.py +1 -0
- flowllm/op/agent/react_v1_op.py +109 -0
- flowllm/op/agent/react_v1_prompt.yaml +54 -0
- flowllm/op/agent/react_v2_op.py +86 -0
- flowllm/op/agent/react_v2_prompt.yaml +35 -0
- flowllm/op/akshare/__init__.py +3 -0
- flowllm/op/akshare/get_ak_a_code_op.py +14 -22
- flowllm/op/akshare/get_ak_a_info_op.py +17 -20
- flowllm/op/{llm_base_op.py → base_llm_op.py} +7 -5
- flowllm/op/base_op.py +40 -44
- flowllm/op/base_ray_op.py +313 -0
- flowllm/op/code/__init__.py +1 -0
- flowllm/op/code/execute_code_op.py +42 -0
- flowllm/op/gallery/__init__.py +2 -0
- flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
- flowllm/op/gallery/terminate_op.py +29 -0
- flowllm/op/parallel_op.py +2 -9
- flowllm/op/search/__init__.py +3 -0
- flowllm/op/search/dashscope_deep_research_op.py +267 -0
- flowllm/op/search/dashscope_search_op.py +186 -0
- flowllm/op/search/dashscope_search_prompt.yaml +13 -0
- flowllm/op/search/tavily_search_op.py +109 -0
- flowllm/op/sequential_op.py +1 -9
- flowllm/schema/flow_request.py +12 -0
- flowllm/schema/message.py +2 -0
- flowllm/schema/service_config.py +12 -16
- flowllm/schema/tool_call.py +20 -8
- flowllm/schema/vector_node.py +1 -0
- flowllm/service/__init__.py +3 -2
- flowllm/service/base_service.py +50 -41
- flowllm/service/cmd_service.py +15 -0
- flowllm/service/http_service.py +34 -42
- flowllm/service/mcp_service.py +13 -11
- flowllm/storage/cache/__init__.py +1 -0
- flowllm/storage/cache/cache_data_handler.py +104 -0
- flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
- flowllm/storage/vector_store/__init__.py +3 -3
- flowllm/storage/vector_store/base_vector_store.py +3 -0
- flowllm/storage/vector_store/es_vector_store.py +4 -5
- flowllm/storage/vector_store/local_vector_store.py +0 -1
- flowllm/utils/common_utils.py +9 -21
- flowllm/utils/fetch_url.py +16 -12
- flowllm/utils/llm_utils.py +28 -0
- flowllm/utils/logger_utils.py +28 -0
- flowllm/utils/ridge_v2.py +54 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/METADATA +43 -390
- flowllm-0.1.3.dist-info/RECORD +102 -0
- flowllm-0.1.3.dist-info/entry_points.txt +2 -0
- flowllm/flow_engine/__init__.py +0 -1
- flowllm/flow_engine/base_flow_engine.py +0 -34
- flowllm-0.1.1.dist-info/RECORD +0 -62
- flowllm-0.1.1.dist-info/entry_points.txt +0 -4
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/WHEEL +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
from flowllm.context.flow_context import FlowContext
|
2
|
+
from flowllm.context.service_context import C
|
3
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
4
|
+
from flowllm.op.search.dashscope_deep_research_op import DashscopeDeepResearchOp
|
5
|
+
from flowllm.schema.tool_call import ToolCall
|
6
|
+
|
7
|
+
|
8
|
+
@C.register_tool_flow()
|
9
|
+
class DeepSearchToolFlow(BaseToolFlow):
|
10
|
+
|
11
|
+
def build_flow(self):
|
12
|
+
return DashscopeDeepResearchOp()
|
13
|
+
|
14
|
+
def build_tool_call(self) -> ToolCall:
|
15
|
+
return ToolCall(**{
|
16
|
+
"name": "deep_search",
|
17
|
+
"description": "Perform deep research on a topic using Dashscope's qwen-deep-research model. This tool will conduct multi-phase research including model questioning, web research, and result generation.",
|
18
|
+
"input_schema": {
|
19
|
+
"query": {
|
20
|
+
"type": "str",
|
21
|
+
"description": "Research topic or question",
|
22
|
+
"required": True
|
23
|
+
}
|
24
|
+
}
|
25
|
+
})
|
26
|
+
|
27
|
+
def return_callback(self, context: FlowContext):
|
28
|
+
context.response.answer = context.dashscope_deep_research_result
|
29
|
+
return context.response
|
30
|
+
|
31
|
+
|
32
|
+
if __name__ == "__main__":
|
33
|
+
from flowllm.utils.common_utils import load_env
|
34
|
+
|
35
|
+
load_env()
|
36
|
+
|
37
|
+
flow = DeepSearchToolFlow()
|
38
|
+
result = flow(query="中国电解铝行业值得投资吗,有哪些值得投资的标的,各个标的之间需要对比优劣势")
|
39
|
+
print(result.answer)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
2
|
+
from flowllm.flow.parser.expression_parser import ExpressionParser
|
3
|
+
from flowllm.schema.service_config import FlowConfig
|
4
|
+
from flowllm.schema.tool_call import ToolCall
|
5
|
+
|
6
|
+
|
7
|
+
class ExpressionToolFlow(BaseToolFlow):
|
8
|
+
|
9
|
+
def __init__(self, flow_config: FlowConfig = None, **kwargs):
|
10
|
+
self.flow_config: FlowConfig = flow_config
|
11
|
+
super().__init__(name=flow_config.name, **kwargs)
|
12
|
+
|
13
|
+
def build_flow(self):
|
14
|
+
parser = ExpressionParser(self.flow_config.flow_content)
|
15
|
+
return parser.parse_flow()
|
16
|
+
|
17
|
+
def build_tool_call(self) -> ToolCall:
|
18
|
+
return ToolCall(**self.flow_config.model_dump())
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from flowllm.context.service_context import C
|
2
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
3
|
+
from flowllm.op.gallery import Mock1Op, Mock2Op, Mock3Op, Mock4Op, Mock5Op, Mock6Op
|
4
|
+
from flowllm.schema.tool_call import ToolCall, ParamAttrs
|
5
|
+
|
6
|
+
|
7
|
+
@C.register_tool_flow()
|
8
|
+
class MockToolFlow(BaseToolFlow):
|
9
|
+
|
10
|
+
def build_flow(self):
|
11
|
+
mock1_op = Mock1Op()
|
12
|
+
mock2_op = Mock2Op()
|
13
|
+
mock3_op = Mock3Op()
|
14
|
+
mock4_op = Mock4Op()
|
15
|
+
mock5_op = Mock5Op()
|
16
|
+
mock6_op = Mock6Op()
|
17
|
+
|
18
|
+
op = mock1_op >> ((mock4_op >> mock2_op) | mock5_op) >> (mock3_op | mock6_op)
|
19
|
+
return op
|
20
|
+
|
21
|
+
def build_tool_call(self) -> ToolCall:
|
22
|
+
return ToolCall(**{
|
23
|
+
"index": 0,
|
24
|
+
"id": "call_mock_tool_12345",
|
25
|
+
"type": "function",
|
26
|
+
"name": "mock_data_processor",
|
27
|
+
"description": "A mock tool that processes data through multiple operations and returns structured results",
|
28
|
+
"input_schema": {
|
29
|
+
"input_data": ParamAttrs(
|
30
|
+
type="string",
|
31
|
+
description="The input data to be processed",
|
32
|
+
required=True
|
33
|
+
),
|
34
|
+
"processing_mode": ParamAttrs(
|
35
|
+
type="string",
|
36
|
+
description="Processing mode: basic, advanced, or expert",
|
37
|
+
required=False
|
38
|
+
),
|
39
|
+
"output_format": ParamAttrs(
|
40
|
+
type="string",
|
41
|
+
description="Output format: json, xml, or plain",
|
42
|
+
required=False
|
43
|
+
)
|
44
|
+
},
|
45
|
+
"output_schema": {
|
46
|
+
"result": ParamAttrs(
|
47
|
+
type="object",
|
48
|
+
description="Processed result data",
|
49
|
+
required=True
|
50
|
+
),
|
51
|
+
"status": ParamAttrs(
|
52
|
+
type="string",
|
53
|
+
description="Processing status: success, warning, or error",
|
54
|
+
required=True
|
55
|
+
),
|
56
|
+
"metadata": ParamAttrs(
|
57
|
+
type="object",
|
58
|
+
description="Additional metadata about the processing",
|
59
|
+
required=False
|
60
|
+
)
|
61
|
+
}
|
62
|
+
})
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from flowllm.context.flow_context import FlowContext
|
2
|
+
from flowllm.context.service_context import C
|
3
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
4
|
+
from flowllm.op.search import TavilySearchOp
|
5
|
+
from flowllm.schema.tool_call import ToolCall
|
6
|
+
|
7
|
+
|
8
|
+
@C.register_tool_flow()
|
9
|
+
class TavilySearchToolFlow(BaseToolFlow):
|
10
|
+
|
11
|
+
def build_flow(self):
|
12
|
+
return TavilySearchOp()
|
13
|
+
|
14
|
+
def build_tool_call(self) -> ToolCall:
|
15
|
+
return ToolCall(**{
|
16
|
+
"name": "web_search",
|
17
|
+
"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.",
|
18
|
+
"input_schema": {
|
19
|
+
"query": {
|
20
|
+
"type": "str",
|
21
|
+
"description": "search keyword",
|
22
|
+
"required": True
|
23
|
+
}
|
24
|
+
}
|
25
|
+
})
|
26
|
+
|
27
|
+
def return_callback(self, context: FlowContext):
|
28
|
+
context.response.answer = context.tavily_search_result
|
29
|
+
return context.response
|
30
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from flowllm.context.flow_context import FlowContext
|
2
|
+
from flowllm.context.service_context import C
|
3
|
+
from flowllm.flow.base_tool_flow import BaseToolFlow
|
4
|
+
from flowllm.op.gallery.terminate_op import TerminateOp
|
5
|
+
from flowllm.schema.tool_call import ToolCall
|
6
|
+
|
7
|
+
|
8
|
+
@C.register_tool_flow()
|
9
|
+
class TerminateToolFlow(BaseToolFlow):
|
10
|
+
|
11
|
+
def build_flow(self):
|
12
|
+
return TerminateOp()
|
13
|
+
|
14
|
+
def build_tool_call(self) -> ToolCall:
|
15
|
+
return ToolCall(**{
|
16
|
+
"name": "terminate",
|
17
|
+
"description": "If you can answer the user's question based on the context, be sure to use the **terminate** tool.",
|
18
|
+
"input_schema": {
|
19
|
+
"status": {
|
20
|
+
"type": "str",
|
21
|
+
"description": "If the user's question can be answered, return success, otherwise return failure.",
|
22
|
+
"required": True,
|
23
|
+
"enum": ["success", "failure"],
|
24
|
+
}
|
25
|
+
}
|
26
|
+
})
|
27
|
+
|
28
|
+
def return_callback(self, context: FlowContext):
|
29
|
+
context.response.answer = context.terminate_answer
|
30
|
+
return context.response
|
File without changes
|
@@ -1,46 +1,47 @@
|
|
1
1
|
import re
|
2
2
|
|
3
|
-
from loguru import logger
|
4
|
-
|
5
3
|
from flowllm.context.service_context import C
|
6
|
-
from flowllm.flow_engine.base_flow_engine import BaseFlowEngine
|
7
4
|
from flowllm.op.base_op import BaseOp
|
8
5
|
from flowllm.op.parallel_op import ParallelOp
|
9
6
|
from flowllm.op.sequential_op import SequentialOp
|
10
7
|
from flowllm.schema.service_config import OpConfig
|
11
8
|
|
12
9
|
|
13
|
-
|
14
|
-
class SimpleFlowEngine(BaseFlowEngine):
|
10
|
+
class ExpressionParser:
|
15
11
|
SEQ_SYMBOL = ">>"
|
16
12
|
PARALLEL_SYMBOL = "|"
|
17
13
|
|
18
14
|
"""
|
19
|
-
Simple flow implementation that supports parsing
|
20
|
-
|
15
|
+
Simple flow implementation that supports parsing operation expressions.
|
16
|
+
|
21
17
|
Supports flow expressions like:
|
22
|
-
- "op1 >> op2" (sequential
|
23
|
-
- "op1 | op2" (parallel
|
24
|
-
- "op1 >> (op2 | op3) >> op4" (mixed
|
25
|
-
- "op1 >> (op1 | (op2 >> op3)) >> op4" (complex nested
|
18
|
+
- "op1 >> op2" (sequential expressions)
|
19
|
+
- "op1 | op2" (parallel expressions)
|
20
|
+
- "op1 >> (op2 | op3) >> op4" (mixed expressions)
|
21
|
+
- "op1 >> (op1 | (op2 >> op3)) >> op4" (complex nested expressions)
|
26
22
|
"""
|
27
23
|
|
28
|
-
def
|
24
|
+
def __init__(self, flow_content: str = ""):
|
25
|
+
self.flow_content: str = flow_content
|
26
|
+
self._parsed_ops_cache = {}
|
27
|
+
|
28
|
+
def parse_flow(self):
|
29
|
+
self._parsed_ops_cache.clear()
|
29
30
|
expression = re.sub(r'\s+', ' ', self.flow_content.strip())
|
30
|
-
|
31
|
+
return self._parse_expression(expression)
|
31
32
|
|
32
33
|
def _parse_expression(self, expression: str) -> BaseOp:
|
33
34
|
"""
|
34
35
|
Parse the flow content string into executable operations.
|
35
|
-
|
36
|
+
|
36
37
|
Supports expressions with operators:
|
37
38
|
- ">>" for sequential execution
|
38
39
|
- "|" for parallel execution
|
39
40
|
- Parentheses for grouping operations
|
40
|
-
|
41
|
+
|
41
42
|
Args:
|
42
43
|
expression: The expression string to parse. If None, uses self.flow_content
|
43
|
-
|
44
|
+
|
44
45
|
Returns:
|
45
46
|
BaseOp: The parsed flow as an executable operation tree
|
46
47
|
"""
|
@@ -78,7 +79,7 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
78
79
|
def _parse_flat_expression(self, expression: str) -> BaseOp:
|
79
80
|
"""
|
80
81
|
Parse a flat expression (no parentheses) into operation objects.
|
81
|
-
|
82
|
+
|
82
83
|
Args:
|
83
84
|
expression: The flat expression string
|
84
85
|
|
@@ -98,7 +99,7 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
98
99
|
else:
|
99
100
|
ops.append(self._parse_parallel_expression(part))
|
100
101
|
|
101
|
-
return SequentialOp(ops=ops
|
102
|
+
return SequentialOp(ops=ops)
|
102
103
|
|
103
104
|
else:
|
104
105
|
# no sequential operators, parse for parallel
|
@@ -107,7 +108,7 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
107
108
|
def _parse_parallel_expression(self, expression: str) -> BaseOp:
|
108
109
|
"""
|
109
110
|
Parse a parallel expression (operations separated by |).
|
110
|
-
|
111
|
+
|
111
112
|
Args:
|
112
113
|
expression: The expression string
|
113
114
|
|
@@ -126,7 +127,7 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
126
127
|
else:
|
127
128
|
ops.append(self._create_op(part))
|
128
129
|
|
129
|
-
return ParallelOp(ops=ops
|
130
|
+
return ParallelOp(ops=ops)
|
130
131
|
|
131
132
|
else:
|
132
133
|
# single operation
|
@@ -136,9 +137,10 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
136
137
|
else:
|
137
138
|
return self._create_op(part)
|
138
139
|
|
139
|
-
|
140
|
-
|
141
|
-
|
140
|
+
@staticmethod
|
141
|
+
def _create_op(op_name: str) -> BaseOp:
|
142
|
+
if op_name in C.service_config.op:
|
143
|
+
op_config: OpConfig = C.service_config.op[op_name]
|
142
144
|
op_cls = C.resolve_op(op_config.backend)
|
143
145
|
|
144
146
|
|
@@ -152,7 +154,6 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
152
154
|
kwargs = {
|
153
155
|
"name": op_name,
|
154
156
|
"raise_exception": op_config.raise_exception,
|
155
|
-
"flow_context": self.flow_context,
|
156
157
|
**op_config.params
|
157
158
|
}
|
158
159
|
|
@@ -168,46 +169,3 @@ class SimpleFlowEngine(BaseFlowEngine):
|
|
168
169
|
kwargs["vector_store"] = op_config.vector_store
|
169
170
|
|
170
171
|
return op_cls(**kwargs)
|
171
|
-
|
172
|
-
def _print_flow(self):
|
173
|
-
"""
|
174
|
-
Print the parsed flow structure in a readable format.
|
175
|
-
Allows users to visualize the execution flow on screen.
|
176
|
-
"""
|
177
|
-
assert self._parsed_flow is not None, "flow_content is not parsed!"
|
178
|
-
|
179
|
-
logger.info(f"Expression: {self.flow_content}")
|
180
|
-
self._print_operation_tree(self._parsed_flow, indent=0)
|
181
|
-
|
182
|
-
def _print_operation_tree(self, op: BaseOp, indent: int = 0):
|
183
|
-
"""
|
184
|
-
Recursively print the operation tree structure.
|
185
|
-
|
186
|
-
Args:
|
187
|
-
op: The operation to print
|
188
|
-
indent: Current indentation level
|
189
|
-
"""
|
190
|
-
prefix = " " * indent
|
191
|
-
if isinstance(op, SequentialOp):
|
192
|
-
logger.info(f"{prefix}Sequential Execution:")
|
193
|
-
for i, sub_op in enumerate(op.ops):
|
194
|
-
logger.info(f"{prefix} Step {i + 1}:")
|
195
|
-
self._print_operation_tree(sub_op, indent + 2)
|
196
|
-
|
197
|
-
elif isinstance(op, ParallelOp):
|
198
|
-
logger.info(f"{prefix}Parallel Execution:")
|
199
|
-
for i, sub_op in enumerate(op.ops):
|
200
|
-
logger.info(f"{prefix} Branch {i + 1}:")
|
201
|
-
self._print_operation_tree(sub_op, indent + 2)
|
202
|
-
|
203
|
-
else:
|
204
|
-
logger.info(f"{prefix}Operation: {op.name}")
|
205
|
-
|
206
|
-
def _execute_flow(self):
|
207
|
-
"""
|
208
|
-
Execute the parsed flow and return the result.
|
209
|
-
|
210
|
-
Returns:
|
211
|
-
The result of executing the flow
|
212
|
-
"""
|
213
|
-
return self._parsed_flow.execute()
|
flowllm/llm/__init__.py
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
from
|
1
|
+
from .litellm_llm import LiteLLMBaseLLM
|
2
|
+
from .openai_compatible_llm import OpenAICompatibleBaseLLM
|
flowllm/llm/base_llm.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import asyncio
|
1
2
|
import time
|
2
3
|
from abc import ABC
|
3
4
|
from typing import List, Callable
|
@@ -28,10 +29,10 @@ class BaseLLM(BaseModel, ABC):
|
|
28
29
|
stream_options: dict = Field(default={"include_usage": True}, description="Options for streaming responses")
|
29
30
|
temperature: float = Field(default=0.0000001, description="Sampling temperature (low for deterministic outputs)")
|
30
31
|
presence_penalty: float | None = Field(default=None, description="Presence penalty to reduce repetition")
|
31
|
-
|
32
|
+
|
32
33
|
# Model-specific features
|
33
34
|
enable_thinking: bool = Field(default=False, description="Enable reasoning/thinking mode for supported models")
|
34
|
-
|
35
|
+
|
35
36
|
# Tool usage configuration
|
36
37
|
tool_choice: str = Field(default=None, description="Strategy for tool selection")
|
37
38
|
parallel_tool_calls: bool = Field(default=True, description="Allow multiple tool calls in parallel")
|
@@ -57,6 +58,23 @@ class BaseLLM(BaseModel, ABC):
|
|
57
58
|
"""
|
58
59
|
raise NotImplementedError
|
59
60
|
|
61
|
+
async def astream_chat(self, messages: List[Message], tools: List[ToolCall] = None, **kwargs):
|
62
|
+
"""
|
63
|
+
Async stream chat completions from the LLM.
|
64
|
+
|
65
|
+
This method should yield chunks of the response as they become available,
|
66
|
+
allowing for real-time display of the model's output in async contexts.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
messages: List of conversation messages
|
70
|
+
tools: Optional list of tools the model can use
|
71
|
+
**kwargs: Additional model-specific parameters
|
72
|
+
|
73
|
+
Yields:
|
74
|
+
Chunks of the streaming response with their types
|
75
|
+
"""
|
76
|
+
raise NotImplementedError
|
77
|
+
|
60
78
|
def _chat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
61
79
|
**kwargs) -> Message:
|
62
80
|
"""
|
@@ -77,6 +95,26 @@ class BaseLLM(BaseModel, ABC):
|
|
77
95
|
"""
|
78
96
|
raise NotImplementedError
|
79
97
|
|
98
|
+
async def _achat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
99
|
+
**kwargs) -> Message:
|
100
|
+
"""
|
101
|
+
Internal async method to perform a single chat completion.
|
102
|
+
|
103
|
+
This method should be implemented by subclasses to handle the actual
|
104
|
+
async communication with the LLM provider. It's called by the public achat()
|
105
|
+
method which adds retry logic and error handling.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
messages: List of conversation messages
|
109
|
+
tools: Optional list of tools the model can use
|
110
|
+
enable_stream_print: Whether to print streaming response to console
|
111
|
+
**kwargs: Additional model-specific parameters
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
The complete response message from the LLM
|
115
|
+
"""
|
116
|
+
raise NotImplementedError
|
117
|
+
|
80
118
|
def chat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
81
119
|
callback_fn: Callable = None, default_value=None, **kwargs):
|
82
120
|
"""
|
@@ -107,7 +145,7 @@ class BaseLLM(BaseModel, ABC):
|
|
107
145
|
tools=tools,
|
108
146
|
enable_stream_print=enable_stream_print,
|
109
147
|
**kwargs)
|
110
|
-
|
148
|
+
|
111
149
|
# Apply callback function if provided
|
112
150
|
if callback_fn:
|
113
151
|
return callback_fn(message)
|
@@ -116,7 +154,7 @@ class BaseLLM(BaseModel, ABC):
|
|
116
154
|
|
117
155
|
except Exception as e:
|
118
156
|
logger.exception(f"chat with model={self.model_name} encounter error with e={e.args}")
|
119
|
-
|
157
|
+
|
120
158
|
# Exponential backoff: wait longer after each failure
|
121
159
|
time.sleep(1 + i)
|
122
160
|
|
@@ -128,3 +166,55 @@ class BaseLLM(BaseModel, ABC):
|
|
128
166
|
return default_value
|
129
167
|
|
130
168
|
return None
|
169
|
+
|
170
|
+
async def achat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
171
|
+
callback_fn: Callable = None, default_value=None, **kwargs):
|
172
|
+
"""
|
173
|
+
Perform an async chat completion with retry logic and error handling.
|
174
|
+
|
175
|
+
This is the main public interface for async chat completions. It wraps the
|
176
|
+
internal _achat() method with robust error handling, exponential backoff,
|
177
|
+
and optional callback processing.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
messages: List of conversation messages
|
181
|
+
tools: Optional list of tools the model can use
|
182
|
+
callback_fn: Optional callback to process the response message
|
183
|
+
default_value: Value to return if all retries fail (when raise_exception=False)
|
184
|
+
enable_stream_print: Whether to print streaming response to console
|
185
|
+
**kwargs: Additional model-specific parameters
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
The response message (possibly processed by callback_fn) or default_value
|
189
|
+
|
190
|
+
Raises:
|
191
|
+
Exception: If raise_exception=True and all retries fail
|
192
|
+
"""
|
193
|
+
for i in range(self.max_retries):
|
194
|
+
try:
|
195
|
+
# Attempt to get response from the model
|
196
|
+
message: Message = await self._achat(messages=messages,
|
197
|
+
tools=tools,
|
198
|
+
enable_stream_print=enable_stream_print,
|
199
|
+
**kwargs)
|
200
|
+
|
201
|
+
# Apply callback function if provided
|
202
|
+
if callback_fn:
|
203
|
+
return callback_fn(message)
|
204
|
+
else:
|
205
|
+
return message
|
206
|
+
|
207
|
+
except Exception as e:
|
208
|
+
logger.exception(f"async chat with model={self.model_name} encounter error with e={e.args}")
|
209
|
+
|
210
|
+
# Exponential backoff: wait longer after each failure
|
211
|
+
await asyncio.sleep(1 + i)
|
212
|
+
|
213
|
+
# Handle final retry failure
|
214
|
+
if i == self.max_retries - 1:
|
215
|
+
if self.raise_exception:
|
216
|
+
raise e
|
217
|
+
else:
|
218
|
+
return default_value
|
219
|
+
|
220
|
+
return None
|