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.
Files changed (92) hide show
  1. flowllm/__init__.py +19 -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 +83 -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 +74 -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 +62 -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 +456 -0
  36. flowllm/llm/openai_compatible_llm.py +205 -5
  37. flowllm/op/__init__.py +12 -3
  38. flowllm/op/agent/__init__.py +1 -0
  39. flowllm/op/agent/react_v1_op.py +109 -0
  40. flowllm/op/agent/react_v1_prompt.yaml +54 -0
  41. flowllm/op/agent/react_v2_op.py +86 -0
  42. flowllm/op/agent/react_v2_prompt.yaml +35 -0
  43. flowllm/op/akshare/__init__.py +3 -0
  44. flowllm/op/akshare/get_ak_a_code_op.py +14 -22
  45. flowllm/op/akshare/get_ak_a_info_op.py +17 -20
  46. flowllm/op/{llm_base_op.py → base_llm_op.py} +7 -5
  47. flowllm/op/base_op.py +40 -44
  48. flowllm/op/base_ray_op.py +313 -0
  49. flowllm/op/code/__init__.py +1 -0
  50. flowllm/op/code/execute_code_op.py +42 -0
  51. flowllm/op/gallery/__init__.py +2 -0
  52. flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
  53. flowllm/op/gallery/terminate_op.py +29 -0
  54. flowllm/op/parallel_op.py +2 -9
  55. flowllm/op/search/__init__.py +3 -0
  56. flowllm/op/search/dashscope_deep_research_op.py +267 -0
  57. flowllm/op/search/dashscope_search_op.py +186 -0
  58. flowllm/op/search/dashscope_search_prompt.yaml +13 -0
  59. flowllm/op/search/tavily_search_op.py +109 -0
  60. flowllm/op/sequential_op.py +1 -9
  61. flowllm/schema/flow_request.py +12 -0
  62. flowllm/schema/message.py +2 -0
  63. flowllm/schema/service_config.py +12 -16
  64. flowllm/schema/tool_call.py +20 -8
  65. flowllm/schema/vector_node.py +1 -0
  66. flowllm/service/__init__.py +3 -2
  67. flowllm/service/base_service.py +50 -41
  68. flowllm/service/cmd_service.py +15 -0
  69. flowllm/service/http_service.py +34 -42
  70. flowllm/service/mcp_service.py +13 -11
  71. flowllm/storage/cache/__init__.py +1 -0
  72. flowllm/storage/cache/cache_data_handler.py +104 -0
  73. flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
  74. flowllm/storage/vector_store/__init__.py +3 -3
  75. flowllm/storage/vector_store/base_vector_store.py +3 -0
  76. flowllm/storage/vector_store/es_vector_store.py +4 -5
  77. flowllm/storage/vector_store/local_vector_store.py +0 -1
  78. flowllm/utils/common_utils.py +9 -21
  79. flowllm/utils/fetch_url.py +16 -12
  80. flowllm/utils/llm_utils.py +28 -0
  81. flowllm/utils/logger_utils.py +28 -0
  82. flowllm/utils/ridge_v2.py +54 -0
  83. {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/METADATA +43 -390
  84. flowllm-0.1.3.dist-info/RECORD +102 -0
  85. flowllm-0.1.3.dist-info/entry_points.txt +2 -0
  86. flowllm/flow_engine/__init__.py +0 -1
  87. flowllm/flow_engine/base_flow_engine.py +0 -34
  88. flowllm-0.1.1.dist-info/RECORD +0 -62
  89. flowllm-0.1.1.dist-info/entry_points.txt +0 -4
  90. {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/WHEEL +0 -0
  91. {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/licenses/LICENSE +0 -0
  92. {flowllm-0.1.1.dist-info → flowllm-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,20 @@
1
- from typing import List
2
-
3
-
4
1
  class BaseContext:
5
2
  def __init__(self, **kwargs):
6
3
  self._data = {**kwargs}
7
4
 
8
5
  def __getattr__(self, name: str):
9
- if name in self._data:
10
- return self._data[name]
6
+ # Avoid infinite recursion when _data is not yet initialized
7
+ if name == '_data':
8
+ raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
9
+
10
+ # Use object.__getattribute__ to safely access _data
11
+ try:
12
+ data = object.__getattribute__(self, '_data')
13
+ except AttributeError:
14
+ raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
15
+
16
+ if name in data:
17
+ return data[name]
11
18
 
12
19
  raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
13
20
 
@@ -34,13 +41,26 @@ class BaseContext:
34
41
  def dump(self) -> dict:
35
42
  return self._data
36
43
 
44
+ def get(self, key: str, default=None):
45
+ return self._data.get(key, default)
46
+
37
47
  @property
38
- def keys(self) -> List[str]:
39
- return sorted(self._data.keys())
48
+ def keys(self):
49
+ return self._data.keys()
40
50
 
41
51
  def update(self, **kwargs):
42
52
  self._data.update(kwargs)
43
53
 
54
+ def items(self):
55
+ return self._data.items()
56
+
57
+ def __getstate__(self):
58
+ """Support for pickle serialization"""
59
+ return {'_data': self._data}
60
+
61
+ def __setstate__(self, state):
62
+ """Support for pickle deserialization"""
63
+ self._data = state['_data']
44
64
 
45
65
  if __name__ == "__main__":
46
66
  ctx = BaseContext(**{"name": "Alice", "age": 30, "city": "New York"})
@@ -2,27 +2,15 @@ import uuid
2
2
 
3
3
  from flowllm.context.base_context import BaseContext
4
4
  from flowllm.schema.flow_response import FlowResponse
5
- from flowllm.schema.service_config import ServiceConfig
6
5
 
7
6
 
8
7
  class FlowContext(BaseContext):
9
8
 
10
- def __init__(self, flow_id: str = uuid.uuid4().hex, **kwargs):
9
+ def __init__(self,
10
+ flow_id: str = uuid.uuid4().hex,
11
+ response: FlowResponse = None,
12
+ **kwargs):
11
13
  super().__init__(**kwargs)
12
- self.flow_id: str = flow_id
13
-
14
- @property
15
- def service_config(self) -> ServiceConfig:
16
- return self._data.get("service_config")
17
-
18
- @service_config.setter
19
- def service_config(self, service_config: ServiceConfig):
20
- self._data["service_config"] = service_config
21
14
 
22
- @property
23
- def response(self) -> FlowResponse:
24
- return self._data.get("response")
25
-
26
- @response.setter
27
- def response(self, response: FlowResponse):
28
- self._data["response"] = response
15
+ self.flow_id: str = flow_id
16
+ self.response: FlowResponse = FlowResponse() if response is None else response
@@ -6,13 +6,17 @@ from flowllm.utils.common_utils import camel_to_snake
6
6
 
7
7
  class Registry(BaseContext):
8
8
 
9
- def __init__(self, registry_name: str, enable_log: bool = True, **kwargs):
9
+ def __init__(self, registry_name: str, enable_log: bool = True, register_flow_module: bool = True, **kwargs):
10
10
  super().__init__(**kwargs)
11
11
  self.registry_name: str = registry_name
12
12
  self.enable_log: bool = enable_log
13
+ self.register_flow_module: bool = register_flow_module
13
14
 
14
15
  def register(self, name: str = ""):
15
16
  def decorator(cls):
17
+ if not self.register_flow_module and cls.__module__.startswith("flowllm"):
18
+ return cls
19
+
16
20
  class_name = name if name else camel_to_snake(cls.__name__)
17
21
  if self.enable_log:
18
22
  if class_name in self._data:
@@ -1,9 +1,15 @@
1
+ import os
1
2
  import uuid
2
3
  from concurrent.futures import ThreadPoolExecutor
3
- from typing import Dict
4
+ from inspect import isclass
5
+ from typing import Dict, List
6
+
7
+ import ray
8
+ from loguru import logger
4
9
 
5
10
  from flowllm.context.base_context import BaseContext
6
11
  from flowllm.context.registry import Registry
12
+ from flowllm.schema.service_config import ServiceConfig, EmbeddingModelConfig
7
13
  from flowllm.utils.singleton import singleton
8
14
 
9
15
 
@@ -12,41 +18,82 @@ class ServiceContext(BaseContext):
12
18
 
13
19
  def __init__(self, service_id: str = uuid.uuid4().hex, **kwargs):
14
20
  super().__init__(**kwargs)
15
- self.service_id: str = service_id
16
- self.registry_dict: Dict[str, Registry] = \
17
- {k: Registry(k) for k in ["embedding_model", "llm", "vector_store", "op", "flow_engine", "service"]}
18
-
19
- @property
20
- def language(self) -> str:
21
- return self._data.get("language", "")
22
-
23
- @language.setter
24
- def language(self, value: str):
25
- self._data["language"] = value
26
-
27
- @property
28
- def thread_pool(self) -> ThreadPoolExecutor:
29
- return self._data["thread_pool"]
30
21
 
31
- @thread_pool.setter
32
- def thread_pool(self, thread_pool: ThreadPoolExecutor):
33
- self._data["thread_pool"] = thread_pool
22
+ self.service_id: str = service_id
23
+ self.service_config: ServiceConfig | None = None
24
+ self.language: str = ""
25
+ self.thread_pool: ThreadPoolExecutor | None = None
26
+ self.vector_store_dict: dict = {}
27
+
28
+ self.registry_dict: Dict[str, Registry] = {}
29
+ use_framework: bool = os.environ.get("FLOW_USE_FRAMEWORK", "").lower() == "true"
30
+ for key in ["embedding_model", "llm", "vector_store", "op", "tool_flow", "service"]:
31
+ enable_log = True
32
+ register_flow_module = True
33
+
34
+ if use_framework:
35
+ enable_log = False
36
+ if key in ["op", "tool_flow"]:
37
+ register_flow_module = False
38
+ self.registry_dict[key] = Registry(key, enable_log=enable_log, register_flow_module=register_flow_module)
39
+
40
+ self.tool_flow_dict: dict = {}
41
+
42
+ def set_default_service_config(self, parser=None):
43
+ if parser is None:
44
+ from flowllm.config.pydantic_config_parser import PydanticConfigParser
45
+ parser = PydanticConfigParser
46
+
47
+ config_parser = parser(ServiceConfig)
48
+ self.service_config = config_parser.parse_args("config=default")
49
+ return self
50
+
51
+ def init_by_service_config(self, service_config: ServiceConfig = None):
52
+ if service_config:
53
+ self.service_config = service_config
54
+
55
+ self.language = self.service_config.language
56
+ self.thread_pool = ThreadPoolExecutor(max_workers=self.service_config.thread_pool_max_workers)
57
+ if self.service_config.ray_max_workers > 1:
58
+ ray.init(num_cpus=self.service_config.ray_max_workers)
59
+
60
+ # add vector store
61
+ for name, config in self.service_config.vector_store.items():
62
+ vector_store_cls = self.resolve_vector_store(config.backend)
63
+ embedding_model_config: EmbeddingModelConfig = self.service_config.embedding_model[config.embedding_model]
64
+ embedding_model_cls = self.resolve_embedding_model(embedding_model_config.backend)
65
+ embedding_model = embedding_model_cls(model_name=embedding_model_config.model_name,
66
+ **embedding_model_config.params)
67
+ self.vector_store_dict[name] = vector_store_cls(embedding_model=embedding_model, **config.params)
68
+
69
+ from flowllm.flow.base_tool_flow import BaseToolFlow
70
+ from flowllm.flow.gallery import ExpressionToolFlow
71
+
72
+ # add tool flow cls
73
+ for name, tool_flow_cls in self.registry_dict["tool_flow"].items():
74
+ if not isclass(tool_flow_cls):
75
+ continue
76
+
77
+ tool_flow: BaseToolFlow = tool_flow_cls()
78
+ self.tool_flow_dict[tool_flow.name] = tool_flow
79
+ logger.info(f"add diy tool_flow: {tool_flow.name}")
80
+
81
+ # add tool flow config
82
+ for name, flow_config in self.service_config.flow.items():
83
+ flow_config.name = name
84
+ tool_flow: BaseToolFlow = ExpressionToolFlow(flow_config=flow_config)
85
+ self.tool_flow_dict[tool_flow.name] = tool_flow
86
+ logger.info(f"add expression tool_flow:{tool_flow.name}")
34
87
 
35
88
  def get_vector_store(self, name: str = "default"):
36
- vector_store_dict: dict = self._data["vector_store_dict"]
37
- if name not in vector_store_dict:
38
- raise KeyError(f"vector store {name} not found")
39
-
40
- return vector_store_dict[name]
89
+ return self.vector_store_dict[name]
41
90
 
42
- def set_vector_store(self, name: str, vector_store):
43
- if "vector_store_dict" not in self._data:
44
- self.set_vector_stores({})
91
+ def get_tool_flow(self, name: str = "default"):
92
+ return self.tool_flow_dict[name]
45
93
 
46
- self._data["vector_store_dict"][name] = vector_store
47
-
48
- def set_vector_stores(self, vector_store_dict: dict):
49
- self._data["vector_store_dict"] = vector_store_dict
94
+ @property
95
+ def tool_flow_names(self) -> List[str]:
96
+ return sorted(self.tool_flow_dict.keys())
50
97
 
51
98
  """
52
99
  register models
@@ -64,8 +111,8 @@ class ServiceContext(BaseContext):
64
111
  def register_op(self, name: str = ""):
65
112
  return self.registry_dict["op"].register(name=name)
66
113
 
67
- def register_flow_engine(self, name: str = ""):
68
- return self.registry_dict["flow_engine"].register(name=name)
114
+ def register_tool_flow(self, name: str = ""):
115
+ return self.registry_dict["tool_flow"].register(name=name)
69
116
 
70
117
  def register_service(self, name: str = ""):
71
118
  return self.registry_dict["service"].register(name=name)
@@ -90,14 +137,13 @@ class ServiceContext(BaseContext):
90
137
  assert name in self.registry_dict["op"], f"op={name} not found!"
91
138
  return self.registry_dict["op"][name]
92
139
 
93
- def resolve_flow_engine(self, name: str):
94
- assert name in self.registry_dict["flow_engine"], f"flow={name} not found!"
95
- return self.registry_dict["flow_engine"][name]
140
+ def resolve_tool_flow(self, name: str):
141
+ assert name in self.registry_dict["tool_flow"], f"tool_flow={name} not found!"
142
+ return self.registry_dict["tool_flow"][name]
96
143
 
97
144
  def resolve_service(self, name: str):
98
145
  assert name in self.registry_dict["service"], f"service={name} not found!"
99
146
  return self.registry_dict["service"][name]
100
147
 
101
148
 
102
-
103
149
  C = ServiceContext()
@@ -1 +1 @@
1
- from flowllm.embedding_model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
1
+ from .openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from abc import ABC
2
3
  from typing import List
3
4
 
@@ -37,6 +38,21 @@ class BaseEmbeddingModel(BaseModel, ABC):
37
38
  """
38
39
  raise NotImplementedError
39
40
 
41
+ async def _get_embeddings_async(self, input_text: str | List[str]):
42
+ """
43
+ Abstract async method to get embeddings from the model.
44
+
45
+ This method must be implemented by concrete subclasses to provide
46
+ the actual async embedding functionality.
47
+
48
+ Args:
49
+ input_text: Single text string or list of text strings to embed
50
+
51
+ Returns:
52
+ Embedding vector(s) corresponding to the input text(s)
53
+ """
54
+ raise NotImplementedError
55
+
40
56
  def get_embeddings(self, input_text: str | List[str]):
41
57
  """
42
58
  Get embeddings with retry logic and error handling.
@@ -64,6 +80,33 @@ class BaseEmbeddingModel(BaseModel, ABC):
64
80
  # Return None if all retries failed and raise_exception is False
65
81
  return None
66
82
 
83
+ async def get_embeddings_async(self, input_text: str | List[str]):
84
+ """
85
+ Get embeddings asynchronously with retry logic and error handling.
86
+
87
+ This method wraps the _get_embeddings_async method with automatic retry
88
+ functionality in case of failures.
89
+
90
+ Args:
91
+ input_text: Single text string or list of text strings to embed
92
+
93
+ Returns:
94
+ Embedding vector(s) or None if all retries failed and raise_exception is False
95
+ """
96
+ # Retry loop with exponential backoff potential
97
+ for i in range(self.max_retries):
98
+ try:
99
+ return await self._get_embeddings_async(input_text)
100
+
101
+ except Exception as e:
102
+ logger.exception(f"embedding model name={self.model_name} encounter error with e={e.args}")
103
+ # If this is the last retry and raise_exception is True, re-raise the exception
104
+ if i == self.max_retries - 1 and self.raise_exception:
105
+ raise e
106
+
107
+ # Return None if all retries failed and raise_exception is False
108
+ return None
109
+
67
110
  def get_node_embeddings(self, nodes: VectorNode | List[VectorNode]):
68
111
  """
69
112
  Generate embeddings for VectorNode objects and update their vector fields.
@@ -102,3 +145,51 @@ class BaseEmbeddingModel(BaseModel, ABC):
102
145
 
103
146
  else:
104
147
  raise TypeError(f"unsupported type={type(nodes)}")
148
+
149
+ async def get_node_embeddings_async(self, nodes: VectorNode | List[VectorNode]):
150
+ """
151
+ Generate embeddings asynchronously for VectorNode objects and update their vector fields.
152
+
153
+ This method handles both single nodes and lists of nodes, with automatic
154
+ batching for efficient processing of large node lists.
155
+
156
+ Args:
157
+ nodes: Single VectorNode or list of VectorNode objects to embed
158
+
159
+ Returns:
160
+ The same node(s) with updated vector fields containing embeddings
161
+
162
+ Raises:
163
+ RuntimeError: If unsupported node type is provided
164
+ """
165
+ # Handle single VectorNode
166
+ if isinstance(nodes, VectorNode):
167
+ nodes.vector = await self.get_embeddings_async(nodes.content)
168
+ return nodes
169
+
170
+ # Handle list of VectorNodes with batch processing
171
+ elif isinstance(nodes, list):
172
+ # Process nodes in batches to respect max_batch_size limits
173
+ batch_tasks = []
174
+ for i in range(0, len(nodes), self.max_batch_size):
175
+ batch_nodes = nodes[i:i + self.max_batch_size]
176
+ batch_content = [node.content for node in batch_nodes]
177
+ batch_tasks.append(self.get_embeddings_async(batch_content))
178
+
179
+ # Execute all batch tasks concurrently
180
+ batch_results = await asyncio.gather(*batch_tasks)
181
+
182
+ # Flatten the results
183
+ embeddings = [emb for batch_result in batch_results for emb in batch_result]
184
+
185
+ # Validate that we got the expected number of embeddings
186
+ if len(embeddings) != len(nodes):
187
+ logger.warning(f"embeddings.size={len(embeddings)} <> nodes.size={len(nodes)}")
188
+ else:
189
+ # Assign embeddings to corresponding nodes
190
+ for node, embedding in zip(nodes, embeddings):
191
+ node.vector = embedding
192
+ return nodes
193
+
194
+ else:
195
+ raise TypeError(f"unsupported type={type(nodes)}")
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  from typing import Literal, List
3
3
 
4
- from openai import OpenAI
4
+ from openai import OpenAI, AsyncOpenAI
5
5
  from pydantic import Field, PrivateAttr, model_validator
6
6
 
7
7
  from flowllm.context.service_context import C
@@ -26,21 +26,23 @@ class OpenAICompatibleEmbeddingModel(BaseEmbeddingModel):
26
26
  dimensions: int = Field(default=1024, description="Dimensionality of the embedding vectors")
27
27
  encoding_format: Literal["float", "base64"] = Field(default="float", description="Encoding format for embeddings")
28
28
 
29
- # Private OpenAI client instance
29
+ # Private OpenAI client instances
30
30
  _client: OpenAI = PrivateAttr()
31
+ _async_client: AsyncOpenAI = PrivateAttr()
31
32
 
32
33
  @model_validator(mode="after")
33
34
  def init_client(self):
34
35
  """
35
- Initialize the OpenAI client after model validation.
36
+ Initialize the OpenAI clients after model validation.
36
37
 
37
38
  This method is called automatically after Pydantic model validation
38
- to set up the OpenAI client with the provided API key and base URL.
39
+ to set up both sync and async OpenAI clients with the provided API key and base URL.
39
40
 
40
41
  Returns:
41
42
  self: The model instance for method chaining
42
43
  """
43
44
  self._client = OpenAI(api_key=self.api_key, base_url=self.base_url)
45
+ self._async_client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url)
44
46
  return self
45
47
 
46
48
  def _get_embeddings(self, input_text: str | List[str]):
@@ -78,6 +80,41 @@ class OpenAICompatibleEmbeddingModel(BaseEmbeddingModel):
78
80
  else:
79
81
  raise RuntimeError(f"unsupported type={type(input_text)}")
80
82
 
83
+ async def _get_embeddings_async(self, input_text: str | List[str]):
84
+ """
85
+ Get embeddings asynchronously from the OpenAI-compatible API.
86
+
87
+ This method implements the abstract _get_embeddings_async method from BaseEmbeddingModel
88
+ by calling the OpenAI-compatible embeddings API asynchronously.
89
+
90
+ Args:
91
+ input_text: Single text string or list of text strings to embed
92
+
93
+ Returns:
94
+ Embedding vector(s) corresponding to the input text(s)
95
+
96
+ Raises:
97
+ RuntimeError: If unsupported input type is provided
98
+ """
99
+ completion = await self._async_client.embeddings.create(
100
+ model=self.model_name,
101
+ input=input_text,
102
+ dimensions=self.dimensions,
103
+ encoding_format=self.encoding_format
104
+ )
105
+
106
+ if isinstance(input_text, str):
107
+ return completion.data[0].embedding
108
+
109
+ elif isinstance(input_text, list):
110
+ result_emb = [[] for _ in range(len(input_text))]
111
+ for emb in completion.data:
112
+ result_emb[emb.index] = emb.embedding
113
+ return result_emb
114
+
115
+ else:
116
+ raise RuntimeError(f"unsupported type={type(input_text)}")
117
+
81
118
 
82
119
  def main():
83
120
  from flowllm.utils.common_utils import load_env
@@ -91,5 +128,26 @@ def main():
91
128
  print(res2)
92
129
 
93
130
 
131
+ async def async_main():
132
+ from flowllm.utils.common_utils import load_env
133
+
134
+ load_env()
135
+ model = OpenAICompatibleEmbeddingModel(dimensions=64, model_name="text-embedding-v4")
136
+
137
+ # Test async single text embedding
138
+ res1 = await model.get_embeddings_async(
139
+ "The clothes are of good quality and look good, definitely worth the wait. I love them.")
140
+
141
+ # Test async batch text embedding
142
+ res2 = await model.get_embeddings_async(["aa", "bb"])
143
+
144
+ print("Async results:")
145
+ print(res1)
146
+ print(res2)
147
+
148
+
94
149
  if __name__ == "__main__":
95
- main()
150
+ # main()
151
+ import asyncio
152
+
153
+ asyncio.run(async_main())
@@ -0,0 +1 @@
1
+ from . import gallery
@@ -0,0 +1,74 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from loguru import logger
5
+
6
+ from flowllm.context.flow_context import FlowContext
7
+ from flowllm.op.base_op import BaseOp
8
+ from flowllm.op.parallel_op import ParallelOp
9
+ from flowllm.op.sequential_op import SequentialOp
10
+ from flowllm.schema.flow_response import FlowResponse
11
+ from flowllm.utils.common_utils import camel_to_snake
12
+
13
+
14
+ class BaseFlow(ABC):
15
+
16
+ def __init__(self, name: str = "", **kwargs):
17
+ self.name: str = name or camel_to_snake(self.__class__.__name__)
18
+ self.flow_params: dict = kwargs
19
+
20
+ self.flow_op: BaseOp = self.build_flow()
21
+ self.print_flow()
22
+
23
+ @abstractmethod
24
+ def build_flow(self):
25
+ ...
26
+
27
+ def print_flow(self):
28
+ assert self.flow_op is not None, "flow_content is not parsed!"
29
+ logger.info(f"---------- start print flow={self.name} ----------")
30
+ self._print_operation_tree(self.flow_op, indent=0)
31
+ logger.info(f"---------- end print flow={self.name} ----------")
32
+
33
+ def _print_operation_tree(self, op: BaseOp, indent: int):
34
+ """
35
+ Recursively print the operation tree structure.
36
+
37
+ Args:
38
+ op: The operation to print
39
+ indent: Current indentation level
40
+ """
41
+ prefix = " " * indent
42
+ if isinstance(op, SequentialOp):
43
+ logger.info(f"{prefix}Sequential Execution:")
44
+ for i, sub_op in enumerate(op.ops):
45
+ logger.info(f"{prefix} Step {i + 1}:")
46
+ self._print_operation_tree(sub_op, indent + 2)
47
+
48
+ elif isinstance(op, ParallelOp):
49
+ logger.info(f"{prefix}Parallel Execution:")
50
+ for i, sub_op in enumerate(op.ops):
51
+ logger.info(f"{prefix} Branch {i + 1}:")
52
+ self._print_operation_tree(sub_op, indent + 2)
53
+
54
+ else:
55
+ logger.info(f"{prefix}Operation: {op.name}")
56
+
57
+ def return_callback(self, context: FlowContext):
58
+ logger.info(f"context.response={context.response.model_dump_json()}")
59
+ return context.response
60
+
61
+ def __call__(self, **kwargs) -> FlowResponse:
62
+ context = FlowContext(**kwargs)
63
+ logger.info(f"request.params={kwargs}")
64
+
65
+ try:
66
+ flow_op = self.build_flow()
67
+ flow_op(context=context)
68
+
69
+ except Exception as e:
70
+ logger.exception(f"flow_name={self.name} encounter error={e.args}")
71
+ context.response.success = False
72
+ context.response.answer = str(e.args)
73
+
74
+ return self.return_callback(context=context)
@@ -0,0 +1,15 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from flowllm.flow.base_flow import BaseFlow
4
+ from flowllm.schema.tool_call import ToolCall
5
+
6
+
7
+ class BaseToolFlow(BaseFlow, ABC):
8
+
9
+ def __init__(self, **kwargs):
10
+ super().__init__(**kwargs)
11
+ self.tool_call: ToolCall = self.build_tool_call()
12
+
13
+ @abstractmethod
14
+ def build_tool_call(self) -> ToolCall:
15
+ ...
@@ -0,0 +1,8 @@
1
+ from .cmd_flow import CmdFlow
2
+ from .code_tool_flow import CodeToolFlow
3
+ from .dashscope_search_tool_flow import DashscopeSearchToolFlow
4
+ from .deepsearch_tool_flow import DeepSearchToolFlow
5
+ from .expression_tool_flow import ExpressionToolFlow
6
+ from .mock_tool_flow import MockToolFlow
7
+ from .tavily_search_tool_flow import TavilySearchToolFlow
8
+ from .terminate_tool_flow import TerminateToolFlow
@@ -0,0 +1,11 @@
1
+ from flowllm.flow.base_flow import BaseFlow
2
+ from flowllm.flow.parser.expression_parser import ExpressionParser
3
+
4
+
5
+ class CmdFlow(BaseFlow):
6
+
7
+ def build_flow(self):
8
+ flow: str = self.flow_params["flow"]
9
+ assert flow, "add `flow=<op_flow>` in cmd!"
10
+ parser = ExpressionParser(flow)
11
+ return parser.parse_flow()
@@ -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.code.execute_code_op import ExecuteCodeOp
5
+ from flowllm.schema.tool_call import ToolCall
6
+
7
+
8
+ @C.register_tool_flow()
9
+ class CodeToolFlow(BaseToolFlow):
10
+
11
+ def build_flow(self):
12
+ return ExecuteCodeOp()
13
+
14
+ def build_tool_call(self) -> ToolCall:
15
+ return ToolCall(**{
16
+ "name": "python_execute",
17
+ "description": "Execute python code can be used in scenarios such as analysis or calculation, and the final result can be printed using the `print` function.",
18
+ "input_schema": {
19
+ "code": {
20
+ "type": "str",
21
+ "description": "code to be executed. Please do not execute any matplotlib code here.",
22
+ "required": True
23
+ }
24
+ }
25
+ })
26
+
27
+ def return_callback(self, context: FlowContext):
28
+ context.response.answer = context.code_result
29
+ return context.response
30
+
@@ -0,0 +1,34 @@
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 DashscopeSearchOp
5
+ from flowllm.schema.tool_call import ToolCall
6
+
7
+
8
+ @C.register_tool_flow()
9
+ class DashscopeSearchToolFlow(BaseToolFlow):
10
+
11
+ def build_flow(self):
12
+ return DashscopeSearchOp()
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.dashscope_search_result
29
+ return context.response
30
+
31
+
32
+ if __name__ == "__main__":
33
+ flow = DashscopeSearchToolFlow()
34
+ flow(query="what is AI?")