hiagent-observe 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.
@@ -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,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: hiagent-observe
3
+ Version: 0.1.0
4
+ Summary: HiAgent Observe SDK
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: hiagent-api<0.2.0,>=0.1.0
7
+ Requires-Dist: opentelemetry-api>=1.33.1
8
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.33.1
9
+ Requires-Dist: opentelemetry-sdk>=1.33.1
10
+ Requires-Dist: pydantic>=2.11.5
11
+ Requires-Dist: python-dotenv>=1.1.0
12
+ Requires-Dist: requests==2.32.3
13
+ Description-Content-Type: text/markdown
14
+
15
+ # HiAgent Observe SDK
@@ -0,0 +1 @@
1
+ # HiAgent Observe SDK
@@ -0,0 +1,14 @@
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
+ VERSION = "v0.1.0"
@@ -0,0 +1,123 @@
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
+ import logging
15
+ import time
16
+
17
+ import requests
18
+ from hiagent_api.observe import ObserveService, observe_types
19
+ from hiagent_observe import VERSION
20
+ from opentelemetry import trace
21
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
22
+ from opentelemetry.propagate import set_global_textmap
23
+ from opentelemetry.propagators.composite import CompositePropagator
24
+ from opentelemetry.sdk.resources import Resource
25
+ from opentelemetry.sdk.trace import TracerProvider
26
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
27
+ from opentelemetry.semconv.resource import ResourceAttributes
28
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class AuthSession(requests.Session):
34
+ def __init__(self, endpoint: str, ak: str, sk: str, workspace_id: str, app_id: str):
35
+ super().__init__()
36
+ self.workspace_id = workspace_id
37
+ self.app_id = app_id
38
+ self.token = None
39
+ self.expires_at = 0
40
+ self.observe_svc = ObserveService(endpoint=endpoint, region="cn-north-1")
41
+ self.observe_svc.set_ak(ak)
42
+ self.observe_svc.set_sk(sk)
43
+
44
+ def get_token(self) -> str:
45
+ token_resp = self.observe_svc.CreateApiToken(
46
+ observe_types.CreateApiTokenRequest(
47
+ WorkspaceID=self.workspace_id, CustomAppID=self.app_id
48
+ )
49
+ )
50
+ self.token = token_resp.Token
51
+
52
+ # 预留一些空间提前过期
53
+ self.expires_at = time.time() + token_resp.ExpiresIn - 100
54
+
55
+ return self.token
56
+
57
+ def is_token_expired(self):
58
+ return time.time() >= self.expires_at
59
+
60
+ def refresh_token_if_needed(self):
61
+ if not self.token or self.is_token_expired():
62
+ logger.debug("token expired or not set, refreshing...")
63
+ self.get_token()
64
+
65
+ def request(self, method, url, headers=None, **kwargs):
66
+ self.refresh_token_if_needed()
67
+
68
+ headers = headers or {}
69
+ headers["Authorization"] = f"Bearer {self.token}"
70
+ logger.debug(f"requesting {url} to export trace data")
71
+
72
+ response = super().request(method, url, headers=headers, **kwargs)
73
+
74
+ # 如果 token 失效,尝试重新获取并重试一次
75
+ if response.status_code == 401:
76
+ self.get_token()
77
+ headers["Authorization"] = f"Bearer {self.token}"
78
+ response = super().request(method, url, headers=headers, **kwargs)
79
+
80
+ return response
81
+
82
+
83
+ def init(
84
+ trace_endpoint: str,
85
+ top_endpoint: str,
86
+ ak: str,
87
+ sk: str,
88
+ workspace_id: str,
89
+ app_id: str,
90
+ ):
91
+ auth_session = AuthSession(top_endpoint, ak, sk, workspace_id, app_id)
92
+
93
+ try:
94
+ token = auth_session.get_token()
95
+ logger.debug(f"got initial token: {token}")
96
+ except Exception as e:
97
+ raise RuntimeError(f"failed to get initial token: {e}")
98
+
99
+ exporter = OTLPSpanExporter(
100
+ endpoint=f"{trace_endpoint}/v1/traces", session=auth_session
101
+ )
102
+
103
+ resource = Resource.create(
104
+ {
105
+ ResourceAttributes.SERVICE_NAME: "HIAGENT_OBSERVE_SDK",
106
+ ResourceAttributes.SERVICE_VERSION: VERSION,
107
+ }
108
+ )
109
+ provider = TracerProvider(
110
+ resource=resource,
111
+ )
112
+ provider.add_span_processor(BatchSpanProcessor(exporter))
113
+ trace.set_tracer_provider(provider)
114
+
115
+ set_global_textmap(
116
+ CompositePropagator(
117
+ [
118
+ TraceContextTextMapPropagator(),
119
+ ]
120
+ )
121
+ )
122
+
123
+ return provider
@@ -0,0 +1,113 @@
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
+ import logging
15
+ from contextlib import contextmanager
16
+ from functools import wraps
17
+ from typing import Dict
18
+
19
+ from hiagent_observe.semconv import SemanticConvention
20
+ from opentelemetry import trace
21
+ from opentelemetry.sdk.trace import TracerProvider
22
+ from opentelemetry.trace import Span, SpanKind, Status, StatusCode
23
+
24
+
25
+ def tracable(wrapped):
26
+ """
27
+ Generates a telemetry wrapper for messages to collect metrics.
28
+ """
29
+ if not callable(wrapped):
30
+ raise TypeError(
31
+ f"@trace can only be applied to callable objects, got {type(wrapped).__name__}"
32
+ )
33
+
34
+ try:
35
+ __trace = trace.get_tracer_provider()
36
+ tracer = __trace.get_tracer(__name__)
37
+ except Exception as tracer_exception:
38
+ logging.error(
39
+ "Failed to initialize tracer: %s", tracer_exception, exc_info=True
40
+ )
41
+ raise
42
+
43
+ @wraps(wrapped)
44
+ def wrapper(*args, **kwargs):
45
+ with tracer.start_as_current_span(
46
+ name=wrapped.__name__,
47
+ kind=SpanKind.CLIENT,
48
+ ) as span:
49
+ response = None
50
+ try:
51
+ response = wrapped(*args, **kwargs)
52
+ span.set_attribute(
53
+ SemanticConvention.GEN_AI_CONTENT_COMPLETION, response or ""
54
+ )
55
+ span.set_status(Status(StatusCode.OK))
56
+ except Exception as e:
57
+ span.record_exception(e)
58
+ span.set_status(status=Status(StatusCode.ERROR), description=str(e))
59
+ logging.error("Error in %s: %s", wrapped.__name__, e, exc_info=True)
60
+ raise
61
+
62
+ try:
63
+ span.set_attribute("function.args", str(args))
64
+ span.set_attribute("function.kwargs", str(kwargs))
65
+ # span.set_attribute(
66
+ # SERVICE_NAME,
67
+ # OpenlitConfig.application_name,
68
+ # )
69
+ # span.set_attribute(DEPLOYMENT_ENVIRONMENT, OpenlitConfig.environment)
70
+ except Exception as meta_exception:
71
+ logging.error(
72
+ "Failed to set metadata for %s: %s",
73
+ wrapped.__name__,
74
+ meta_exception,
75
+ exc_info=True,
76
+ )
77
+
78
+ return response
79
+
80
+ return wrapper
81
+
82
+
83
+ class TracedSpan:
84
+ def __init__(self, span: Span):
85
+ self._span: Span = span
86
+
87
+ def __enter__(self):
88
+ return self
89
+
90
+ def set_attribute(self, key: str, value: str):
91
+ self._span.set_attribute(key, value)
92
+ self._span.set_status(Status(StatusCode.OK))
93
+
94
+ def set_status(self, status: StatusCode, description: str):
95
+ self._span.set_status(status=status, description=description)
96
+
97
+ def set_attributes(self, attributes: Dict):
98
+ for key, value in attributes.items():
99
+ self.set_attribute(key, value)
100
+
101
+ def __exit__(self, exc_type, exc_val, exc_tb):
102
+ self._span.end()
103
+
104
+
105
+ @contextmanager
106
+ def start_trace(name: str, provider: TracerProvider):
107
+ if provider is None:
108
+ provider = trace.get_tracer_provider()
109
+ with provider.get_tracer(__name__).start_as_current_span(
110
+ name,
111
+ kind=SpanKind.CLIENT,
112
+ ) as span:
113
+ yield TracedSpan(span)
@@ -0,0 +1,208 @@
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 enum import Enum
15
+
16
+
17
+ class SpanType(Enum):
18
+ START = "start"
19
+ """
20
+ START 表示开始调用模型或者工作流
21
+ """
22
+ END = "end"
23
+ """
24
+ END 停止调用模型或者工作流
25
+ """
26
+ LLM = "llm"
27
+ """
28
+ LLM 大模型调用节点
29
+ """
30
+ QA = "qa"
31
+ """
32
+ QA 代表问答库调用节点
33
+ """
34
+ KNOWLEDGE = "knowledge"
35
+ """
36
+ KNOWLEDGE 代表知识库调用节点
37
+ """
38
+ WORKFLOW = "workflow"
39
+ """
40
+ WORKFLOW 代表工作流的调用节点
41
+ """
42
+ RERANK = "rerank"
43
+ """
44
+ RERANK 代表 rerank 模型调用节点
45
+ """
46
+ EMBEDDINGS = "embeddings"
47
+ """
48
+ EMBEDDINGS 代表向量化模型调用节点
49
+ """
50
+ TOOL = "tool"
51
+ """
52
+ TOOL 代表工具调用节点
53
+ """
54
+ TERMINLOGY = "terminology"
55
+ """
56
+ TOOL_CALL 代表术语库调用节点
57
+ """
58
+
59
+
60
+ class SemanticConvention:
61
+ """
62
+ 定义常用的语义化的属性
63
+ """
64
+
65
+ APP_ID: str = "app_id"
66
+ """
67
+ APP_ID 是应用中的保留字,不允许设置,如果设置,将会在 collector 中被覆盖为当前自定义应用的 ID
68
+ """
69
+
70
+ WORKSPACE_ID: str = "workspace_id"
71
+ """
72
+ WORKSPACE_ID 是工作区的保留字,是 HiAgent 平台内工作区的唯一标识符,如果设置,将会在 collector 中被覆盖为当前自定义应用所在工作区的 ID
73
+ """
74
+
75
+ USER_ID: str = "user_id"
76
+ """
77
+ USER_ID 当前会话的用户 ID
78
+ """
79
+
80
+ TENANT_ID: str = "tenant_id"
81
+ """
82
+ TENANT_ID 是当前会话的租户 ID
83
+ """
84
+
85
+ CONVERSATION_ID: str = "conversation_id"
86
+ """
87
+ Conversation ID 是会话的保留字,是用户在一个有完整上下文的会话唯一标识符
88
+ """
89
+
90
+ MESSAGE_ID: str = "message_id"
91
+ """
92
+ Message ID 是会话中的保留字,是用户在一个问答中的唯一标识符
93
+ """
94
+
95
+ MODEL_ID: str = "model_id"
96
+ """
97
+ Model ID 是在对话中使用的模型的 ID
98
+ """
99
+
100
+ MODEL_NAME: str = "model_name"
101
+ """
102
+ Model Name 是在对话中使用的模型名称
103
+ """
104
+
105
+ MODEL_PROVIDER: str = "model_provider"
106
+ """
107
+ Model Provider 是模型提供者的保留字,是模型提供者的名称
108
+ """
109
+
110
+ NODE_ID: str = "node_id"
111
+ """
112
+ Node ID 如果在工作流中则是工作流的节点 ID
113
+ """
114
+
115
+ WORKFLOW_ID: str = "workflow_id"
116
+ """
117
+ Workflow ID 如果在有工作流中的工作流 ID
118
+ """
119
+
120
+ TOP_P: str = "top_p"
121
+ """
122
+ TOP_P 是模型生成的概率阈值
123
+ """
124
+
125
+ TEMPERATURE: str = "temperature"
126
+ """
127
+ TEMPERATURE 是模型生成的温度阈值
128
+ """
129
+
130
+ TOP_K: str = "top_k"
131
+ """
132
+ TOP_K 是知识库的召回数量阈值
133
+ """
134
+
135
+ STREAM: str = "stream"
136
+ """
137
+ STREAM 是否是流式返回
138
+ """
139
+
140
+ SPAN_TYPE: str = "span_type"
141
+ """
142
+ SPAN_TYPE 是 span 的类型,用于在页面上拆分不同的展示类型
143
+ """
144
+
145
+ REQUEST_ID: str = "request_id"
146
+ """
147
+ Request ID 是请求的唯一标识符
148
+ """
149
+
150
+ LATENCY: str = "latency"
151
+ """
152
+ LATENCY 是请求的延迟时间,毫秒为单位
153
+ """
154
+
155
+ LATENCT_FIRST_RESP: str = "latency_first_resp"
156
+ """
157
+ LATENCT_FIRST_RESP 是首 token 的延迟时间,毫秒为单位
158
+ """
159
+
160
+ INPUT: str = "input"
161
+ """
162
+ INPUT 是用户输入
163
+ """
164
+
165
+ INPUT_RAW: str = "input_raw"
166
+ """
167
+ INPUT_RAW 是用户输入的原始数据
168
+ """
169
+
170
+ INPUT_TOKENS: str = "input_tokens"
171
+ """
172
+ INPUT_TOKENS 是用户输入的 token 数目
173
+ """
174
+
175
+ INPUT_PRICE: str = "input_price"
176
+ """
177
+ INPUT_PRICE 是用户输入的 token 价格
178
+ """
179
+
180
+ CURRENCY: str = "currency"
181
+ """
182
+ CURRENCY 是用户输入的货币类型,可选:RMB,USD
183
+ """
184
+
185
+ OUTPUT: str = "output"
186
+ """
187
+ OUTPUT 是模型生成的结果
188
+ """
189
+
190
+ OUTPUT_RAW: str = "output_raw"
191
+ """
192
+ OUTPUT_RAW 是模型生成的结果的原始数据
193
+ """
194
+
195
+ OUTPUT_TOKENS: str = "output_tokens"
196
+ """
197
+ OUTPUT_TOKENS 是模型生成的结果的 token 数目
198
+ """
199
+
200
+ OUTPUT_PRICE: str = "output_price"
201
+ """
202
+ OUTPUT_PRICE 是模型生成的结果的价格
203
+ """
204
+
205
+ PRICE_UNIT: str = "price_unit"
206
+ """
207
+ PRICE_UNIT 是模型生成的结果的价格单位
208
+ """
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "hiagent-observe"
3
+ version = "0.1.0"
4
+ description = "HiAgent Observe SDK"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "opentelemetry-api>=1.33.1",
9
+ "opentelemetry-exporter-otlp-proto-http>=1.33.1",
10
+ "opentelemetry-sdk>=1.33.1",
11
+ "pydantic>=2.11.5",
12
+ "python-dotenv>=1.1.0",
13
+ "hiagent-api>=0.1.0,<0.2.0",
14
+ "requests==2.32.3",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["."]
23
+
24
+ [tool.hatch.build.targets.sdist]
25
+ exclude = [
26
+ "samples/",
27
+ ]
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "chainlit>=2.2.1",
32
+ ]