hiagent-eva 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_eva-0.1.0/.gitignore +169 -0
- hiagent_eva-0.1.0/PKG-INFO +5 -0
- hiagent_eva-0.1.0/README.md +0 -0
- hiagent_eva-0.1.0/__init__.py +86 -0
- hiagent_eva-0.1.0/client.py +487 -0
- hiagent_eva-0.1.0/pyproject.toml +14 -0
- hiagent_eva-0.1.0/samples/sample.py +145 -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
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
"""
|
|
15
|
+
Eva SDK - Evaluation Service SDK
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from ..api.hiagent_api.eva import EvaService
|
|
19
|
+
from ..api.hiagent_api.eva_types import (
|
|
20
|
+
AgentModeStrategy,
|
|
21
|
+
CaseData,
|
|
22
|
+
CreateEvaTaskRequest,
|
|
23
|
+
CreateEvaTaskResponse,
|
|
24
|
+
DatasetTaskConfig,
|
|
25
|
+
EvaDatasetColumn,
|
|
26
|
+
EvaDatasetConversationItem,
|
|
27
|
+
EvaTargetCustomAPPConfig,
|
|
28
|
+
EvaTargetType,
|
|
29
|
+
EvaTaskItem,
|
|
30
|
+
EvaTaskItemDataset,
|
|
31
|
+
EvaTaskItemRuleset,
|
|
32
|
+
EvaTaskItemRulesetRule,
|
|
33
|
+
EvaTaskItemTarget,
|
|
34
|
+
EvaTaskResultTargetContentPair,
|
|
35
|
+
EvaTaskResultUpdateTargetContent,
|
|
36
|
+
EvaTaskStatus,
|
|
37
|
+
EvaTaskTarget,
|
|
38
|
+
ExecEvaTaskRowGroupRequest,
|
|
39
|
+
GetEvaTaskReportRequest,
|
|
40
|
+
GetEvaTaskReportResponse,
|
|
41
|
+
GetEvaTaskRequest,
|
|
42
|
+
GetEvaTaskResponse,
|
|
43
|
+
InferenceResult,
|
|
44
|
+
ListEvaDatasetColumnsRequest,
|
|
45
|
+
ListEvaDatasetColumnsResponse,
|
|
46
|
+
ListEvaDatasetConversationsRequest,
|
|
47
|
+
ListEvaDatasetConversationsResponse,
|
|
48
|
+
ModelAgentConfig,
|
|
49
|
+
)
|
|
50
|
+
from .client import Client
|
|
51
|
+
|
|
52
|
+
__version__ = "1.0.0"
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"Client",
|
|
56
|
+
"EvaService",
|
|
57
|
+
"CreateEvaTaskRequest",
|
|
58
|
+
"ListEvaDatasetConversationsRequest",
|
|
59
|
+
"ListEvaDatasetColumnsRequest",
|
|
60
|
+
"ExecEvaTaskRowGroupRequest",
|
|
61
|
+
"GetEvaTaskReportRequest",
|
|
62
|
+
"GetEvaTaskRequest",
|
|
63
|
+
"CreateEvaTaskResponse",
|
|
64
|
+
"ListEvaDatasetConversationsResponse",
|
|
65
|
+
"ListEvaDatasetColumnsResponse",
|
|
66
|
+
"GetEvaTaskReportResponse",
|
|
67
|
+
"GetEvaTaskResponse",
|
|
68
|
+
"EvaTaskTarget",
|
|
69
|
+
"DatasetTaskConfig",
|
|
70
|
+
"EvaDatasetColumn",
|
|
71
|
+
"EvaDatasetConversationItem",
|
|
72
|
+
"EvaTaskResultTargetContentPair",
|
|
73
|
+
"EvaTaskResultUpdateTargetContent",
|
|
74
|
+
"InferenceResult",
|
|
75
|
+
"EvaTaskItem",
|
|
76
|
+
"EvaTaskItemRuleset",
|
|
77
|
+
"EvaTaskItemRulesetRule",
|
|
78
|
+
"EvaTaskItemDataset",
|
|
79
|
+
"EvaTaskItemTarget",
|
|
80
|
+
"ModelAgentConfig",
|
|
81
|
+
"EvaTargetCustomAPPConfig",
|
|
82
|
+
"EvaTargetType",
|
|
83
|
+
"EvaTaskStatus",
|
|
84
|
+
"CaseData",
|
|
85
|
+
"AgentModeStrategy",
|
|
86
|
+
]
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import logging
|
|
16
|
+
import time
|
|
17
|
+
from typing import Callable, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed
|
|
20
|
+
|
|
21
|
+
from libs.observe.hiagent_observe.client import AuthSession
|
|
22
|
+
|
|
23
|
+
from ..api.hiagent_api import eva_types
|
|
24
|
+
from ..api.hiagent_api.eva import EvaService
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def init(endpoint: str, ak: str, sk: str, workspace_id: str, app_id: str):
|
|
30
|
+
"""
|
|
31
|
+
Initialize client configuration
|
|
32
|
+
"""
|
|
33
|
+
return Client(endpoint, ak, sk, workspace_id, app_id)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Client:
|
|
37
|
+
"""Eva SDK Client Class"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, endpoint: str, ak: str, sk: str, workspace_id: str, app_id: str):
|
|
40
|
+
"""
|
|
41
|
+
Initialize client configuration
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
endpoint: API endpoint
|
|
45
|
+
ak: Access Key
|
|
46
|
+
sk: Secret Key
|
|
47
|
+
workspace_id: Workspace ID
|
|
48
|
+
app_id: Application ID
|
|
49
|
+
"""
|
|
50
|
+
self.endpoint = endpoint
|
|
51
|
+
self.workspace_id = workspace_id
|
|
52
|
+
self.app_id = app_id
|
|
53
|
+
self.logger = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
# Initialize Eva service
|
|
56
|
+
self.eva_service = EvaService(endpoint=endpoint)
|
|
57
|
+
|
|
58
|
+
self.eva_service.set_ak(ak)
|
|
59
|
+
self.eva_service.set_sk(sk)
|
|
60
|
+
|
|
61
|
+
self.logger.info(f"Eva client initialized with endpoint: {endpoint}")
|
|
62
|
+
|
|
63
|
+
def create_task(
|
|
64
|
+
self,
|
|
65
|
+
dataset_id: str,
|
|
66
|
+
task_name: str,
|
|
67
|
+
ruleset_id: str,
|
|
68
|
+
description: str = "",
|
|
69
|
+
model_agent_config: Optional[eva_types.ModelAgentConfig] = None,
|
|
70
|
+
run_immediately: bool = True,
|
|
71
|
+
) -> eva_types.CreateEvaTaskResponse:
|
|
72
|
+
"""
|
|
73
|
+
Create evaluation task
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
dataset_id: Dataset ID
|
|
77
|
+
task_name: Task name
|
|
78
|
+
ruleset_id: Ruleset ID
|
|
79
|
+
description: Task description
|
|
80
|
+
model_agent_config: Model agent configuration (optional, will be automatically assembled into complete configuration using app_id)
|
|
81
|
+
run_immediately: Whether to run immediately
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
CreateEvaTaskResponse: Create task response
|
|
85
|
+
"""
|
|
86
|
+
if not self.eva_service:
|
|
87
|
+
raise ValueError("Client not initialized. Call init() first.")
|
|
88
|
+
|
|
89
|
+
# Automatically create evaluation target, using app_id as target_id
|
|
90
|
+
# If ModelAgentConfig is provided, assemble it into complete EvaTargetCustomAPPConfig
|
|
91
|
+
if model_agent_config:
|
|
92
|
+
target_config = eva_types.EvaTargetCustomAPPConfig(
|
|
93
|
+
AppID=self.app_id, ModelAgentConfig=model_agent_config.model_dump()
|
|
94
|
+
)
|
|
95
|
+
target_config_dict = target_config.model_dump()
|
|
96
|
+
else:
|
|
97
|
+
target_config_dict = {}
|
|
98
|
+
|
|
99
|
+
targets = [
|
|
100
|
+
eva_types.EvaTaskTarget(
|
|
101
|
+
Type="CustomAPP",
|
|
102
|
+
TargetID=self.app_id,
|
|
103
|
+
TargetName=f"App-{self.app_id}",
|
|
104
|
+
TargetConfig=target_config_dict,
|
|
105
|
+
QPS=1,
|
|
106
|
+
)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
request = eva_types.CreateEvaTaskRequest(
|
|
110
|
+
WorkspaceID=self.workspace_id,
|
|
111
|
+
Name=task_name,
|
|
112
|
+
Description=description,
|
|
113
|
+
Targets=targets,
|
|
114
|
+
DatasetID=dataset_id,
|
|
115
|
+
RulesetID=ruleset_id,
|
|
116
|
+
RunImmediately=run_immediately,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return self.eva_service.CreateEvaTask(request)
|
|
120
|
+
|
|
121
|
+
def list_dataset_conversations(
|
|
122
|
+
self, dataset_id: str, page_number: int = 1, page_size: int = 20
|
|
123
|
+
) -> eva_types.ListEvaDatasetConversationsResponse:
|
|
124
|
+
"""
|
|
125
|
+
List dataset conversations
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
dataset_id: Dataset ID
|
|
129
|
+
page_number: Page number
|
|
130
|
+
page_size: Page size
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
ListEvaDatasetConversationsResponse: Dataset conversation response
|
|
134
|
+
"""
|
|
135
|
+
if not self.eva_service:
|
|
136
|
+
raise ValueError("Client not initialized. Call init() first.")
|
|
137
|
+
|
|
138
|
+
request = eva_types.ListEvaDatasetConversationsRequest(
|
|
139
|
+
WorkspaceID=self.workspace_id,
|
|
140
|
+
DatasetID=dataset_id,
|
|
141
|
+
PageNumber=page_number,
|
|
142
|
+
PageSize=page_size,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return self.eva_service.ListEvaDatasetConversations(request)
|
|
146
|
+
|
|
147
|
+
def list_dataset_columns(
|
|
148
|
+
self, dataset_id: str
|
|
149
|
+
) -> eva_types.ListEvaDatasetColumnsResponse:
|
|
150
|
+
"""
|
|
151
|
+
Get dataset column information
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
dataset_id: Dataset ID
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
ListEvaDatasetColumnsResponse: Dataset column information response
|
|
158
|
+
"""
|
|
159
|
+
if not self.eva_service:
|
|
160
|
+
raise ValueError("Client not initialized. Call init() first.")
|
|
161
|
+
|
|
162
|
+
request = eva_types.ListEvaDatasetColumnsRequest(
|
|
163
|
+
WorkspaceID=self.workspace_id,
|
|
164
|
+
DatasetID=dataset_id,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return self.eva_service.ListEvaDatasetColumns(request)
|
|
168
|
+
|
|
169
|
+
def submit_task_row_group_results(
|
|
170
|
+
self,
|
|
171
|
+
task_id: str,
|
|
172
|
+
row_id: str,
|
|
173
|
+
target_results: Optional[
|
|
174
|
+
List[eva_types.EvaTaskResultUpdateTargetContent]
|
|
175
|
+
] = None,
|
|
176
|
+
) -> eva_types.ExecEvaTaskRowGroupResponse:
|
|
177
|
+
"""
|
|
178
|
+
Submit evaluation task row group results
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
task_id: Task ID
|
|
182
|
+
row_id: Row ID
|
|
183
|
+
target_results: Target results list
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
ExecEvaTaskRowGroupResponse: Execution response
|
|
187
|
+
"""
|
|
188
|
+
if not self.eva_service:
|
|
189
|
+
raise ValueError("Client not initialized. Call init() first.")
|
|
190
|
+
|
|
191
|
+
request = eva_types.ExecEvaTaskRowGroupRequest(
|
|
192
|
+
WorkspaceID=self.workspace_id,
|
|
193
|
+
TaskID=task_id,
|
|
194
|
+
RowID=row_id,
|
|
195
|
+
TargetResults=target_results,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return self.eva_service.ExecEvaTaskRowGroup(request)
|
|
199
|
+
|
|
200
|
+
@retry(
|
|
201
|
+
stop=stop_after_attempt(3),
|
|
202
|
+
wait=wait_fixed(1),
|
|
203
|
+
before_sleep=before_sleep_log(logger, logging.INFO),
|
|
204
|
+
)
|
|
205
|
+
def get_task_report(self, task_id: str) -> eva_types.GetEvaTaskReportResponse:
|
|
206
|
+
"""
|
|
207
|
+
Get task report (with retry mechanism: 1 second interval, 3 attempts)
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
task_id: Task ID
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
GetEvaTaskReportResponse: Task report response
|
|
214
|
+
"""
|
|
215
|
+
if not self.eva_service:
|
|
216
|
+
raise ValueError("Client not initialized. Call init() first.")
|
|
217
|
+
|
|
218
|
+
request = eva_types.GetEvaTaskReportRequest(
|
|
219
|
+
WorkspaceID=self.workspace_id, TaskID=task_id
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return self.eva_service.GetEvaTaskReport(request)
|
|
223
|
+
|
|
224
|
+
def _convert_to_case_data_list(
|
|
225
|
+
self,
|
|
226
|
+
conversation_item: eva_types.EvaDatasetConversationItem,
|
|
227
|
+
columns: List[eva_types.EvaDatasetColumn],
|
|
228
|
+
) -> List[eva_types.CaseData]:
|
|
229
|
+
"""
|
|
230
|
+
Convert raw conversation item to case data list
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
conversation_item: Raw conversation item
|
|
234
|
+
columns: Column information
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Converted case data list
|
|
238
|
+
"""
|
|
239
|
+
# Create column ID to column name mapping
|
|
240
|
+
column_map = {col.ID: col.Name for col in columns}
|
|
241
|
+
|
|
242
|
+
# Organize data by rounds
|
|
243
|
+
rounds_data: Dict[int, Dict[str, eva_types.CellContent]] = {}
|
|
244
|
+
|
|
245
|
+
for data_cell in conversation_item.DataRow:
|
|
246
|
+
column_name = column_map.get(
|
|
247
|
+
data_cell.ColumnID, f"Column_{data_cell.ColumnID}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
for conv_data in data_cell.ConversationGroup:
|
|
251
|
+
round_num = conv_data.Round
|
|
252
|
+
if round_num not in rounds_data:
|
|
253
|
+
rounds_data[round_num] = {}
|
|
254
|
+
rounds_data[round_num][column_name] = self._convert_to_cell_content(
|
|
255
|
+
conv_data.AtomicData
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Create CellContent list in round order
|
|
259
|
+
sorted_rounds = sorted(rounds_data.keys())
|
|
260
|
+
case_data_list = []
|
|
261
|
+
|
|
262
|
+
for round_num in sorted_rounds:
|
|
263
|
+
case_data = eva_types.CaseData(**rounds_data[round_num])
|
|
264
|
+
case_data_list.append(case_data)
|
|
265
|
+
|
|
266
|
+
return case_data_list
|
|
267
|
+
|
|
268
|
+
def _convert_to_cell_content(
|
|
269
|
+
self, atomic_data: eva_types.EvaDatasetAtomicData
|
|
270
|
+
) -> eva_types.CellContent:
|
|
271
|
+
"""
|
|
272
|
+
Convert atomic data to cell content
|
|
273
|
+
"""
|
|
274
|
+
content = []
|
|
275
|
+
if atomic_data.Type == eva_types.DatasetAtomicDataType.TEXT:
|
|
276
|
+
content.append(
|
|
277
|
+
eva_types.CellContentPart(
|
|
278
|
+
Type=eva_types.CellContentPartType.TEXT,
|
|
279
|
+
Text=atomic_data.TextData,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
elif atomic_data.Type == eva_types.DatasetAtomicDataType.IMAGE:
|
|
283
|
+
content.extend(
|
|
284
|
+
[
|
|
285
|
+
eva_types.CellContentPart(
|
|
286
|
+
Type=eva_types.CellContentPartType.IMAGE_URL,
|
|
287
|
+
ImageURL=image.URL,
|
|
288
|
+
)
|
|
289
|
+
for image in atomic_data.ImageData
|
|
290
|
+
]
|
|
291
|
+
)
|
|
292
|
+
elif atomic_data.Type == eva_types.DatasetAtomicDataType.FILE:
|
|
293
|
+
content.extend(
|
|
294
|
+
[
|
|
295
|
+
eva_types.CellContentPart(
|
|
296
|
+
Type=eva_types.CellContentPartType.VIDEO_URL,
|
|
297
|
+
VideoURL=file.URL,
|
|
298
|
+
)
|
|
299
|
+
for file in atomic_data.Files
|
|
300
|
+
]
|
|
301
|
+
)
|
|
302
|
+
return content
|
|
303
|
+
|
|
304
|
+
def run_evaluation(
|
|
305
|
+
self,
|
|
306
|
+
dataset_id: str,
|
|
307
|
+
task_name: str,
|
|
308
|
+
inference_function: Callable[
|
|
309
|
+
[List[eva_types.CaseData]], List[eva_types.InferenceResult]
|
|
310
|
+
],
|
|
311
|
+
ruleset_id: str,
|
|
312
|
+
target_config: Optional[eva_types.ModelAgentConfig] = None,
|
|
313
|
+
max_conversations: int = 10,
|
|
314
|
+
) -> eva_types.GetEvaTaskReportResponse:
|
|
315
|
+
"""
|
|
316
|
+
Run complete evaluation process
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
dataset_id: Dataset ID
|
|
320
|
+
task_name: Task name
|
|
321
|
+
inference_function: Inference function that receives case data list and returns user inference results list
|
|
322
|
+
ruleset_id: Ruleset ID
|
|
323
|
+
target_config: Model agent configuration (optional)
|
|
324
|
+
max_conversations: Maximum number of conversations
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
GetEvaTaskReportResponse: Evaluation report
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
# 1. Create evaluation task
|
|
331
|
+
self.logger.info(f"Creating evaluation task: {task_name}")
|
|
332
|
+
task_response = self.create_task(
|
|
333
|
+
dataset_id=dataset_id,
|
|
334
|
+
task_name=task_name,
|
|
335
|
+
ruleset_id=ruleset_id,
|
|
336
|
+
model_agent_config=target_config,
|
|
337
|
+
)
|
|
338
|
+
task_id = task_response.TaskID
|
|
339
|
+
self.logger.info(f"Task created successfully: {task_id}")
|
|
340
|
+
|
|
341
|
+
# 2. Get dataset column information
|
|
342
|
+
self.logger.info("Fetching dataset columns...")
|
|
343
|
+
columns_response = self.list_dataset_columns(dataset_id)
|
|
344
|
+
columns = columns_response.Columns
|
|
345
|
+
self.logger.info(
|
|
346
|
+
f"Fetched {len(columns)} columns: {[col.Name for col in columns]}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# 3. Get dataset conversations
|
|
350
|
+
self.logger.info("Fetching dataset conversations...")
|
|
351
|
+
conversations_response = self.list_dataset_conversations(
|
|
352
|
+
dataset_id=dataset_id, page_size=max_conversations
|
|
353
|
+
)
|
|
354
|
+
conversation_items = conversations_response.Items
|
|
355
|
+
self.logger.info(f"Fetched {len(conversation_items)} conversation items")
|
|
356
|
+
|
|
357
|
+
# 4. Execute inference and submit results
|
|
358
|
+
self.logger.info("Running inference and submitting results...")
|
|
359
|
+
time.sleep(2)
|
|
360
|
+
for conversation_item in conversation_items:
|
|
361
|
+
# Convert to case data list
|
|
362
|
+
case_data_list = self._convert_to_case_data_list(
|
|
363
|
+
conversation_item, columns
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Call inference function, passing case data list
|
|
367
|
+
target_content_pairs = self._execute_inference_with_wrapper(
|
|
368
|
+
inference_function, case_data_list, conversation_item.RowID
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# Create target results
|
|
372
|
+
target_results = [
|
|
373
|
+
eva_types.EvaTaskResultUpdateTargetContent(
|
|
374
|
+
TargetType=eva_types.EvaTargetType.CUSTOM_APP,
|
|
375
|
+
TargetID=self.app_id,
|
|
376
|
+
Results=target_content_pairs,
|
|
377
|
+
)
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
# Submit results
|
|
381
|
+
self.submit_task_row_group_results(
|
|
382
|
+
task_id, conversation_item.RowID, target_results
|
|
383
|
+
)
|
|
384
|
+
self.logger.debug(
|
|
385
|
+
f"Results submitted for row {conversation_item.RowID}"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# 5. Wait for processing to complete
|
|
389
|
+
self.logger.info("Waiting for evaluation to complete...")
|
|
390
|
+
self._wait_task_finished(task_id=task_id)
|
|
391
|
+
|
|
392
|
+
# 6. Get evaluation report
|
|
393
|
+
report = self.get_task_report(task_id)
|
|
394
|
+
self.logger.info(f"Evaluation completed with status: {report.Status}")
|
|
395
|
+
|
|
396
|
+
return report
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
self.logger.error(f"Evaluation failed: {e}", exc_info=True)
|
|
400
|
+
raise
|
|
401
|
+
|
|
402
|
+
def _execute_inference_with_wrapper(
|
|
403
|
+
self,
|
|
404
|
+
inference_function: Callable[
|
|
405
|
+
[List[eva_types.CaseData]], List[eva_types.InferenceResult]
|
|
406
|
+
],
|
|
407
|
+
case_data_list: List[eva_types.CaseData],
|
|
408
|
+
row_id: str,
|
|
409
|
+
) -> List[eva_types.EvaTaskResultTargetContentPair]:
|
|
410
|
+
"""
|
|
411
|
+
Execute inference function and wrap results
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
inference_function: User-provided inference function
|
|
415
|
+
case_data_list: Case data list
|
|
416
|
+
row_id: Row ID
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Wrapped target content pair list
|
|
420
|
+
"""
|
|
421
|
+
import time
|
|
422
|
+
|
|
423
|
+
start_time = time.time()
|
|
424
|
+
round_counter = 1
|
|
425
|
+
target_content_pairs = []
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
# Execute user's inference function, passing case data list
|
|
429
|
+
user_results = inference_function(case_data_list)
|
|
430
|
+
end_time = time.time()
|
|
431
|
+
inference_duration = int((end_time - start_time) * 1000)
|
|
432
|
+
|
|
433
|
+
# Wrap each result
|
|
434
|
+
for user_result in user_results:
|
|
435
|
+
target_content_pair = eva_types.EvaTaskResultTargetContentPair(
|
|
436
|
+
Content=user_result.Content,
|
|
437
|
+
ContentThought=user_result.ContentThought,
|
|
438
|
+
Round=round_counter,
|
|
439
|
+
MessageID=None, # Not needed
|
|
440
|
+
ConversationID=None, # Not needed
|
|
441
|
+
Status=eva_types.EvaConversationStatus.SUCCEED, # Success status
|
|
442
|
+
StatusMessage=None,
|
|
443
|
+
CostTokens=user_result.CostTokens,
|
|
444
|
+
InferenceDuration=inference_duration,
|
|
445
|
+
RuleDuration=None, # Not needed
|
|
446
|
+
TTFT=user_result.TTFT,
|
|
447
|
+
)
|
|
448
|
+
target_content_pairs.append(target_content_pair)
|
|
449
|
+
round_counter += 1
|
|
450
|
+
|
|
451
|
+
except Exception as e:
|
|
452
|
+
# If there's an exception, create a failed status result
|
|
453
|
+
end_time = time.time()
|
|
454
|
+
inference_duration = int((end_time - start_time) * 1000)
|
|
455
|
+
|
|
456
|
+
target_content_pair = eva_types.EvaTaskResultTargetContentPair(
|
|
457
|
+
Content=None,
|
|
458
|
+
ContentThought=None,
|
|
459
|
+
Round=1,
|
|
460
|
+
MessageID=None,
|
|
461
|
+
ConversationID=None,
|
|
462
|
+
Status=eva_types.EvaConversationStatus.FAILED,
|
|
463
|
+
StatusMessage=str(e), # Exception message as status message
|
|
464
|
+
CostTokens=None,
|
|
465
|
+
InferenceDuration=inference_duration,
|
|
466
|
+
RuleDuration=None,
|
|
467
|
+
TTFT=None,
|
|
468
|
+
)
|
|
469
|
+
target_content_pairs.append(target_content_pair)
|
|
470
|
+
|
|
471
|
+
self.logger.error(f"Inference failed for row {row_id}: {e}")
|
|
472
|
+
|
|
473
|
+
return target_content_pairs
|
|
474
|
+
|
|
475
|
+
def _wait_task_finished(self, task_id: str):
|
|
476
|
+
request = eva_types.GetEvaTaskRequest(
|
|
477
|
+
WorkspaceID=self.workspace_id, TaskID=task_id
|
|
478
|
+
)
|
|
479
|
+
task = self.eva_service.GetEvaTask(request)
|
|
480
|
+
while task.Status not in [
|
|
481
|
+
eva_types.EvaTaskStatus.SUCCEED,
|
|
482
|
+
eva_types.EvaTaskStatus.FAILED,
|
|
483
|
+
eva_types.EvaTaskStatus.CANCELLED,
|
|
484
|
+
eva_types.EvaTaskStatus.PARTIAL_SUCCEED,
|
|
485
|
+
]:
|
|
486
|
+
time.sleep(1)
|
|
487
|
+
task = self.eva_service.GetEvaTask(request)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hiagent-eva"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["hatchling"]
|
|
11
|
+
build-backend = "hatchling.build"
|
|
12
|
+
|
|
13
|
+
[tool.hatch.build.targets.wheel]
|
|
14
|
+
packages = ["."]
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
#!/usr/bin/env python3
|
|
15
|
+
"""
|
|
16
|
+
Eva SDK Usage Example
|
|
17
|
+
|
|
18
|
+
Demonstrates how to use Eva SDK to create and execute evaluation tasks
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
from time import sleep
|
|
26
|
+
from typing import List
|
|
27
|
+
|
|
28
|
+
from dotenv import load_dotenv
|
|
29
|
+
|
|
30
|
+
# Import Eva SDK
|
|
31
|
+
from libs.api.hiagent_api import eva_types
|
|
32
|
+
from libs.eva import client
|
|
33
|
+
|
|
34
|
+
# Configure logging
|
|
35
|
+
logging.basicConfig(level=logging.INFO)
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
load_dotenv()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def my_inference_function(
|
|
42
|
+
case_data_list: List[eva_types.CaseData],
|
|
43
|
+
) -> List[eva_types.InferenceResult]:
|
|
44
|
+
results = []
|
|
45
|
+
message_list = []
|
|
46
|
+
for case in case_data_list:
|
|
47
|
+
input = case["input"][0].Text
|
|
48
|
+
message_list.append({"role": "user", "content": input})
|
|
49
|
+
content = f"message list={json.dumps(message_list, ensure_ascii=False)}"
|
|
50
|
+
message_list.append({"role": "assistant", "content": content})
|
|
51
|
+
# Create inference result
|
|
52
|
+
result = eva_types.InferenceResult(
|
|
53
|
+
Content=content,
|
|
54
|
+
CostTokens=100,
|
|
55
|
+
TTFT=101,
|
|
56
|
+
)
|
|
57
|
+
results.append(result)
|
|
58
|
+
sleep(0.5)
|
|
59
|
+
|
|
60
|
+
return results
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main():
|
|
64
|
+
"""Main function: Run complete evaluation example"""
|
|
65
|
+
|
|
66
|
+
# Parse command line arguments
|
|
67
|
+
parser = argparse.ArgumentParser(description="Eva SDK Evaluation Example")
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"-d", "--dataset-id", required=True, help="Dataset ID for evaluation"
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"-r", "--ruleset-id", required=True, help="Ruleset ID for evaluation"
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument("-n", "--name", required=True, help="Task name")
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
|
|
77
|
+
print("=== Eva SDK Example ===\n")
|
|
78
|
+
|
|
79
|
+
# Initialize client
|
|
80
|
+
provider = client.init(
|
|
81
|
+
endpoint=os.getenv("HIAGENT_TOP_ENDPOINT"),
|
|
82
|
+
ak=os.getenv("VOLC_ACCESSKEY"),
|
|
83
|
+
sk=os.getenv("VOLC_SECRETKEY"),
|
|
84
|
+
workspace_id=os.getenv("WORKSPACE_ID"),
|
|
85
|
+
app_id=os.getenv("CUSTOM_APP_ID"),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Use command line arguments
|
|
89
|
+
dataset_id = args.dataset_id
|
|
90
|
+
ruleset_id = args.ruleset_id
|
|
91
|
+
task_name = args.name
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Run evaluation
|
|
95
|
+
print("Starting evaluation...")
|
|
96
|
+
print(f"Dataset ID: {dataset_id}")
|
|
97
|
+
print(f"Ruleset ID: {ruleset_id}")
|
|
98
|
+
report = provider.run_evaluation(
|
|
99
|
+
dataset_id=dataset_id,
|
|
100
|
+
task_name=task_name,
|
|
101
|
+
inference_function=my_inference_function,
|
|
102
|
+
ruleset_id=ruleset_id,
|
|
103
|
+
max_conversations=5,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Print results
|
|
107
|
+
print("\n✓ Evaluation completed!")
|
|
108
|
+
print(f" Task ID: {report.TaskID}")
|
|
109
|
+
print(f" Task Name: {report.TaskName}")
|
|
110
|
+
print(f" Status: {report.Status}")
|
|
111
|
+
print(f" Number of Rules: {len(report.Rules)}")
|
|
112
|
+
print(f" Number of Targets: {len(report.Targets)}")
|
|
113
|
+
print(f" Created At: {report.CreatedAt}")
|
|
114
|
+
print(f" Updated At: {report.UpdatedAt}")
|
|
115
|
+
|
|
116
|
+
# Display target information
|
|
117
|
+
if report.Targets:
|
|
118
|
+
print("\nInference Information:")
|
|
119
|
+
for target in report.Targets:
|
|
120
|
+
print(f" • Target ID: {target.TargetID}")
|
|
121
|
+
print(f" Target Name: {target.TargetDetail.TargetName}")
|
|
122
|
+
print(f" Average Token Cost: {target.AvgCostTokens}")
|
|
123
|
+
print(f" Average TTFT: {target.AvgTTFT}ms")
|
|
124
|
+
print(f" Average Duration: {target.AvgDuration}ms")
|
|
125
|
+
print(f" Total Token Cost: {target.CostTokens}")
|
|
126
|
+
|
|
127
|
+
# Display rule information
|
|
128
|
+
if report.Rules:
|
|
129
|
+
print("\nRule Information:")
|
|
130
|
+
for rule in report.Rules:
|
|
131
|
+
print(f" • Rule ID: {rule.RuleID}")
|
|
132
|
+
for rule_target in rule.Targets:
|
|
133
|
+
print(f" Target: {rule_target.TargetDetail.TargetName}")
|
|
134
|
+
print(f" Average Score: {rule_target.AvgScore}")
|
|
135
|
+
print(f" Percentage: {rule_target.Percent}%")
|
|
136
|
+
|
|
137
|
+
print("\n=== Evaluation Completed ===")
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.error(f"Evaluation execution failed: {e}", exc_info=True)
|
|
141
|
+
print(f"✗ Evaluation execution failed: {e}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|