flowllm 0.1.0__py3-none-any.whl → 0.1.1__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 +12 -0
- flowllm/app.py +25 -0
- flowllm/config/default_config.yaml +82 -0
- flowllm/config/pydantic_config_parser.py +242 -0
- flowllm/context/base_context.py +59 -0
- flowllm/context/flow_context.py +28 -0
- llmflow/op/prompt_mixin.py → flowllm/context/prompt_handler.py +25 -14
- flowllm/context/registry.py +26 -0
- flowllm/context/service_context.py +103 -0
- flowllm/embedding_model/__init__.py +1 -0
- {llmflow → flowllm}/embedding_model/base_embedding_model.py +2 -2
- {llmflow → flowllm}/embedding_model/openai_compatible_embedding_model.py +8 -8
- flowllm/flow_engine/__init__.py +1 -0
- flowllm/flow_engine/base_flow_engine.py +34 -0
- flowllm/flow_engine/simple_flow_engine.py +213 -0
- flowllm/llm/__init__.py +1 -0
- {llmflow → flowllm}/llm/base_llm.py +16 -24
- {llmflow → flowllm}/llm/openai_compatible_llm.py +64 -108
- flowllm/op/__init__.py +3 -0
- flowllm/op/akshare/get_ak_a_code_op.py +116 -0
- flowllm/op/akshare/get_ak_a_code_prompt.yaml +21 -0
- flowllm/op/akshare/get_ak_a_info_op.py +143 -0
- flowllm/op/base_op.py +169 -0
- flowllm/op/llm_base_op.py +63 -0
- flowllm/op/mock_op.py +42 -0
- flowllm/op/parallel_op.py +30 -0
- flowllm/op/sequential_op.py +29 -0
- flowllm/schema/flow_response.py +12 -0
- flowllm/schema/message.py +35 -0
- flowllm/schema/service_config.py +76 -0
- flowllm/schema/tool_call.py +110 -0
- flowllm/service/__init__.py +2 -0
- flowllm/service/base_service.py +59 -0
- flowllm/service/http_service.py +87 -0
- flowllm/service/mcp_service.py +45 -0
- flowllm/storage/__init__.py +1 -0
- flowllm/storage/vector_store/__init__.py +3 -0
- flowllm/storage/vector_store/base_vector_store.py +44 -0
- {llmflow → flowllm/storage}/vector_store/chroma_vector_store.py +11 -10
- {llmflow → flowllm/storage}/vector_store/es_vector_store.py +10 -9
- llmflow/vector_store/file_vector_store.py → flowllm/storage/vector_store/local_vector_store.py +110 -10
- flowllm/utils/common_utils.py +64 -0
- flowllm/utils/dataframe_cache.py +331 -0
- flowllm/utils/fetch_url.py +113 -0
- {llmflow → flowllm}/utils/timer.py +5 -4
- {flowllm-0.1.0.dist-info → flowllm-0.1.1.dist-info}/METADATA +31 -27
- flowllm-0.1.1.dist-info/RECORD +62 -0
- flowllm-0.1.1.dist-info/entry_points.txt +4 -0
- {flowllm-0.1.0.dist-info → flowllm-0.1.1.dist-info}/licenses/LICENSE +1 -1
- flowllm-0.1.1.dist-info/top_level.txt +1 -0
- flowllm-0.1.0.dist-info/RECORD +0 -66
- flowllm-0.1.0.dist-info/entry_points.txt +0 -3
- flowllm-0.1.0.dist-info/top_level.txt +0 -1
- llmflow/app.py +0 -53
- llmflow/config/config_parser.py +0 -80
- llmflow/config/mock_config.yaml +0 -58
- llmflow/embedding_model/__init__.py +0 -5
- llmflow/enumeration/agent_state.py +0 -8
- llmflow/llm/__init__.py +0 -5
- llmflow/mcp_server.py +0 -110
- llmflow/op/__init__.py +0 -10
- llmflow/op/base_op.py +0 -125
- llmflow/op/mock_op.py +0 -40
- llmflow/op/react/react_v1_op.py +0 -88
- llmflow/op/react/react_v1_prompt.yaml +0 -28
- llmflow/op/vector_store/__init__.py +0 -13
- llmflow/op/vector_store/recall_vector_store_op.py +0 -48
- llmflow/op/vector_store/update_vector_store_op.py +0 -28
- llmflow/op/vector_store/vector_store_action_op.py +0 -46
- llmflow/pipeline/pipeline.py +0 -94
- llmflow/pipeline/pipeline_context.py +0 -37
- llmflow/schema/app_config.py +0 -69
- llmflow/schema/experience.py +0 -144
- llmflow/schema/message.py +0 -68
- llmflow/schema/request.py +0 -32
- llmflow/schema/response.py +0 -29
- llmflow/service/__init__.py +0 -0
- llmflow/service/llmflow_service.py +0 -96
- llmflow/tool/__init__.py +0 -9
- llmflow/tool/base_tool.py +0 -80
- llmflow/tool/code_tool.py +0 -43
- llmflow/tool/dashscope_search_tool.py +0 -162
- llmflow/tool/mcp_tool.py +0 -77
- llmflow/tool/tavily_search_tool.py +0 -109
- llmflow/tool/terminate_tool.py +0 -23
- llmflow/utils/__init__.py +0 -0
- llmflow/utils/common_utils.py +0 -17
- llmflow/utils/file_handler.py +0 -25
- llmflow/utils/http_client.py +0 -156
- llmflow/utils/op_utils.py +0 -102
- llmflow/utils/registry.py +0 -33
- llmflow/vector_store/__init__.py +0 -7
- llmflow/vector_store/base_vector_store.py +0 -136
- {llmflow → flowllm/config}/__init__.py +0 -0
- {llmflow/config → flowllm/context}/__init__.py +0 -0
- {llmflow → flowllm}/enumeration/__init__.py +0 -0
- {llmflow → flowllm}/enumeration/chunk_enum.py +0 -0
- {llmflow → flowllm}/enumeration/http_enum.py +0 -0
- {llmflow → flowllm}/enumeration/role.py +0 -0
- {llmflow/op/react → flowllm/op/akshare}/__init__.py +0 -0
- {llmflow/pipeline → flowllm/schema}/__init__.py +0 -0
- {llmflow → flowllm}/schema/vector_node.py +0 -0
- {llmflow/schema → flowllm/utils}/__init__.py +0 -0
- {llmflow → flowllm}/utils/singleton.py +0 -0
- {flowllm-0.1.0.dist-info → flowllm-0.1.1.dist-info}/WHEEL +0 -0
flowllm/__init__.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
from flowllm import embedding_model
|
2
|
+
from flowllm import flow_engine
|
3
|
+
from flowllm import llm
|
4
|
+
from flowllm import op
|
5
|
+
from flowllm import service
|
6
|
+
from flowllm import storage
|
7
|
+
|
8
|
+
from .app import main
|
9
|
+
|
10
|
+
__version__ = "0.1.1"
|
11
|
+
|
12
|
+
__all__ = ["main"]
|
flowllm/app.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
from flowllm.service.base_service import BaseService
|
4
|
+
from flowllm.utils.common_utils import load_env
|
5
|
+
|
6
|
+
load_env()
|
7
|
+
|
8
|
+
from flowllm.config.pydantic_config_parser import PydanticConfigParser
|
9
|
+
from flowllm.schema.service_config import ServiceConfig
|
10
|
+
from flowllm.context.service_context import C
|
11
|
+
|
12
|
+
|
13
|
+
def main():
|
14
|
+
config_parser = PydanticConfigParser(ServiceConfig)
|
15
|
+
service_config: ServiceConfig = config_parser.parse_args(*sys.argv[1:])
|
16
|
+
service_cls = C.resolve_service(service_config.backend)
|
17
|
+
service: BaseService = service_cls(service_config)
|
18
|
+
service()
|
19
|
+
|
20
|
+
if __name__ == "__main__":
|
21
|
+
main()
|
22
|
+
|
23
|
+
|
24
|
+
# python -m build
|
25
|
+
# twine upload dist/*
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# default config.yaml
|
2
|
+
backend: mcp
|
3
|
+
language: ""
|
4
|
+
thread_pool_max_workers: 32
|
5
|
+
ray_max_workers: 8
|
6
|
+
|
7
|
+
mcp:
|
8
|
+
transport: sse
|
9
|
+
host: "0.0.0.0"
|
10
|
+
port: 8001
|
11
|
+
|
12
|
+
http:
|
13
|
+
host: "0.0.0.0"
|
14
|
+
port: 8001
|
15
|
+
timeout_keep_alive: 600
|
16
|
+
limit_concurrency: 64
|
17
|
+
|
18
|
+
|
19
|
+
flow_engine:
|
20
|
+
backend: simple
|
21
|
+
|
22
|
+
|
23
|
+
flow:
|
24
|
+
get_a_stock_infos:
|
25
|
+
flow_content: get_ak_a_code_op >> get_ak_a_info_op >> get_ak_a_spot_op >> get_ak_a_money_flow_op >> get_ak_a_financial_info_op >> merge_ak_a_info_op
|
26
|
+
description: "Retrieve the A-share stock codes from the query, and fetch information about these stocks, including company basic information, current stock price and its change percentage, capital inflow and outflow data for the most recent day, and financial information from the latest quarter."
|
27
|
+
input_schema:
|
28
|
+
query:
|
29
|
+
type: "str"
|
30
|
+
description: "user question"
|
31
|
+
|
32
|
+
get_a_stock_news:
|
33
|
+
flow_content: get_ak_a_code_op >> get_ak_a_news_op >> merge_ak_a_info_op
|
34
|
+
description: "Retrieve the A-share stock codes from the query, and obtain the latest news information about these stocks."
|
35
|
+
input_schema:
|
36
|
+
query:
|
37
|
+
type: "str"
|
38
|
+
description: "user question"
|
39
|
+
|
40
|
+
mock_flow:
|
41
|
+
flow_content: mock1_op>>((mock4_op>>mock2_op)|mock5_op)>>(mock3_op|mock6_op)
|
42
|
+
description: "mock flow"
|
43
|
+
input_schema:
|
44
|
+
a:
|
45
|
+
type: "str"
|
46
|
+
description: "mock attr a"
|
47
|
+
required: true
|
48
|
+
b:
|
49
|
+
type: "str"
|
50
|
+
description: "mock attr b"
|
51
|
+
required: true
|
52
|
+
|
53
|
+
op:
|
54
|
+
mock1_op:
|
55
|
+
backend: mock1_op
|
56
|
+
llm: default
|
57
|
+
vector_store: default
|
58
|
+
|
59
|
+
llm:
|
60
|
+
default:
|
61
|
+
backend: openai_compatible
|
62
|
+
model_name: qwen3-30b-a3b-thinking-2507
|
63
|
+
params:
|
64
|
+
temperature: 0.6
|
65
|
+
qwen3_30b_instruct:
|
66
|
+
backend: openai_compatible
|
67
|
+
model_name: qwen3-30b-a3b-instruct-2507
|
68
|
+
|
69
|
+
embedding_model:
|
70
|
+
default:
|
71
|
+
backend: openai_compatible
|
72
|
+
model_name: text-embedding-v4
|
73
|
+
params:
|
74
|
+
dimensions: 1024
|
75
|
+
|
76
|
+
vector_store:
|
77
|
+
default:
|
78
|
+
backend: elasticsearch
|
79
|
+
embedding_model: default
|
80
|
+
params:
|
81
|
+
hosts: "http://localhost:9200"
|
82
|
+
|
@@ -0,0 +1,242 @@
|
|
1
|
+
import copy
|
2
|
+
import json
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Generic, List, Type, TypeVar
|
5
|
+
|
6
|
+
import yaml
|
7
|
+
from loguru import logger
|
8
|
+
from pydantic import BaseModel
|
9
|
+
|
10
|
+
T = TypeVar('T', bound=BaseModel)
|
11
|
+
|
12
|
+
|
13
|
+
class PydanticConfigParser(Generic[T]):
|
14
|
+
"""
|
15
|
+
Pydantic Configuration Parser
|
16
|
+
|
17
|
+
Supported configuration sources (priority from low to high):
|
18
|
+
1. Default configuration (Pydantic model default values)
|
19
|
+
2. YAML configuration file
|
20
|
+
3. Command line arguments (dot notation format)
|
21
|
+
4. Runtime parameters
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, config_class: Type[T]):
|
25
|
+
"""
|
26
|
+
Initialize configuration parser
|
27
|
+
|
28
|
+
Args:
|
29
|
+
config_class: Pydantic configuration model class
|
30
|
+
"""
|
31
|
+
self.config_class = config_class
|
32
|
+
self.config_dict: dict = {}
|
33
|
+
|
34
|
+
def parse_dot_notation(self, dot_list: List[str]) -> dict:
|
35
|
+
"""
|
36
|
+
Parse dot notation format configuration list
|
37
|
+
|
38
|
+
Args:
|
39
|
+
dot_list: Configuration list in format ['a.b.c=value', 'x.y=123']
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
Parsed nested dictionary
|
43
|
+
"""
|
44
|
+
config_dict = {}
|
45
|
+
|
46
|
+
for item in dot_list:
|
47
|
+
if '=' not in item:
|
48
|
+
continue
|
49
|
+
|
50
|
+
key_path, value_str = item.split('=', 1)
|
51
|
+
keys = key_path.split('.')
|
52
|
+
|
53
|
+
# Automatic type conversion
|
54
|
+
value = self._convert_value(value_str)
|
55
|
+
|
56
|
+
# Build nested dictionary
|
57
|
+
current_dict = config_dict
|
58
|
+
for key in keys[:-1]:
|
59
|
+
if key not in current_dict:
|
60
|
+
current_dict[key] = {}
|
61
|
+
current_dict = current_dict[key]
|
62
|
+
|
63
|
+
current_dict[keys[-1]] = value
|
64
|
+
|
65
|
+
return config_dict
|
66
|
+
|
67
|
+
@staticmethod
|
68
|
+
def _convert_value(value_str: str) -> Any:
|
69
|
+
"""
|
70
|
+
Automatically convert string values to appropriate Python types
|
71
|
+
|
72
|
+
Args:
|
73
|
+
value_str: String value
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
Converted value
|
77
|
+
"""
|
78
|
+
value_str = value_str.strip()
|
79
|
+
|
80
|
+
if value_str.lower() in ("true", "false"):
|
81
|
+
return value_str.lower() == "true"
|
82
|
+
|
83
|
+
if value_str.lower() in ("none", "null"):
|
84
|
+
return None
|
85
|
+
|
86
|
+
try:
|
87
|
+
if "." not in value_str and "e" not in value_str.lower():
|
88
|
+
return int(value_str)
|
89
|
+
|
90
|
+
return float(value_str)
|
91
|
+
|
92
|
+
except ValueError:
|
93
|
+
pass
|
94
|
+
|
95
|
+
try:
|
96
|
+
return json.loads(value_str)
|
97
|
+
except (json.JSONDecodeError, ValueError):
|
98
|
+
pass
|
99
|
+
|
100
|
+
return value_str
|
101
|
+
|
102
|
+
@staticmethod
|
103
|
+
def load_from_yaml(yaml_path: str | Path) -> dict:
|
104
|
+
"""
|
105
|
+
Load configuration from YAML file
|
106
|
+
|
107
|
+
Args:
|
108
|
+
yaml_path: YAML file path
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Configuration dictionary
|
112
|
+
"""
|
113
|
+
if isinstance(yaml_path, str):
|
114
|
+
yaml_path = Path(yaml_path)
|
115
|
+
|
116
|
+
if not yaml_path.exists():
|
117
|
+
raise FileNotFoundError(f"Configuration file does not exist: {yaml_path}")
|
118
|
+
|
119
|
+
with yaml_path.open() as f:
|
120
|
+
return yaml.safe_load(f)
|
121
|
+
|
122
|
+
def merge_configs(self, *config_dicts: dict) -> dict:
|
123
|
+
"""
|
124
|
+
Deep merge multiple configuration dictionaries
|
125
|
+
|
126
|
+
Args:
|
127
|
+
*config_dicts: Multiple configuration dictionaries
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Merged configuration dictionary
|
131
|
+
"""
|
132
|
+
result = {}
|
133
|
+
|
134
|
+
for config_dict in config_dicts:
|
135
|
+
result = self._deep_merge(result, config_dict)
|
136
|
+
|
137
|
+
return result
|
138
|
+
|
139
|
+
def _deep_merge(self, base_dict: dict, update_dict: dict) -> dict:
|
140
|
+
"""
|
141
|
+
Deep merge two dictionaries
|
142
|
+
|
143
|
+
Args:
|
144
|
+
base_dict: Base dictionary
|
145
|
+
update_dict: Update dictionary
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
Merged dictionary
|
149
|
+
"""
|
150
|
+
result = base_dict.copy()
|
151
|
+
|
152
|
+
for key, value in update_dict.items():
|
153
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
154
|
+
result[key] = self._deep_merge(result[key], value)
|
155
|
+
else:
|
156
|
+
result[key] = value
|
157
|
+
|
158
|
+
return result
|
159
|
+
|
160
|
+
def parse_args(self, *args) -> T:
|
161
|
+
"""
|
162
|
+
Parse command line arguments and return configuration object
|
163
|
+
|
164
|
+
Args:
|
165
|
+
args: Command line arguments.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Parsed configuration object
|
169
|
+
"""
|
170
|
+
configs_to_merge = []
|
171
|
+
|
172
|
+
# 1. Default configuration (from Pydantic model)
|
173
|
+
default_config = self.config_class().model_dump()
|
174
|
+
configs_to_merge.append(default_config)
|
175
|
+
|
176
|
+
# 2. YAML configuration file
|
177
|
+
config = ""
|
178
|
+
filter_args = []
|
179
|
+
for arg in args:
|
180
|
+
if "=" not in arg:
|
181
|
+
continue
|
182
|
+
|
183
|
+
arg = arg.lstrip("--").lstrip("-")
|
184
|
+
|
185
|
+
if "c=" in arg or "config=" in arg:
|
186
|
+
config = arg.split("=")[-1]
|
187
|
+
else:
|
188
|
+
filter_args.append(arg)
|
189
|
+
|
190
|
+
if config:
|
191
|
+
if not config.endswith(".yaml"):
|
192
|
+
config += ".yaml"
|
193
|
+
|
194
|
+
# load pre-built configs
|
195
|
+
config_path = Path(__file__).parent / config
|
196
|
+
if not config_path.exists():
|
197
|
+
config_path = Path(config)
|
198
|
+
|
199
|
+
yaml_config = self.load_from_yaml(config_path)
|
200
|
+
logger.info(f"flowllm using config={config_path}")
|
201
|
+
configs_to_merge.append(yaml_config)
|
202
|
+
|
203
|
+
# 3. Command line override configuration
|
204
|
+
if args:
|
205
|
+
cli_config = self.parse_dot_notation(filter_args)
|
206
|
+
configs_to_merge.append(cli_config)
|
207
|
+
|
208
|
+
# Merge all configurations
|
209
|
+
self.config_dict = self.merge_configs(*configs_to_merge)
|
210
|
+
|
211
|
+
# Create and validate final configuration object
|
212
|
+
return self.config_class.model_validate(self.config_dict)
|
213
|
+
|
214
|
+
def update_config(self, **kwargs) -> T:
|
215
|
+
"""
|
216
|
+
Update configuration object using keyword arguments
|
217
|
+
|
218
|
+
Args:
|
219
|
+
**kwargs: Configuration items to update, supports dot notation, e.g. server__host='localhost'
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
Updated configuration object
|
223
|
+
"""
|
224
|
+
# Convert kwargs to dot notation format
|
225
|
+
dot_list = []
|
226
|
+
for key, value in kwargs.items():
|
227
|
+
# support double underscore as dot replacement (server__host -> server.host)
|
228
|
+
dot_key = key.replace("__", ".")
|
229
|
+
dot_list.append(f"{dot_key}={value}")
|
230
|
+
|
231
|
+
# Parse and merge configuration
|
232
|
+
override_config = self.parse_dot_notation(dot_list)
|
233
|
+
final_config = self.merge_configs(copy.deepcopy(self.config_dict), override_config)
|
234
|
+
|
235
|
+
return self.config_class.model_validate(final_config)
|
236
|
+
|
237
|
+
|
238
|
+
def get_default_config():
|
239
|
+
from flowllm.schema.service_config import ServiceConfig
|
240
|
+
|
241
|
+
config_parser = PydanticConfigParser(ServiceConfig)
|
242
|
+
return config_parser.parse_args("config=default_config")
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
|
4
|
+
class BaseContext:
|
5
|
+
def __init__(self, **kwargs):
|
6
|
+
self._data = {**kwargs}
|
7
|
+
|
8
|
+
def __getattr__(self, name: str):
|
9
|
+
if name in self._data:
|
10
|
+
return self._data[name]
|
11
|
+
|
12
|
+
raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
|
13
|
+
|
14
|
+
def __setattr__(self, name: str, value):
|
15
|
+
if name == "_data":
|
16
|
+
super().__setattr__(name, value)
|
17
|
+
else:
|
18
|
+
self._data[name] = value
|
19
|
+
|
20
|
+
def __getitem__(self, name: str):
|
21
|
+
if name not in self._data:
|
22
|
+
raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
|
23
|
+
return self._data[name]
|
24
|
+
|
25
|
+
def __setitem__(self, name: str, value):
|
26
|
+
self._data[name] = value
|
27
|
+
|
28
|
+
def __contains__(self, name: str):
|
29
|
+
return name in self._data
|
30
|
+
|
31
|
+
def __repr__(self):
|
32
|
+
return f"{self.__class__.__name__}({self._data!r})"
|
33
|
+
|
34
|
+
def dump(self) -> dict:
|
35
|
+
return self._data
|
36
|
+
|
37
|
+
@property
|
38
|
+
def keys(self) -> List[str]:
|
39
|
+
return sorted(self._data.keys())
|
40
|
+
|
41
|
+
def update(self, **kwargs):
|
42
|
+
self._data.update(kwargs)
|
43
|
+
|
44
|
+
|
45
|
+
if __name__ == "__main__":
|
46
|
+
ctx = BaseContext(**{"name": "Alice", "age": 30, "city": "New York"})
|
47
|
+
|
48
|
+
print(ctx.name)
|
49
|
+
print(ctx.age)
|
50
|
+
print(ctx.city)
|
51
|
+
|
52
|
+
ctx.email = "alice@example.com"
|
53
|
+
ctx["email"] = "alice@example.com"
|
54
|
+
print(ctx.email)
|
55
|
+
|
56
|
+
print(ctx.keys)
|
57
|
+
print(ctx)
|
58
|
+
|
59
|
+
# print(ctx.city2)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
from flowllm.context.base_context import BaseContext
|
4
|
+
from flowllm.schema.flow_response import FlowResponse
|
5
|
+
from flowllm.schema.service_config import ServiceConfig
|
6
|
+
|
7
|
+
|
8
|
+
class FlowContext(BaseContext):
|
9
|
+
|
10
|
+
def __init__(self, flow_id: str = uuid.uuid4().hex, **kwargs):
|
11
|
+
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
|
+
|
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
|
@@ -3,42 +3,56 @@ from pathlib import Path
|
|
3
3
|
import yaml
|
4
4
|
from loguru import logger
|
5
5
|
|
6
|
+
from flowllm.context.base_context import BaseContext
|
7
|
+
from flowllm.context.service_context import C
|
6
8
|
|
7
|
-
class PromptMixin:
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
class PromptHandler(BaseContext):
|
11
|
+
|
12
|
+
def __init__(self, language: str = "", **kwargs):
|
13
|
+
super().__init__(**kwargs)
|
14
|
+
self.language: str = language or C.language
|
11
15
|
|
12
16
|
def load_prompt_by_file(self, prompt_file_path: Path | str = None):
|
13
17
|
if prompt_file_path is None:
|
14
|
-
return
|
18
|
+
return self
|
15
19
|
|
16
20
|
if isinstance(prompt_file_path, str):
|
17
21
|
prompt_file_path = Path(prompt_file_path)
|
18
22
|
|
19
23
|
if not prompt_file_path.exists():
|
20
|
-
return
|
24
|
+
return self
|
21
25
|
|
22
26
|
with prompt_file_path.open() as f:
|
23
27
|
prompt_dict = yaml.load(f, yaml.FullLoader)
|
24
28
|
self.load_prompt_dict(prompt_dict)
|
29
|
+
return self
|
25
30
|
|
26
31
|
def load_prompt_dict(self, prompt_dict: dict = None):
|
27
32
|
if not prompt_dict:
|
28
|
-
return
|
33
|
+
return self
|
29
34
|
|
30
35
|
for key, value in prompt_dict.items():
|
31
36
|
if isinstance(value, str):
|
32
|
-
if key in self.
|
33
|
-
self.
|
37
|
+
if key in self._data:
|
38
|
+
self._data[key] = value
|
34
39
|
logger.warning(f"prompt_dict key={key} overwrite!")
|
35
40
|
|
36
41
|
else:
|
37
|
-
self.
|
42
|
+
self._data[key] = value
|
38
43
|
logger.info(f"add prompt_dict key={key}")
|
44
|
+
return self
|
45
|
+
|
46
|
+
def get_prompt(self, prompt_name: str):
|
47
|
+
key: str = prompt_name
|
48
|
+
if self.language and not key.endswith(self.language.strip()):
|
49
|
+
key += "_" + self.language.strip()
|
39
50
|
|
40
|
-
|
41
|
-
|
51
|
+
assert key in self._data, f"prompt_name={key} not found."
|
52
|
+
return self._data[key]
|
53
|
+
|
54
|
+
def prompt_format(self, prompt_name: str, **kwargs) -> str:
|
55
|
+
prompt = self.get_prompt(prompt_name)
|
42
56
|
|
43
57
|
flag_kwargs = {k: v for k, v in kwargs.items() if isinstance(v, bool)}
|
44
58
|
other_kwargs = {k: v for k, v in kwargs.items() if not isinstance(v, bool)}
|
@@ -69,6 +83,3 @@ class PromptMixin:
|
|
69
83
|
prompt = prompt.format(**other_kwargs)
|
70
84
|
|
71
85
|
return prompt
|
72
|
-
|
73
|
-
def get_prompt(self, key: str):
|
74
|
-
return self._prompt_dict[key]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from loguru import logger
|
2
|
+
|
3
|
+
from flowllm.context.base_context import BaseContext
|
4
|
+
from flowllm.utils.common_utils import camel_to_snake
|
5
|
+
|
6
|
+
|
7
|
+
class Registry(BaseContext):
|
8
|
+
|
9
|
+
def __init__(self, registry_name: str, enable_log: bool = True, **kwargs):
|
10
|
+
super().__init__(**kwargs)
|
11
|
+
self.registry_name: str = registry_name
|
12
|
+
self.enable_log: bool = enable_log
|
13
|
+
|
14
|
+
def register(self, name: str = ""):
|
15
|
+
def decorator(cls):
|
16
|
+
class_name = name if name else camel_to_snake(cls.__name__)
|
17
|
+
if self.enable_log:
|
18
|
+
if class_name in self._data:
|
19
|
+
logger.warning(f"{self.registry_name}.class({class_name}) is already registered!")
|
20
|
+
else:
|
21
|
+
logger.info(f"{self.registry_name}.class({class_name}) is registered.")
|
22
|
+
|
23
|
+
self._data[class_name] = cls
|
24
|
+
return cls
|
25
|
+
|
26
|
+
return decorator
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import uuid
|
2
|
+
from concurrent.futures import ThreadPoolExecutor
|
3
|
+
from typing import Dict
|
4
|
+
|
5
|
+
from flowllm.context.base_context import BaseContext
|
6
|
+
from flowllm.context.registry import Registry
|
7
|
+
from flowllm.utils.singleton import singleton
|
8
|
+
|
9
|
+
|
10
|
+
@singleton
|
11
|
+
class ServiceContext(BaseContext):
|
12
|
+
|
13
|
+
def __init__(self, service_id: str = uuid.uuid4().hex, **kwargs):
|
14
|
+
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
|
+
|
31
|
+
@thread_pool.setter
|
32
|
+
def thread_pool(self, thread_pool: ThreadPoolExecutor):
|
33
|
+
self._data["thread_pool"] = thread_pool
|
34
|
+
|
35
|
+
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]
|
41
|
+
|
42
|
+
def set_vector_store(self, name: str, vector_store):
|
43
|
+
if "vector_store_dict" not in self._data:
|
44
|
+
self.set_vector_stores({})
|
45
|
+
|
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
|
50
|
+
|
51
|
+
"""
|
52
|
+
register models
|
53
|
+
"""
|
54
|
+
|
55
|
+
def register_embedding_model(self, name: str = ""):
|
56
|
+
return self.registry_dict["embedding_model"].register(name=name)
|
57
|
+
|
58
|
+
def register_llm(self, name: str = ""):
|
59
|
+
return self.registry_dict["llm"].register(name=name)
|
60
|
+
|
61
|
+
def register_vector_store(self, name: str = ""):
|
62
|
+
return self.registry_dict["vector_store"].register(name=name)
|
63
|
+
|
64
|
+
def register_op(self, name: str = ""):
|
65
|
+
return self.registry_dict["op"].register(name=name)
|
66
|
+
|
67
|
+
def register_flow_engine(self, name: str = ""):
|
68
|
+
return self.registry_dict["flow_engine"].register(name=name)
|
69
|
+
|
70
|
+
def register_service(self, name: str = ""):
|
71
|
+
return self.registry_dict["service"].register(name=name)
|
72
|
+
|
73
|
+
"""
|
74
|
+
resolve models
|
75
|
+
"""
|
76
|
+
|
77
|
+
def resolve_embedding_model(self, name: str):
|
78
|
+
assert name in self.registry_dict["embedding_model"], f"embedding_model={name} not found!"
|
79
|
+
return self.registry_dict["embedding_model"][name]
|
80
|
+
|
81
|
+
def resolve_llm(self, name: str):
|
82
|
+
assert name in self.registry_dict["llm"], f"llm={name} not found!"
|
83
|
+
return self.registry_dict["llm"][name]
|
84
|
+
|
85
|
+
def resolve_vector_store(self, name: str):
|
86
|
+
assert name in self.registry_dict["vector_store"], f"vector_store={name} not found!"
|
87
|
+
return self.registry_dict["vector_store"][name]
|
88
|
+
|
89
|
+
def resolve_op(self, name: str):
|
90
|
+
assert name in self.registry_dict["op"], f"op={name} not found!"
|
91
|
+
return self.registry_dict["op"][name]
|
92
|
+
|
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]
|
96
|
+
|
97
|
+
def resolve_service(self, name: str):
|
98
|
+
assert name in self.registry_dict["service"], f"service={name} not found!"
|
99
|
+
return self.registry_dict["service"][name]
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
C = ServiceContext()
|
@@ -0,0 +1 @@
|
|
1
|
+
from flowllm.embedding_model.openai_compatible_embedding_model import OpenAICompatibleEmbeddingModel
|
@@ -4,7 +4,7 @@ from typing import List
|
|
4
4
|
from loguru import logger
|
5
5
|
from pydantic import BaseModel, Field
|
6
6
|
|
7
|
-
from
|
7
|
+
from flowllm.schema.vector_node import VectorNode
|
8
8
|
|
9
9
|
|
10
10
|
class BaseEmbeddingModel(BaseModel, ABC):
|
@@ -101,4 +101,4 @@ class BaseEmbeddingModel(BaseModel, ABC):
|
|
101
101
|
return nodes
|
102
102
|
|
103
103
|
else:
|
104
|
-
raise
|
104
|
+
raise TypeError(f"unsupported type={type(nodes)}")
|