ccp-client 1.0.0__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.
ccp_client/__init__.py ADDED
@@ -0,0 +1,413 @@
1
+ """
2
+ CCP Client — Python SDK for CCP Protocol v1.0.0
3
+
4
+ CCP(能力通约协议)的 Python 参考实现。与 JS SDK 功能对等。
5
+
6
+ 功能:
7
+ - 能力发现:search / list / detail
8
+ - 适配器生成:为 5 个框架生成调用代码
9
+ - 遥测闭环:回传调用结果,维护信任向量
10
+ - 信任向量计算:computeWeightedScore 等算法
11
+ - 联邦支持:联邦搜索 + 节点信息
12
+
13
+ 用法:
14
+ from ccp_client import CCPClient
15
+
16
+ client = CCPClient()
17
+ results = client.search("pdf text extraction")
18
+ detail = client.detail("pdf-extract-text-001")
19
+ client.telemetry("pdf-extract-text-001", success=True)
20
+
21
+ 版本:v1.0.0
22
+ 协议:CCP v1.0.0
23
+ 许可:MIT
24
+ """
25
+
26
+ import math
27
+ from typing import Optional, Dict, Any, List
28
+
29
+ __version__ = "1.0.0"
30
+
31
+
32
+ def _normalize_endpoint(endpoint: str) -> str:
33
+ """将端点 URL 规范化:非 ASCII 主机名转为 IDNA(punycode)编码。
34
+
35
+ Python urllib 不支持含非 ASCII 字符的 hostname(会抛 latin-1 编码错误),
36
+ 需在发起请求前把中文域名转换为 punycode 形式(如 礼字号.中国 -> xn--...)。
37
+ """
38
+ import urllib.parse
39
+
40
+ parts = urllib.parse.urlsplit(endpoint)
41
+ hostname = parts.hostname
42
+ if hostname and any(ord(c) > 127 for c in hostname):
43
+ ascii_host = hostname.encode("idna").decode("ascii")
44
+ if ":" in hostname or (parts.port is not None):
45
+ ascii_host = f"{ascii_host}:{parts.port}"
46
+ netloc = ascii_host
47
+ if parts.username:
48
+ userinfo = parts.username
49
+ if parts.password:
50
+ userinfo += f":{parts.password}"
51
+ netloc = f"{userinfo}@{ascii_host}"
52
+ return urllib.parse.urlunsplit(
53
+ (parts.scheme, netloc, parts.path, parts.query, parts.fragment)
54
+ )
55
+ return endpoint
56
+
57
+
58
+ # ========== 信任向量算法(v1.0.0 冻结) ==========
59
+
60
+ def compute_usage_rate(usage: int) -> float:
61
+ """计算使用痕迹率(归一化至 0-1)
62
+
63
+ 公式: min(1, log10(max(usage, 1)) / 5)
64
+ 1 次调用 → 0, 10 次 → 0.2, 100 次 → 0.4, 1000 次 → 0.6, 100000 次 → 1.0
65
+ """
66
+ return min(1.0, math.log10(max(usage, 1)) / 5.0)
67
+
68
+
69
+ def compute_success(old_success: float, old_evidence: int, success: bool) -> float:
70
+ """计算成功率(滚动平均)
71
+
72
+ 公式: newSuccess = (oldSuccess * oldEvidence + newSuccessValue) / (oldEvidence + 1)
73
+ """
74
+ if old_evidence > 0:
75
+ return (old_success * old_evidence + (1.0 if success else 0.0)) / (old_evidence + 1)
76
+ return 1.0 if success else 0.5
77
+
78
+
79
+ def compute_uncertainty(evidence_count: int) -> float:
80
+ """计算不确定性(基于 Josang 主观逻辑)
81
+
82
+ 公式: u = 1 / (1 + log10(max(count, 1)))
83
+ 0 次证据 → 1.0, 10 次 → 0.5, 100 次 → 0.33, 1000 次 → 0.25
84
+ """
85
+ return 1.0 / (1.0 + math.log10(max(evidence_count, 1)))
86
+
87
+
88
+ def compute_time_score(old_time: float) -> float:
89
+ """计算时效性(衰减函数)
90
+
91
+ 公式: newTime = min(1, oldTime * 0.95 + 0.05)
92
+ """
93
+ return min(1.0, (old_time or 0.5) * 0.95 + 0.05)
94
+
95
+
96
+ def compute_weighted_score(
97
+ trust: Dict[str, Any],
98
+ uncertainty: float,
99
+ weights: Optional[Dict[str, float]] = None,
100
+ ) -> float:
101
+ """计算加权综合信任分
102
+
103
+ 默认权重(SPEC v1.0.0): source=25%, usage=15%, success=30%, risk=15%, time=15%
104
+ 结果受不确定性惩罚: finalScore = weightedScore * (1 - uncertainty * 0.5)
105
+
106
+ Args:
107
+ trust: 信任向量字典,包含 trustSource/trustUsage/trustUsageRate/trustSuccess/trustRisk/trustTime
108
+ uncertainty: 不确定性(0-1)
109
+ weights: 可选权重配置
110
+
111
+ Returns:
112
+ 0-1 之间的综合信任分
113
+ """
114
+ if weights is None:
115
+ weights = {}
116
+
117
+ w = {
118
+ "source": weights.get("source", 0.25),
119
+ "usage": weights.get("usage", 0.15),
120
+ "success": weights.get("success", 0.30),
121
+ "risk": weights.get("risk", 0.15),
122
+ "time": weights.get("time", 0.15),
123
+ }
124
+
125
+ usage_rate = trust.get("trustUsageRate") or compute_usage_rate(trust.get("trustUsage", 0))
126
+
127
+ weighted = (
128
+ w["source"] * (trust.get("trustSource", 0) or 0)
129
+ + w["usage"] * usage_rate
130
+ + w["success"] * (trust.get("trustSuccess", 0) or 0)
131
+ + w["risk"] * (1.0 - (trust.get("trustRisk", 0) or 0))
132
+ + w["time"] * (trust.get("trustTime", 0) or 0)
133
+ )
134
+
135
+ return weighted * (1.0 - (uncertainty if uncertainty is not None else 0.5) * 0.5)
136
+
137
+
138
+ # ========== CCP API 客户端 ==========
139
+
140
+ class CCPClient:
141
+ """CCP API HTTP 客户端
142
+
143
+ Args:
144
+ endpoint: CCP API 端点 URL(默认使用 SkillMesh 主节点)
145
+ timeout: 请求超时秒数(默认 10)
146
+ """
147
+
148
+ DEFAULT_ENDPOINT = "https://skillmesh.礼字号.中国/api/ccp/v1"
149
+
150
+ def __init__(self, endpoint: Optional[str] = None, timeout: int = 10):
151
+ import urllib.request
152
+ import urllib.error
153
+
154
+ self.endpoint = _normalize_endpoint(endpoint or self.DEFAULT_ENDPOINT).rstrip("/")
155
+ self.timeout = timeout
156
+
157
+ def _request(
158
+ self,
159
+ path: str,
160
+ method: str = "GET",
161
+ body: Optional[Dict[str, Any]] = None,
162
+ ) -> Dict[str, Any]:
163
+ """发送 HTTP 请求"""
164
+ import urllib.request
165
+ import urllib.error
166
+ import json
167
+
168
+ url = f"{self.endpoint}{path}"
169
+ data = json.dumps(body).encode("utf-8") if body else None
170
+
171
+ req = urllib.request.Request(
172
+ url,
173
+ data=data,
174
+ headers={
175
+ "Content-Type": "application/json",
176
+ "Accept": "application/json",
177
+ "User-Agent": f"ccp-client-python/{__version__}",
178
+ },
179
+ method=method,
180
+ )
181
+
182
+ try:
183
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
184
+ return json.loads(resp.read().decode("utf-8"))
185
+ except urllib.error.HTTPError as e:
186
+ return {
187
+ "error": {
188
+ "code": "HTTP_ERROR",
189
+ "message": str(e),
190
+ "status": e.code,
191
+ }
192
+ }
193
+ except Exception as e:
194
+ return {
195
+ "error": {
196
+ "code": "NETWORK_ERROR",
197
+ "message": str(e),
198
+ }
199
+ }
200
+
201
+ def search(
202
+ self,
203
+ q: str,
204
+ category: Optional[str] = None,
205
+ federated: bool = False,
206
+ ) -> Dict[str, Any]:
207
+ """搜索能力锚点
208
+
209
+ Args:
210
+ q: 搜索关键词(中文或英文)
211
+ category: 能力分类过滤(ai/data/media/language/dev)
212
+ federated: 是否启用联邦搜索
213
+
214
+ Returns:
215
+ 包含 results 列表和 count 的字典
216
+ """
217
+ import urllib.parse
218
+
219
+ params = f"?q={urllib.parse.quote(q)}"
220
+ if category:
221
+ params += f"&category={urllib.parse.quote(category)}"
222
+ if federated:
223
+ params += "&federated=true"
224
+ return self._request(f"/search{params}")
225
+
226
+ def detail(self, capability_id: str) -> Dict[str, Any]:
227
+ """获取能力锚点详情
228
+
229
+ Args:
230
+ capability_id: 能力锚点 ID(如 pdf-extract-text-001)
231
+
232
+ Returns:
233
+ 完整的能力锚点对象
234
+ """
235
+ import urllib.parse
236
+
237
+ return self._request(
238
+ f"/capabilities/{urllib.parse.quote(capability_id)}"
239
+ )
240
+
241
+ def list_capabilities(
242
+ self, category: Optional[str] = None
243
+ ) -> Dict[str, Any]:
244
+ """列出所有能力锚点
245
+
246
+ Args:
247
+ category: 能力分类过滤
248
+
249
+ Returns:
250
+ 包含 capabilities 列表的字典
251
+ """
252
+ params = ""
253
+ if category:
254
+ import urllib.parse
255
+ params = f"?category={urllib.parse.quote(category)}"
256
+ return self._request(f"/capabilities{params}")
257
+
258
+ def adapter(
259
+ self, capability_id: str, framework: str = "dsh"
260
+ ) -> Dict[str, Any]:
261
+ """生成适配器代码(服务端返回 YAML 文本)
262
+
263
+ Args:
264
+ capability_id: 能力锚点 ID
265
+ framework: 目标框架(dsh/mcp/langchain/crewai/dify)
266
+
267
+ Returns:
268
+ 包含适配器 YAML 文本的字典:{"adapter": "...", "framework": "..."}
269
+ """
270
+ import urllib.request
271
+ import urllib.error
272
+ import urllib.parse
273
+
274
+ url = (
275
+ f"{self.endpoint}/capabilities/{urllib.parse.quote(capability_id)}"
276
+ f"/adapters/{urllib.parse.quote(framework)}"
277
+ )
278
+ req = urllib.request.Request(
279
+ url,
280
+ headers={
281
+ "Accept": "text/yaml,text/plain,*/*",
282
+ "User-Agent": f"ccp-client-python/{__version__}",
283
+ },
284
+ method="GET",
285
+ )
286
+ try:
287
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
288
+ return {"adapter": resp.read().decode("utf-8"), "framework": framework}
289
+ except urllib.error.HTTPError as e:
290
+ return {
291
+ "error": {
292
+ "code": "HTTP_ERROR",
293
+ "message": str(e),
294
+ "status": e.code,
295
+ }
296
+ }
297
+ except Exception as e:
298
+ return {
299
+ "error": {
300
+ "code": "NETWORK_ERROR",
301
+ "message": str(e),
302
+ }
303
+ }
304
+
305
+ def telemetry(
306
+ self,
307
+ capability_id: str,
308
+ success: bool,
309
+ latency_ms: int = 0,
310
+ ) -> Dict[str, Any]:
311
+ """回传调用结果到遥测端点
312
+
313
+ Args:
314
+ capability_id: 能力锚点 ID
315
+ success: 调用是否成功
316
+ latency_ms: 调用延迟(毫秒)
317
+
318
+ Returns:
319
+ 确认信息
320
+ """
321
+ return self._request(
322
+ "/telemetry",
323
+ method="POST",
324
+ body={
325
+ "capability_id": capability_id,
326
+ "success": success,
327
+ "latency_ms": latency_ms,
328
+ "agent_id": "ccp-client-python",
329
+ },
330
+ )
331
+
332
+ def node_info(self) -> Dict[str, Any]:
333
+ """获取节点信息"""
334
+ return self._request("/node")
335
+
336
+ def contribute(self, capability: Dict[str, Any]) -> Dict[str, Any]:
337
+ """贡献新的能力锚点
338
+
339
+ Args:
340
+ capability: 能力锚点对象(需符合 CCP Schema)
341
+
342
+ Returns:
343
+ 确认信息
344
+ """
345
+ return self._request(
346
+ "/contribute",
347
+ method="POST",
348
+ body=capability,
349
+ )
350
+
351
+
352
+ # ========== 便捷函数 ==========
353
+
354
+ def search(
355
+ q: str,
356
+ endpoint: Optional[str] = None,
357
+ category: Optional[str] = None,
358
+ federated: bool = False,
359
+ ) -> Dict[str, Any]:
360
+ """搜索能力锚点(便捷函数)"""
361
+ client = CCPClient(endpoint=endpoint)
362
+ return client.search(q, category=category, federated=federated)
363
+
364
+
365
+ def detail(
366
+ capability_id: str, endpoint: Optional[str] = None
367
+ ) -> Dict[str, Any]:
368
+ """获取能力锚点详情(便捷函数)"""
369
+ client = CCPClient(endpoint=endpoint)
370
+ return client.detail(capability_id)
371
+
372
+
373
+ def get_trust_summary(capability: Dict[str, Any]) -> Dict[str, Any]:
374
+ """从能力锚点中提取信任向量摘要
375
+
376
+ Args:
377
+ capability: 能力锚点对象
378
+
379
+ Returns:
380
+ 信任向量摘要,包含五维 + 证据层 + 综合评分
381
+ """
382
+ trust = {
383
+ "source": capability.get("trustSource"),
384
+ "usage": capability.get("trustUsage", 0),
385
+ "usage_rate": capability.get("trustUsageRate"),
386
+ "success": capability.get("trustSuccess"),
387
+ "risk": capability.get("trustRisk"),
388
+ "time": capability.get("trustTime"),
389
+ }
390
+
391
+ evidence = capability.get("evidence", {})
392
+ uncertainty = evidence.get("uncertainty") or compute_uncertainty(
393
+ evidence.get("count", 0)
394
+ )
395
+ weighted = compute_weighted_score(trust, uncertainty)
396
+
397
+ return {
398
+ "dimensions": trust,
399
+ "evidence": {
400
+ "count": evidence.get("count", 0),
401
+ "uncertainty": uncertainty,
402
+ },
403
+ "weighted_score": weighted,
404
+ "verdict": (
405
+ "highly_trusted"
406
+ if weighted > 0.8
407
+ else "trusted"
408
+ if weighted > 0.6
409
+ else "uncertain"
410
+ if weighted > 0.4
411
+ else "low_trust"
412
+ ),
413
+ }
ccp_client/py.typed ADDED
File without changes
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccp-client
3
+ Version: 1.0.0
4
+ Summary: CCP 协议 Python 客户端 — 能力通约协议的 Python 参考实现
5
+ Author-email: CCP Community <ccp@skillmesh.礼字号.中国>
6
+ License: MIT
7
+ Project-URL: Homepage, https://skillmesh.礼字号.中国
8
+ Project-URL: Repository, https://github.com/aidulibrary/skillmesh-index
9
+ Project-URL: CCP Spec, https://github.com/aidulibrary/skillmesh-index/tree/main/ccp-spec
10
+ Keywords: ccp,skillmesh,capability,discovery,trust-vector,agent
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: urllib3>=2.0
22
+
23
+ # CCP Client — Python SDK
24
+
25
+ CCP 协议(能力通约协议)的 Python 参考实现。与 JS SDK (`@skillmesh-ccp/ccp-client`) 功能对等。
26
+
27
+ ## 安装
28
+
29
+ ```bash
30
+ pip install ccp-client
31
+ ```
32
+
33
+ ## 快速开始
34
+
35
+ ```python
36
+ from ccp_client import CCPClient, search, get_trust_summary
37
+
38
+ # 创建客户端
39
+ client = CCPClient()
40
+
41
+ # 搜索能力
42
+ results = client.search("pdf text extraction")
43
+ for cap in results["results"]:
44
+ summary = get_trust_summary(cap)
45
+ print(f"{cap['name']} — {summary['verdict']} ({summary['weighted_score']:.2f})")
46
+
47
+ # 获取详情
48
+ detail = client.detail("pdf-extract-text-001")
49
+
50
+ # 生成适配器
51
+ adapter = client.adapter("pdf-extract-text-001", framework="dsh")
52
+
53
+ # 回传遥测
54
+ client.telemetry("pdf-extract-text-001", success=True, latency_ms=120)
55
+ ```
56
+
57
+ ## API 参考
58
+
59
+ | 方法 | 说明 |
60
+ | ------------------------------------- | -------------- |
61
+ | `search(q, category?, federated?)` | 搜索能力锚点 |
62
+ | `detail(id)` | 获取能力详情 |
63
+ | `list_capabilities(category?)` | 列出所有能力 |
64
+ | `adapter(id, framework)` | 生成适配器代码 |
65
+ | `telemetry(id, success, latency_ms?)` | 回传调用结果 |
66
+ | `node_info()` | 获取节点信息 |
67
+ | `contribute(capability)` | 贡献新能力 |
68
+
69
+ ## 信任向量
70
+
71
+ ```python
72
+ from ccp_client import compute_weighted_score, compute_uncertainty
73
+
74
+ trust = {
75
+ "trustSource": 0.95,
76
+ "trustUsage": 1200,
77
+ "trustSuccess": 0.97,
78
+ "trustRisk": 0.05,
79
+ "trustTime": 0.95,
80
+ }
81
+
82
+ uncertainty = compute_uncertainty(1200)
83
+ score = compute_weighted_score(trust, uncertainty)
84
+ print(f"信任分: {score:.2f}") # 信任分: 0.85
85
+ ```
86
+
87
+ ## 许可
88
+
89
+ MIT
@@ -0,0 +1,6 @@
1
+ ccp_client/__init__.py,sha256=FZRQgtQ62VPPiVpITGnu7ok3csMUuVaAERmCQGQ-mxg,12357
2
+ ccp_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ ccp_client-1.0.0.dist-info/METADATA,sha256=OJ1GRK5sSqjkc_dWGaD56KyN0t6bJCElBU5DhfQg6ic,2841
4
+ ccp_client-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
5
+ ccp_client-1.0.0.dist-info/top_level.txt,sha256=sOkLzsWqOAo4uX_cyBkMKCoi7Y9bJYvKvAG0_a-nDy4,11
6
+ ccp_client-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ccp_client