hiagent-components 0.1.0__tar.gz
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.
- hiagent_components-0.1.0/.gitignore +169 -0
- hiagent_components-0.1.0/PKG-INFO +14 -0
- hiagent_components-0.1.0/README.md +1 -0
- hiagent_components-0.1.0/hiagent_components/__init__.py +13 -0
- hiagent_components-0.1.0/hiagent_components/agent/__init__.py +16 -0
- hiagent_components-0.1.0/hiagent_components/agent/base.py +260 -0
- hiagent_components-0.1.0/hiagent_components/base/__init__.py +18 -0
- hiagent_components-0.1.0/hiagent_components/base/base.py +218 -0
- hiagent_components-0.1.0/hiagent_components/base/utils.py +61 -0
- hiagent_components-0.1.0/hiagent_components/integrations/__init__.py +13 -0
- hiagent_components-0.1.0/hiagent_components/integrations/langchain/__init__.py +20 -0
- hiagent_components-0.1.0/hiagent_components/integrations/langchain/retriever.py +71 -0
- hiagent_components-0.1.0/hiagent_components/integrations/langchain/tool.py +34 -0
- hiagent_components-0.1.0/hiagent_components/integrations/mcp/tool.py +43 -0
- hiagent_components-0.1.0/hiagent_components/retriever/__init__.py +16 -0
- hiagent_components-0.1.0/hiagent_components/retriever/base.py +218 -0
- hiagent_components-0.1.0/hiagent_components/tool/__init__.py +19 -0
- hiagent_components-0.1.0/hiagent_components/tool/base.py +68 -0
- hiagent_components-0.1.0/hiagent_components/tool/tool.py +257 -0
- hiagent_components-0.1.0/hiagent_components/utils/__init__.py +13 -0
- hiagent_components-0.1.0/hiagent_components/utils/schema.py +177 -0
- hiagent_components-0.1.0/hiagent_components/workflow/__init__.py +16 -0
- hiagent_components-0.1.0/hiagent_components/workflow/base.py +253 -0
- hiagent_components-0.1.0/hiagent_components/workflow/utils.py +24 -0
- hiagent_components-0.1.0/pyproject.toml +33 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
### Python template
|
|
2
|
+
# Byte-compiled / optimized / DLL files
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
|
|
7
|
+
# C extensions
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# Distribution / packaging
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
111
|
+
.pdm.toml
|
|
112
|
+
.pdm-python
|
|
113
|
+
.pdm-build/
|
|
114
|
+
|
|
115
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
116
|
+
__pypackages__/
|
|
117
|
+
|
|
118
|
+
# Celery stuff
|
|
119
|
+
celerybeat-schedule
|
|
120
|
+
celerybeat.pid
|
|
121
|
+
|
|
122
|
+
# SageMath parsed files
|
|
123
|
+
*.sage.py
|
|
124
|
+
|
|
125
|
+
# Environments
|
|
126
|
+
.env
|
|
127
|
+
.venv
|
|
128
|
+
env/
|
|
129
|
+
venv/
|
|
130
|
+
ENV/
|
|
131
|
+
env.bak/
|
|
132
|
+
venv.bak/
|
|
133
|
+
|
|
134
|
+
# Spyder project settings
|
|
135
|
+
.spyderproject
|
|
136
|
+
.spyproject
|
|
137
|
+
|
|
138
|
+
# Rope project settings
|
|
139
|
+
.ropeproject
|
|
140
|
+
|
|
141
|
+
# mkdocs documentation
|
|
142
|
+
/site
|
|
143
|
+
|
|
144
|
+
# mypy
|
|
145
|
+
.mypy_cache/
|
|
146
|
+
.dmypy.json
|
|
147
|
+
dmypy.json
|
|
148
|
+
|
|
149
|
+
# Pyre type checker
|
|
150
|
+
.pyre/
|
|
151
|
+
|
|
152
|
+
# pytype static type analyzer
|
|
153
|
+
.pytype/
|
|
154
|
+
|
|
155
|
+
# Cython debug symbols
|
|
156
|
+
cython_debug/
|
|
157
|
+
|
|
158
|
+
# PyCharm
|
|
159
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
160
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
161
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
162
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
163
|
+
#.idea/
|
|
164
|
+
|
|
165
|
+
chainlit.md
|
|
166
|
+
.chainlit/
|
|
167
|
+
.vscode
|
|
168
|
+
.idea
|
|
169
|
+
license.py
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hiagent-components
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: hiagent-api<0.2.0,>=0.1.0
|
|
7
|
+
Requires-Dist: json-schema-to-pydantic>=0.2.6
|
|
8
|
+
Requires-Dist: pydantic>=2.11.5
|
|
9
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
10
|
+
Requires-Dist: strenum>=0.4.15
|
|
11
|
+
Requires-Dist: tenacity>=9.1.2
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
HiAgent-Components SDK
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
HiAgent-Components SDK
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from .base import Agent
|
|
15
|
+
|
|
16
|
+
__all__ = ["Agent"]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from concurrent.futures import Executor
|
|
15
|
+
from io import StringIO
|
|
16
|
+
from typing import Any, AsyncIterator, Iterator, Optional
|
|
17
|
+
|
|
18
|
+
from hiagent_api.chat import ChatService
|
|
19
|
+
from hiagent_api.chat_types import (
|
|
20
|
+
ChatEvent,
|
|
21
|
+
ChatRequest,
|
|
22
|
+
CreateConversationRequest,
|
|
23
|
+
GetAppConfigPreviewRequest,
|
|
24
|
+
MessageChatEvent,
|
|
25
|
+
StreamingChatEventType,
|
|
26
|
+
ToolMessageChatEvent,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from hiagent_components.base.base import Executable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Agent(Executable):
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
svc: ChatService,
|
|
36
|
+
app_key: str,
|
|
37
|
+
user_id: str,
|
|
38
|
+
conversation_id: str,
|
|
39
|
+
name: str,
|
|
40
|
+
description: str,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.svc = svc
|
|
43
|
+
self.app_key = app_key
|
|
44
|
+
self.user_id = user_id
|
|
45
|
+
self.conversation_id = conversation_id
|
|
46
|
+
self.name = name or ""
|
|
47
|
+
self.description = description or ""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def input_schema(self) -> dict:
|
|
51
|
+
return {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"query": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "需要智能体解决的原始问题",
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": ["query"],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def init(
|
|
64
|
+
cls,
|
|
65
|
+
svc: ChatService,
|
|
66
|
+
app_key: str,
|
|
67
|
+
user_id: str,
|
|
68
|
+
variables: dict,
|
|
69
|
+
conversation_id: Optional[str] = None,
|
|
70
|
+
name: Optional[str] = None,
|
|
71
|
+
description: Optional[str] = None,
|
|
72
|
+
) -> "Agent":
|
|
73
|
+
resp = svc.get_app(
|
|
74
|
+
app_key=app_key,
|
|
75
|
+
params=GetAppConfigPreviewRequest(
|
|
76
|
+
app_key=app_key,
|
|
77
|
+
user_id=user_id,
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
if not name:
|
|
81
|
+
name = resp.name
|
|
82
|
+
if not description:
|
|
83
|
+
description = ""
|
|
84
|
+
|
|
85
|
+
if not conversation_id:
|
|
86
|
+
resp = svc.create_conversation(
|
|
87
|
+
app_key=app_key,
|
|
88
|
+
conversation=CreateConversationRequest(
|
|
89
|
+
app_key=app_key,
|
|
90
|
+
inputs=variables,
|
|
91
|
+
user_id=user_id,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
conversation_id = resp.conversation.app_conversation_id
|
|
95
|
+
|
|
96
|
+
agent = cls(
|
|
97
|
+
svc=svc,
|
|
98
|
+
app_key=app_key,
|
|
99
|
+
user_id=user_id,
|
|
100
|
+
conversation_id=conversation_id,
|
|
101
|
+
name=name,
|
|
102
|
+
description=description,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return agent
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
async def ainit(
|
|
109
|
+
cls,
|
|
110
|
+
svc: ChatService,
|
|
111
|
+
app_key: str,
|
|
112
|
+
user_id: str,
|
|
113
|
+
variables: dict,
|
|
114
|
+
conversation_id: Optional[str] = None,
|
|
115
|
+
name: Optional[str] = None,
|
|
116
|
+
description: Optional[str] = None,
|
|
117
|
+
) -> "Agent":
|
|
118
|
+
resp = await svc.aget_app(
|
|
119
|
+
app_key=app_key,
|
|
120
|
+
params=GetAppConfigPreviewRequest(
|
|
121
|
+
app_key=app_key,
|
|
122
|
+
user_id=user_id,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
if not name:
|
|
126
|
+
name = resp.name
|
|
127
|
+
if not description:
|
|
128
|
+
description = ""
|
|
129
|
+
|
|
130
|
+
if not conversation_id:
|
|
131
|
+
resp = svc.create_conversation(
|
|
132
|
+
app_key=app_key,
|
|
133
|
+
conversation=CreateConversationRequest(
|
|
134
|
+
app_key=app_key,
|
|
135
|
+
inputs=variables,
|
|
136
|
+
user_id=user_id,
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
conversation_id = resp.conversation.app_conversation_id
|
|
140
|
+
|
|
141
|
+
agent = cls(
|
|
142
|
+
svc=svc,
|
|
143
|
+
app_key=app_key,
|
|
144
|
+
user_id=user_id,
|
|
145
|
+
conversation_id=conversation_id,
|
|
146
|
+
name=name,
|
|
147
|
+
description=description,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return agent
|
|
151
|
+
|
|
152
|
+
def invoke(
|
|
153
|
+
self,
|
|
154
|
+
input: dict,
|
|
155
|
+
**kwargs: Any,
|
|
156
|
+
) -> str:
|
|
157
|
+
query = input.get("query")
|
|
158
|
+
if not query:
|
|
159
|
+
raise ValueError("agent invoke input should contains 'query'")
|
|
160
|
+
|
|
161
|
+
resp_generator = self.svc.chat_streaming(
|
|
162
|
+
self.app_key,
|
|
163
|
+
ChatRequest(
|
|
164
|
+
app_key=self.app_key,
|
|
165
|
+
app_conversation_id=self.conversation_id,
|
|
166
|
+
query=query,
|
|
167
|
+
response_mode="streaming",
|
|
168
|
+
user_id=self.user_id,
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
output = StringIO()
|
|
173
|
+
for event in resp_generator:
|
|
174
|
+
if event.event == StreamingChatEventType.tool_message:
|
|
175
|
+
assert isinstance(event, ToolMessageChatEvent)
|
|
176
|
+
output.write(event.answer)
|
|
177
|
+
elif event.event == StreamingChatEventType.message:
|
|
178
|
+
assert isinstance(event, MessageChatEvent)
|
|
179
|
+
output.write(event.answer)
|
|
180
|
+
|
|
181
|
+
return output.getvalue()
|
|
182
|
+
|
|
183
|
+
async def ainvoke(
|
|
184
|
+
self,
|
|
185
|
+
input: dict,
|
|
186
|
+
executor: Optional[Executor] = None,
|
|
187
|
+
**kwargs: Any,
|
|
188
|
+
) -> str:
|
|
189
|
+
query = input.get("query")
|
|
190
|
+
if not query:
|
|
191
|
+
raise ValueError("agent invoke input should contains 'query'")
|
|
192
|
+
|
|
193
|
+
resp_generator = self.svc.achat_streaming(
|
|
194
|
+
self.app_key,
|
|
195
|
+
ChatRequest(
|
|
196
|
+
app_key=self.app_key,
|
|
197
|
+
app_conversation_id=self.conversation_id,
|
|
198
|
+
query=query,
|
|
199
|
+
response_mode="streaming",
|
|
200
|
+
user_id=self.user_id,
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
output = StringIO()
|
|
205
|
+
async for event in resp_generator:
|
|
206
|
+
if event.event == StreamingChatEventType.tool_message:
|
|
207
|
+
assert isinstance(event, ToolMessageChatEvent)
|
|
208
|
+
output.write(event.answer)
|
|
209
|
+
elif event.event == StreamingChatEventType.tool_message_output_end:
|
|
210
|
+
output.write("\n\n")
|
|
211
|
+
elif event.event == StreamingChatEventType.message:
|
|
212
|
+
assert isinstance(event, MessageChatEvent)
|
|
213
|
+
output.write(event.answer)
|
|
214
|
+
|
|
215
|
+
return output.getvalue()
|
|
216
|
+
|
|
217
|
+
def stream(
|
|
218
|
+
self,
|
|
219
|
+
input: dict,
|
|
220
|
+
**kwargs: Optional[Any],
|
|
221
|
+
) -> Iterator[ChatEvent]:
|
|
222
|
+
query = input.get("query")
|
|
223
|
+
if not query:
|
|
224
|
+
raise ValueError("agent invoke input should contains 'query'")
|
|
225
|
+
|
|
226
|
+
resp_generator = self.svc.chat_streaming(
|
|
227
|
+
self.app_key,
|
|
228
|
+
ChatRequest(
|
|
229
|
+
app_key=self.app_key,
|
|
230
|
+
app_conversation_id=self.conversation_id,
|
|
231
|
+
query=query,
|
|
232
|
+
response_mode="streaming",
|
|
233
|
+
user_id=self.user_id,
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return resp_generator
|
|
238
|
+
|
|
239
|
+
async def astream(
|
|
240
|
+
self,
|
|
241
|
+
input: dict,
|
|
242
|
+
**kwargs: Optional[Any],
|
|
243
|
+
) -> AsyncIterator[ChatEvent]:
|
|
244
|
+
query = input.get("query")
|
|
245
|
+
if not query:
|
|
246
|
+
raise ValueError("agent invoke input should contains 'query'")
|
|
247
|
+
|
|
248
|
+
resp_generator = self.svc.achat_streaming(
|
|
249
|
+
self.app_key,
|
|
250
|
+
ChatRequest(
|
|
251
|
+
app_key=self.app_key,
|
|
252
|
+
app_conversation_id=self.conversation_id,
|
|
253
|
+
query=query,
|
|
254
|
+
response_mode="streaming",
|
|
255
|
+
user_id=self.user_id,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
async for event in resp_generator:
|
|
260
|
+
yield event
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from .base import Executable
|
|
17
|
+
|
|
18
|
+
__all__ = ["Executable"]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from concurrent.futures import Executor
|
|
18
|
+
from typing import (
|
|
19
|
+
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
21
|
+
AsyncIterator,
|
|
22
|
+
Dict,
|
|
23
|
+
Generic,
|
|
24
|
+
Iterator,
|
|
25
|
+
Optional,
|
|
26
|
+
Tuple,
|
|
27
|
+
Type,
|
|
28
|
+
TypeVar,
|
|
29
|
+
Union,
|
|
30
|
+
cast,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from tenacity import (
|
|
34
|
+
AsyncRetrying,
|
|
35
|
+
Retrying,
|
|
36
|
+
retry_if_exception_type,
|
|
37
|
+
stop_after_attempt,
|
|
38
|
+
wait_exponential_jitter,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from hiagent_components.base.utils import (
|
|
42
|
+
gather_with_concurrency,
|
|
43
|
+
get_executor,
|
|
44
|
+
run_in_executor,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from hiagent_components.tool.base import BaseTool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Input = TypeVar("Input", contravariant=True)
|
|
52
|
+
Output = TypeVar("Output", covariant=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Executable(Generic[Input, Output], ABC):
|
|
56
|
+
name: str
|
|
57
|
+
|
|
58
|
+
description: str
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def input_schema(self) -> dict[str, Any]: ...
|
|
62
|
+
|
|
63
|
+
def stream(
|
|
64
|
+
self,
|
|
65
|
+
input: Input,
|
|
66
|
+
**kwargs: Optional[Any],
|
|
67
|
+
) -> Iterator[Output]:
|
|
68
|
+
yield self.invoke(input, **kwargs)
|
|
69
|
+
|
|
70
|
+
async def astream(
|
|
71
|
+
self,
|
|
72
|
+
input: Input,
|
|
73
|
+
**kwargs: Optional[Any],
|
|
74
|
+
) -> AsyncIterator[Output]:
|
|
75
|
+
yield await self.ainvoke(input, None, **kwargs)
|
|
76
|
+
|
|
77
|
+
def batch(
|
|
78
|
+
self,
|
|
79
|
+
inputs: list[Input],
|
|
80
|
+
max_parallel: int,
|
|
81
|
+
return_exceptions: bool = False,
|
|
82
|
+
**kwargs: Optional[Any],
|
|
83
|
+
) -> list[Output]:
|
|
84
|
+
if not inputs:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
def invoke(input: Input) -> Union[Output, Exception]:
|
|
88
|
+
if return_exceptions:
|
|
89
|
+
try:
|
|
90
|
+
return self.invoke(input, **kwargs)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return e
|
|
93
|
+
else:
|
|
94
|
+
return self.invoke(input, **kwargs)
|
|
95
|
+
|
|
96
|
+
if len(inputs) == 1:
|
|
97
|
+
return cast("list[Output]", [invoke(inputs[0])])
|
|
98
|
+
|
|
99
|
+
with get_executor(max_parallel) as executor:
|
|
100
|
+
return cast("list[Output]", list[executor.map(invoke, inputs)])
|
|
101
|
+
|
|
102
|
+
async def abatch(
|
|
103
|
+
self,
|
|
104
|
+
inputs: list[Input],
|
|
105
|
+
max_parallel: int,
|
|
106
|
+
return_exceptions: bool = False,
|
|
107
|
+
**kwargs: Optional[Any],
|
|
108
|
+
) -> list[Output]:
|
|
109
|
+
if not inputs:
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
with get_executor(max_parallel) as executor:
|
|
113
|
+
|
|
114
|
+
async def ainvoke(
|
|
115
|
+
input: Input,
|
|
116
|
+
) -> Union[Output, Exception]:
|
|
117
|
+
if return_exceptions:
|
|
118
|
+
try:
|
|
119
|
+
return await self.ainvoke(input, executor, **kwargs)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
return e
|
|
122
|
+
else:
|
|
123
|
+
return await self.ainvoke(input, executor, **kwargs)
|
|
124
|
+
|
|
125
|
+
coros = map(ainvoke, inputs)
|
|
126
|
+
return await gather_with_concurrency(max_parallel, *coros)
|
|
127
|
+
|
|
128
|
+
def with_retry(
|
|
129
|
+
self,
|
|
130
|
+
retry_exception_types: Tuple[Type[BaseException]] = (Exception,),
|
|
131
|
+
wait_exponential_jitter: bool = True,
|
|
132
|
+
max_attempts: int = 3,
|
|
133
|
+
) -> Executable[Input, Output]:
|
|
134
|
+
return RetryableExecutable(self, retry_exception_types, wait_exponential_jitter, max_attempts)
|
|
135
|
+
|
|
136
|
+
def as_tool(
|
|
137
|
+
self,
|
|
138
|
+
name: Optional[str] = None,
|
|
139
|
+
description: Optional[str] = None,
|
|
140
|
+
) -> BaseTool:
|
|
141
|
+
from hiagent_components.tool.tool import ExecutableTool
|
|
142
|
+
|
|
143
|
+
return ExecutableTool.from_executable(self, name, description)
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def invoke(self, input: Input, **kwargs: Any) -> Output: ...
|
|
147
|
+
|
|
148
|
+
async def ainvoke(
|
|
149
|
+
self,
|
|
150
|
+
input: Input,
|
|
151
|
+
executor: Optional[Executor] = None,
|
|
152
|
+
**kwargs: Any,
|
|
153
|
+
) -> Output:
|
|
154
|
+
return await run_in_executor(executor, self.invoke, input, **kwargs)
|
|
155
|
+
|
|
156
|
+
class RetryableExecutable(Executable[Input, Output]):
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
executable: Executable[Input, Output],
|
|
160
|
+
retry_exception_types: Tuple[Type[BaseException], ...] = (Exception,),
|
|
161
|
+
wait_exponential_jitter: bool = True,
|
|
162
|
+
max_attempts: int = 3,
|
|
163
|
+
):
|
|
164
|
+
self.max_attempts = max_attempts
|
|
165
|
+
self.retry_exception_types = retry_exception_types
|
|
166
|
+
self.wait_exponential_jitter = wait_exponential_jitter
|
|
167
|
+
self.executable = executable
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def _retrying_kwargs(self) -> Dict[str, Any]:
|
|
171
|
+
kwargs: Dict[str, Any] = dict()
|
|
172
|
+
|
|
173
|
+
if self.max_attempts:
|
|
174
|
+
kwargs["stop"] = stop_after_attempt(self.max_attempts)
|
|
175
|
+
|
|
176
|
+
if self.wait_exponential_jitter:
|
|
177
|
+
kwargs["wait"] = wait_exponential_jitter()
|
|
178
|
+
|
|
179
|
+
if self.retry_exception_types:
|
|
180
|
+
kwargs["retry"] = retry_if_exception_type(self.retry_exception_types)
|
|
181
|
+
|
|
182
|
+
return kwargs
|
|
183
|
+
|
|
184
|
+
def _sync_retrying(self, **kwargs: Any) -> Retrying:
|
|
185
|
+
return Retrying(**self._retrying_kwargs, **kwargs)
|
|
186
|
+
|
|
187
|
+
def _async_retrying(self, **kwargs: Any) -> AsyncRetrying:
|
|
188
|
+
return AsyncRetrying(**self._retrying_kwargs, **kwargs)
|
|
189
|
+
|
|
190
|
+
def invoke(self, input: Input, **kwargs: Any) -> Output:
|
|
191
|
+
result = None
|
|
192
|
+
for attempt in self._sync_retrying(reraise=True):
|
|
193
|
+
with attempt:
|
|
194
|
+
result = self.executable.invoke(
|
|
195
|
+
input,
|
|
196
|
+
**kwargs,
|
|
197
|
+
)
|
|
198
|
+
if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
|
|
199
|
+
attempt.retry_state.set_result(result)
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
async def ainvoke(
|
|
203
|
+
self,
|
|
204
|
+
input: Input,
|
|
205
|
+
executor: Optional[Executor] = None,
|
|
206
|
+
**kwargs: Any
|
|
207
|
+
) -> Output:
|
|
208
|
+
result = None
|
|
209
|
+
async for attempt in self._async_retrying(reraise=True):
|
|
210
|
+
with attempt:
|
|
211
|
+
result = await self.executable.ainvoke(
|
|
212
|
+
input,
|
|
213
|
+
executor,
|
|
214
|
+
**kwargs,
|
|
215
|
+
)
|
|
216
|
+
if attempt.retry_state.outcome and not attempt.retry_state.outcome.failed:
|
|
217
|
+
attempt.retry_state.set_result(result)
|
|
218
|
+
return result
|