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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. flowllm/__init__.py +15 -6
  2. flowllm/app.py +4 -14
  3. flowllm/client/__init__.py +25 -0
  4. flowllm/client/async_http_client.py +81 -0
  5. flowllm/client/http_client.py +81 -0
  6. flowllm/client/mcp_client.py +133 -0
  7. flowllm/client/sync_mcp_client.py +116 -0
  8. flowllm/config/__init__.py +1 -0
  9. flowllm/config/{default_config.yaml → default.yaml} +3 -8
  10. flowllm/config/empty.yaml +37 -0
  11. flowllm/config/pydantic_config_parser.py +17 -17
  12. flowllm/context/base_context.py +27 -7
  13. flowllm/context/flow_context.py +6 -18
  14. flowllm/context/registry.py +5 -1
  15. flowllm/context/service_context.py +81 -37
  16. flowllm/embedding_model/__init__.py +1 -1
  17. flowllm/embedding_model/base_embedding_model.py +91 -0
  18. flowllm/embedding_model/openai_compatible_embedding_model.py +63 -5
  19. flowllm/flow/__init__.py +1 -0
  20. flowllm/flow/base_flow.py +72 -0
  21. flowllm/flow/base_tool_flow.py +15 -0
  22. flowllm/flow/gallery/__init__.py +8 -0
  23. flowllm/flow/gallery/cmd_flow.py +11 -0
  24. flowllm/flow/gallery/code_tool_flow.py +30 -0
  25. flowllm/flow/gallery/dashscope_search_tool_flow.py +34 -0
  26. flowllm/flow/gallery/deepsearch_tool_flow.py +39 -0
  27. flowllm/flow/gallery/expression_tool_flow.py +18 -0
  28. flowllm/flow/gallery/mock_tool_flow.py +67 -0
  29. flowllm/flow/gallery/tavily_search_tool_flow.py +30 -0
  30. flowllm/flow/gallery/terminate_tool_flow.py +30 -0
  31. flowllm/flow/parser/__init__.py +0 -0
  32. flowllm/{flow_engine/simple_flow_engine.py → flow/parser/expression_parser.py} +25 -67
  33. flowllm/llm/__init__.py +2 -1
  34. flowllm/llm/base_llm.py +94 -4
  35. flowllm/llm/litellm_llm.py +455 -0
  36. flowllm/llm/openai_compatible_llm.py +205 -5
  37. flowllm/op/__init__.py +11 -3
  38. flowllm/op/agent/__init__.py +0 -0
  39. flowllm/op/agent/react_op.py +83 -0
  40. flowllm/op/agent/react_prompt.yaml +28 -0
  41. flowllm/op/akshare/__init__.py +3 -0
  42. flowllm/op/akshare/get_ak_a_code_op.py +14 -22
  43. flowllm/op/akshare/get_ak_a_info_op.py +17 -20
  44. flowllm/op/{llm_base_op.py → base_llm_op.py} +6 -5
  45. flowllm/op/base_op.py +14 -35
  46. flowllm/op/base_ray_op.py +313 -0
  47. flowllm/op/code/__init__.py +1 -0
  48. flowllm/op/code/execute_code_op.py +42 -0
  49. flowllm/op/gallery/__init__.py +2 -0
  50. flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
  51. flowllm/op/gallery/terminate_op.py +29 -0
  52. flowllm/op/parallel_op.py +2 -9
  53. flowllm/op/search/__init__.py +3 -0
  54. flowllm/op/search/dashscope_deep_research_op.py +260 -0
  55. flowllm/op/search/dashscope_search_op.py +179 -0
  56. flowllm/op/search/dashscope_search_prompt.yaml +13 -0
  57. flowllm/op/search/tavily_search_op.py +102 -0
  58. flowllm/op/sequential_op.py +1 -9
  59. flowllm/schema/flow_request.py +12 -0
  60. flowllm/schema/service_config.py +12 -16
  61. flowllm/schema/tool_call.py +13 -5
  62. flowllm/schema/vector_node.py +1 -0
  63. flowllm/service/__init__.py +3 -2
  64. flowllm/service/base_service.py +50 -41
  65. flowllm/service/cmd_service.py +15 -0
  66. flowllm/service/http_service.py +34 -42
  67. flowllm/service/mcp_service.py +13 -11
  68. flowllm/storage/cache/__init__.py +1 -0
  69. flowllm/storage/cache/cache_data_handler.py +104 -0
  70. flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
  71. flowllm/storage/vector_store/__init__.py +3 -3
  72. flowllm/storage/vector_store/es_vector_store.py +1 -2
  73. flowllm/storage/vector_store/local_vector_store.py +0 -1
  74. flowllm/utils/common_utils.py +9 -21
  75. flowllm/utils/fetch_url.py +16 -12
  76. flowllm/utils/llm_utils.py +28 -0
  77. flowllm/utils/ridge_v2.py +54 -0
  78. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/METADATA +43 -390
  79. flowllm-0.1.2.dist-info/RECORD +99 -0
  80. flowllm-0.1.2.dist-info/entry_points.txt +2 -0
  81. flowllm/flow_engine/__init__.py +0 -1
  82. flowllm/flow_engine/base_flow_engine.py +0 -34
  83. flowllm-0.1.1.dist-info/RECORD +0 -62
  84. flowllm-0.1.1.dist-info/entry_points.txt +0 -4
  85. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/WHEEL +0 -0
  86. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/licenses/LICENSE +0 -0
  87. {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,67 @@
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
+ "arguments": {
29
+ "input_data": "sample_data",
30
+ "processing_mode": "advanced",
31
+ "output_format": "json"
32
+ },
33
+ "input_schema": {
34
+ "input_data": ParamAttrs(
35
+ type="string",
36
+ description="The input data to be processed",
37
+ required=True
38
+ ),
39
+ "processing_mode": ParamAttrs(
40
+ type="string",
41
+ description="Processing mode: basic, advanced, or expert",
42
+ required=False
43
+ ),
44
+ "output_format": ParamAttrs(
45
+ type="string",
46
+ description="Output format: json, xml, or plain",
47
+ required=False
48
+ )
49
+ },
50
+ "output_schema": {
51
+ "result": ParamAttrs(
52
+ type="object",
53
+ description="Processed result data",
54
+ required=True
55
+ ),
56
+ "status": ParamAttrs(
57
+ type="string",
58
+ description="Processing status: success, warning, or error",
59
+ required=True
60
+ ),
61
+ "metadata": ParamAttrs(
62
+ type="object",
63
+ description="Additional metadata about the processing",
64
+ required=False
65
+ )
66
+ }
67
+ })
@@ -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": "Please determine whether the user's question has been completed. (success / 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
- @C.register_flow_engine("simple")
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 and executing operation expressions.
20
-
15
+ Simple flow implementation that supports parsing operation expressions.
16
+
21
17
  Supports flow expressions like:
22
- - "op1 >> op2" (sequential execution)
23
- - "op1 | op2" (parallel execution)
24
- - "op1 >> (op2 | op3) >> op4" (mixed execution)
25
- - "op1 >> (op1 | (op2 >> op3)) >> op4" (complex nested execution)
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 _parse_flow(self):
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
- self._parsed_flow = self._parse_expression(expression)
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, flow_context=self.flow_context)
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, flow_context=self.flow_context)
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
- def _create_op(self, op_name: str) -> BaseOp:
140
- if op_name in self.flow_context.service_config.op:
141
- op_config: OpConfig = self.flow_context.service_config.op[op_name]
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 flowllm.llm.openai_compatible_llm import OpenAICompatibleBaseLLM
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