agent0-sdk 0.2.2__py3-none-any.whl → 0.5__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.
- agent0_sdk/__init__.py +1 -1
- agent0_sdk/core/agent.py +303 -29
- agent0_sdk/core/contracts.py +93 -51
- agent0_sdk/core/feedback_manager.py +191 -162
- agent0_sdk/core/indexer.py +787 -37
- agent0_sdk/core/models.py +10 -21
- agent0_sdk/core/oasf_validator.py +98 -0
- agent0_sdk/core/sdk.py +238 -20
- agent0_sdk/core/subgraph_client.py +56 -17
- agent0_sdk/core/web3_client.py +184 -17
- agent0_sdk/taxonomies/all_domains.json +1565 -0
- agent0_sdk/taxonomies/all_skills.json +1030 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/METADATA +78 -6
- agent0_sdk-0.5.dist-info/RECORD +19 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/top_level.txt +0 -1
- agent0_sdk-0.2.2.dist-info/RECORD +0 -27
- tests/__init__.py +0 -1
- tests/config.py +0 -46
- tests/conftest.py +0 -22
- tests/test_feedback.py +0 -417
- tests/test_models.py +0 -224
- tests/test_real_public_servers.py +0 -103
- tests/test_registration.py +0 -267
- tests/test_registrationIpfs.py +0 -227
- tests/test_sdk.py +0 -240
- tests/test_search.py +0 -415
- tests/test_transfer.py +0 -255
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/WHEEL +0 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -40,60 +40,6 @@ class FeedbackManager:
|
|
|
40
40
|
self.subgraph_client = subgraph_client
|
|
41
41
|
self.indexer = indexer
|
|
42
42
|
|
|
43
|
-
def signFeedbackAuth(
|
|
44
|
-
self,
|
|
45
|
-
agentId: AgentId,
|
|
46
|
-
clientAddress: Address,
|
|
47
|
-
indexLimit: Optional[int] = None,
|
|
48
|
-
expiryHours: int = 24,
|
|
49
|
-
) -> bytes:
|
|
50
|
-
"""Sign feedback authorization for a client."""
|
|
51
|
-
# Parse agent ID to get token ID
|
|
52
|
-
if ":" in agentId:
|
|
53
|
-
tokenId = int(agentId.split(":")[-1])
|
|
54
|
-
else:
|
|
55
|
-
tokenId = int(agentId)
|
|
56
|
-
|
|
57
|
-
# Get current feedback index if not provided
|
|
58
|
-
if indexLimit is None:
|
|
59
|
-
try:
|
|
60
|
-
lastIndex = self.web3_client.call_contract(
|
|
61
|
-
self.reputation_registry,
|
|
62
|
-
"getLastIndex",
|
|
63
|
-
tokenId,
|
|
64
|
-
clientAddress
|
|
65
|
-
)
|
|
66
|
-
indexLimit = lastIndex + 1
|
|
67
|
-
except Exception as e:
|
|
68
|
-
# If we can't get the index, default to 1 (for first feedback)
|
|
69
|
-
indexLimit = 1
|
|
70
|
-
|
|
71
|
-
# Calculate expiry timestamp
|
|
72
|
-
expiry = int(time.time()) + (expiryHours * 3600)
|
|
73
|
-
|
|
74
|
-
# Encode feedback auth data
|
|
75
|
-
authData = self.web3_client.encodeFeedbackAuth(
|
|
76
|
-
agentId=tokenId,
|
|
77
|
-
clientAddress=clientAddress,
|
|
78
|
-
indexLimit=indexLimit,
|
|
79
|
-
expiry=expiry,
|
|
80
|
-
chainId=self.web3_client.chain_id,
|
|
81
|
-
identityRegistry=self.identity_registry.address if self.identity_registry else "0x0",
|
|
82
|
-
signerAddress=self.web3_client.account.address
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
# Hash the encoded data first (matching contract's keccak256(abi.encode(...)))
|
|
86
|
-
messageHash = self.web3_client.w3.keccak(authData)
|
|
87
|
-
|
|
88
|
-
# Sign the hash with Ethereum signed message prefix (matching contract's .toEthSignedMessageHash())
|
|
89
|
-
from eth_account.messages import encode_defunct
|
|
90
|
-
signableMessage = encode_defunct(primitive=messageHash)
|
|
91
|
-
signedMessage = self.web3_client.account.sign_message(signableMessage)
|
|
92
|
-
signature = signedMessage.signature
|
|
93
|
-
|
|
94
|
-
# Combine auth data and signature
|
|
95
|
-
return authData + signature
|
|
96
|
-
|
|
97
43
|
def prepareFeedback(
|
|
98
44
|
self,
|
|
99
45
|
agentId: AgentId,
|
|
@@ -104,6 +50,8 @@ class FeedbackManager:
|
|
|
104
50
|
name: Optional[str] = None,
|
|
105
51
|
skill: Optional[str] = None,
|
|
106
52
|
task: Optional[str] = None,
|
|
53
|
+
endpoint: Optional[str] = None,
|
|
54
|
+
domain: Optional[str] = None,
|
|
107
55
|
context: Optional[Dict[str, Any]] = None,
|
|
108
56
|
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
109
57
|
extra: Optional[Dict[str, Any]] = None,
|
|
@@ -128,12 +76,13 @@ class FeedbackManager:
|
|
|
128
76
|
"agentId": tokenId,
|
|
129
77
|
"clientAddress": f"eip155:{self.web3_client.chain_id}:{self.web3_client.account.address}",
|
|
130
78
|
"createdAt": createdAt,
|
|
131
|
-
"feedbackAuth": "", # Will be filled when giving feedback
|
|
132
79
|
"score": int(score) if score else 0, # Score as integer (0-100)
|
|
133
80
|
|
|
134
81
|
# MAY FIELDS
|
|
135
82
|
"tag1": tags[0] if tags else None,
|
|
136
83
|
"tag2": tags[1] if len(tags) > 1 else None,
|
|
84
|
+
"endpoint": endpoint,
|
|
85
|
+
"domain": domain,
|
|
137
86
|
"skill": skill,
|
|
138
87
|
"context": context,
|
|
139
88
|
"task": task,
|
|
@@ -155,7 +104,6 @@ class FeedbackManager:
|
|
|
155
104
|
agentId: AgentId,
|
|
156
105
|
feedbackFile: Dict[str, Any],
|
|
157
106
|
idem: Optional[IdemKey] = None,
|
|
158
|
-
feedbackAuth: Optional[bytes] = None,
|
|
159
107
|
) -> Feedback:
|
|
160
108
|
"""Give feedback (maps 8004 endpoint)."""
|
|
161
109
|
# Parse agent ID
|
|
@@ -180,22 +128,11 @@ class FeedbackManager:
|
|
|
180
128
|
except Exception as e:
|
|
181
129
|
raise ValueError(f"Failed to get feedback index: {e}")
|
|
182
130
|
|
|
183
|
-
# Prepare
|
|
184
|
-
if feedbackAuth is None:
|
|
185
|
-
feedbackAuth = self.sign_feedbackAuth(
|
|
186
|
-
agentId=agentId,
|
|
187
|
-
clientAddress=clientAddress,
|
|
188
|
-
indexLimit=feedbackIndex,
|
|
189
|
-
expiryHours=24
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
# Update feedback file with auth
|
|
193
|
-
feedbackFile["feedbackAuth"] = feedbackAuth.hex()
|
|
194
|
-
|
|
195
|
-
# Prepare on-chain data (only basic fields, no capability/endpoint)
|
|
131
|
+
# Prepare on-chain data
|
|
196
132
|
score = feedbackFile.get("score", 0) # Already in 0-100 range
|
|
197
|
-
tag1 =
|
|
198
|
-
tag2 =
|
|
133
|
+
tag1 = feedbackFile.get("tag1", "") or ""
|
|
134
|
+
tag2 = feedbackFile.get("tag2", "") or ""
|
|
135
|
+
endpoint = feedbackFile.get("endpoint", "") or ""
|
|
199
136
|
|
|
200
137
|
# Handle off-chain file storage
|
|
201
138
|
feedbackUri = ""
|
|
@@ -216,7 +153,7 @@ class FeedbackManager:
|
|
|
216
153
|
# If we have rich data but no IPFS, we need to store it somewhere
|
|
217
154
|
raise ValueError("Rich feedback data requires IPFS client for storage")
|
|
218
155
|
|
|
219
|
-
# Submit to blockchain
|
|
156
|
+
# Submit to blockchain with new signature: giveFeedback(agentId, score, tag1, tag2, endpoint, feedbackURI, feedbackHash)
|
|
220
157
|
try:
|
|
221
158
|
txHash = self.web3_client.transact_contract(
|
|
222
159
|
self.reputation_registry,
|
|
@@ -225,9 +162,9 @@ class FeedbackManager:
|
|
|
225
162
|
score,
|
|
226
163
|
tag1,
|
|
227
164
|
tag2,
|
|
165
|
+
endpoint,
|
|
228
166
|
feedbackUri,
|
|
229
|
-
feedbackHash
|
|
230
|
-
feedbackAuth
|
|
167
|
+
feedbackHash
|
|
231
168
|
)
|
|
232
169
|
|
|
233
170
|
# Wait for transaction confirmation
|
|
@@ -244,11 +181,12 @@ class FeedbackManager:
|
|
|
244
181
|
agentId=agentId,
|
|
245
182
|
reviewer=clientAddress, # create_id normalizes the ID; reviewer field can remain as-is
|
|
246
183
|
score=int(score) if score and score > 0 else None,
|
|
247
|
-
tags=[
|
|
184
|
+
tags=[tag1, tag2] if tag1 or tag2 else [],
|
|
248
185
|
text=feedbackFile.get("text"),
|
|
249
186
|
context=feedbackFile.get("context"),
|
|
250
187
|
proofOfPayment=feedbackFile.get("proofOfPayment"),
|
|
251
188
|
fileURI=feedbackUri if feedbackUri else None,
|
|
189
|
+
endpoint=endpoint if endpoint else None,
|
|
252
190
|
createdAt=int(time.time()),
|
|
253
191
|
isRevoked=False,
|
|
254
192
|
# Off-chain only fields
|
|
@@ -265,17 +203,21 @@ class FeedbackManager:
|
|
|
265
203
|
feedbackIndex: int,
|
|
266
204
|
) -> Feedback:
|
|
267
205
|
"""Get single feedback with responses from subgraph or blockchain."""
|
|
268
|
-
#
|
|
206
|
+
# Prefer subgraph/indexer for richer data, but fall back to chain when subgraph is behind
|
|
269
207
|
if self.indexer and self.subgraph_client:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
208
|
+
try:
|
|
209
|
+
return self.indexer.get_feedback(agentId, clientAddress, feedbackIndex)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.debug(f"Indexer/subgraph get_feedback failed, falling back to blockchain: {e}")
|
|
212
|
+
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
213
|
+
|
|
275
214
|
if self.subgraph_client:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
215
|
+
try:
|
|
216
|
+
return self._get_feedback_from_subgraph(agentId, clientAddress, feedbackIndex)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.debug(f"Subgraph get feedback failed, falling back to blockchain: {e}")
|
|
219
|
+
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
220
|
+
|
|
279
221
|
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
280
222
|
|
|
281
223
|
def _get_feedback_from_subgraph(
|
|
@@ -320,24 +262,14 @@ class FeedbackManager:
|
|
|
320
262
|
'createdAt': resp.get('createdAt')
|
|
321
263
|
})
|
|
322
264
|
|
|
323
|
-
# Map tags
|
|
324
|
-
tags = []
|
|
265
|
+
# Map tags: rely on whatever the subgraph returns (may be legacy bytes/hash-like values)
|
|
266
|
+
tags: List[str] = []
|
|
325
267
|
tag1 = feedback_data.get('tag1') or feedback_file.get('tag1')
|
|
326
268
|
tag2 = feedback_data.get('tag2') or feedback_file.get('tag2')
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if
|
|
330
|
-
tags
|
|
331
|
-
tag1 if isinstance(tag1, str) else "",
|
|
332
|
-
tag2 if isinstance(tag2, str) else ""
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
# If conversion failed, try as plain strings
|
|
336
|
-
if not tags:
|
|
337
|
-
if tag1 and not tag1.startswith("0x"):
|
|
338
|
-
tags.append(tag1)
|
|
339
|
-
if tag2 and not tag2.startswith("0x"):
|
|
340
|
-
tags.append(tag2)
|
|
269
|
+
if isinstance(tag1, str) and tag1:
|
|
270
|
+
tags.append(tag1)
|
|
271
|
+
if isinstance(tag2, str) and tag2:
|
|
272
|
+
tags.append(tag2)
|
|
341
273
|
|
|
342
274
|
return Feedback(
|
|
343
275
|
id=Feedback.create_id(agentId, clientAddress, feedbackIndex), # create_id now normalizes
|
|
@@ -354,7 +286,8 @@ class FeedbackManager:
|
|
|
354
286
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
355
287
|
'txHash': feedback_file.get('proofOfPaymentTxHash'),
|
|
356
288
|
} if feedback_file.get('proofOfPaymentFromAddress') else None,
|
|
357
|
-
fileURI=feedback_data.get('feedbackUri'),
|
|
289
|
+
fileURI=feedback_data.get('feedbackURI') or feedback_data.get('feedbackUri'), # Handle both old and new field names
|
|
290
|
+
endpoint=feedback_data.get('endpoint'),
|
|
358
291
|
createdAt=feedback_data.get('createdAt', int(time.time())),
|
|
359
292
|
answers=answers,
|
|
360
293
|
isRevoked=feedback_data.get('isRevoked', False),
|
|
@@ -380,7 +313,7 @@ class FeedbackManager:
|
|
|
380
313
|
tokenId = int(agentId)
|
|
381
314
|
|
|
382
315
|
try:
|
|
383
|
-
# Read from blockchain
|
|
316
|
+
# Read from blockchain - new signature: readFeedback(agentId, clientAddress, feedbackIndex)
|
|
384
317
|
result = self.web3_client.call_contract(
|
|
385
318
|
self.reputation_registry,
|
|
386
319
|
"readFeedback",
|
|
@@ -395,17 +328,25 @@ class FeedbackManager:
|
|
|
395
328
|
normalized_address = self.web3_client.normalize_address(clientAddress)
|
|
396
329
|
feedbackId = Feedback.create_id(agentId, normalized_address, feedbackIndex)
|
|
397
330
|
|
|
331
|
+
# Tags are now strings, not bytes32
|
|
332
|
+
tags = []
|
|
333
|
+
if tag1:
|
|
334
|
+
tags.append(tag1)
|
|
335
|
+
if tag2:
|
|
336
|
+
tags.append(tag2)
|
|
337
|
+
|
|
398
338
|
return Feedback(
|
|
399
339
|
id=feedbackId,
|
|
400
340
|
agentId=agentId,
|
|
401
341
|
reviewer=normalized_address,
|
|
402
342
|
score=int(score) if score and score > 0 else None,
|
|
403
|
-
tags=
|
|
343
|
+
tags=tags,
|
|
404
344
|
text=None, # Not stored on-chain
|
|
405
345
|
capability=None, # Not stored on-chain
|
|
406
346
|
context=None, # Not stored on-chain
|
|
407
347
|
proofOfPayment=None, # Not stored on-chain
|
|
408
348
|
fileURI=None, # Would need to be retrieved separately
|
|
349
|
+
endpoint=None, # Not stored on-chain in readFeedback
|
|
409
350
|
createdAt=int(time.time()), # Not stored on-chain
|
|
410
351
|
isRevoked=is_revoked
|
|
411
352
|
)
|
|
@@ -453,12 +394,12 @@ class FeedbackManager:
|
|
|
453
394
|
tokenId = int(agentId)
|
|
454
395
|
|
|
455
396
|
try:
|
|
456
|
-
# Prepare filter parameters
|
|
397
|
+
# Prepare filter parameters - tags are now strings
|
|
457
398
|
client_list = clientAddresses if clientAddresses else []
|
|
458
|
-
tag1_filter =
|
|
459
|
-
tag2_filter =
|
|
399
|
+
tag1_filter = tags[0] if tags else ""
|
|
400
|
+
tag2_filter = tags[1] if tags and len(tags) > 1 else ""
|
|
460
401
|
|
|
461
|
-
# Read from blockchain
|
|
402
|
+
# Read from blockchain - new signature returns: (clientAddresses, feedbackIndexes, scores, tag1s, tag2s, revokedStatuses)
|
|
462
403
|
result = self.web3_client.call_contract(
|
|
463
404
|
self.reputation_registry,
|
|
464
405
|
"readAllFeedback",
|
|
@@ -469,19 +410,27 @@ class FeedbackManager:
|
|
|
469
410
|
include_revoked
|
|
470
411
|
)
|
|
471
412
|
|
|
472
|
-
clients, scores, tag1s, tag2s, revoked_statuses = result
|
|
413
|
+
clients, feedback_indexes, scores, tag1s, tag2s, revoked_statuses = result
|
|
473
414
|
|
|
474
415
|
# Convert to Feedback objects
|
|
475
416
|
feedbacks = []
|
|
476
417
|
for i in range(len(clients)):
|
|
477
|
-
|
|
418
|
+
feedback_index = int(feedback_indexes[i]) if i < len(feedback_indexes) else (i + 1)
|
|
419
|
+
feedbackId = Feedback.create_id(agentId, clients[i], feedback_index)
|
|
420
|
+
|
|
421
|
+
# Tags are now strings
|
|
422
|
+
tags_list = []
|
|
423
|
+
if i < len(tag1s) and tag1s[i]:
|
|
424
|
+
tags_list.append(tag1s[i])
|
|
425
|
+
if i < len(tag2s) and tag2s[i]:
|
|
426
|
+
tags_list.append(tag2s[i])
|
|
478
427
|
|
|
479
428
|
feedback = Feedback(
|
|
480
429
|
id=feedbackId,
|
|
481
430
|
agentId=agentId,
|
|
482
431
|
reviewer=clients[i],
|
|
483
432
|
score=int(scores[i]) if scores[i] and scores[i] > 0 else None,
|
|
484
|
-
tags=
|
|
433
|
+
tags=tags_list,
|
|
485
434
|
text=None,
|
|
486
435
|
capability=None,
|
|
487
436
|
endpoint=None,
|
|
@@ -489,7 +438,7 @@ class FeedbackManager:
|
|
|
489
438
|
proofOfPayment=None,
|
|
490
439
|
fileURI=None,
|
|
491
440
|
createdAt=int(time.time()),
|
|
492
|
-
isRevoked=revoked_statuses[i]
|
|
441
|
+
isRevoked=revoked_statuses[i] if i < len(revoked_statuses) else False
|
|
493
442
|
)
|
|
494
443
|
feedbacks.append(feedback)
|
|
495
444
|
|
|
@@ -555,24 +504,14 @@ class FeedbackManager:
|
|
|
555
504
|
'createdAt': resp.get('createdAt')
|
|
556
505
|
})
|
|
557
506
|
|
|
558
|
-
# Map tags
|
|
559
|
-
tags_list = []
|
|
507
|
+
# Map tags: rely on whatever the subgraph returns (may be legacy bytes/hash-like values)
|
|
508
|
+
tags_list: List[str] = []
|
|
560
509
|
tag1 = fb_data.get('tag1') or feedback_file.get('tag1')
|
|
561
510
|
tag2 = fb_data.get('tag2') or feedback_file.get('tag2')
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if
|
|
565
|
-
tags_list
|
|
566
|
-
tag1 if isinstance(tag1, str) else "",
|
|
567
|
-
tag2 if isinstance(tag2, str) else ""
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
# If conversion failed, try as plain strings
|
|
571
|
-
if not tags_list:
|
|
572
|
-
if tag1 and not tag1.startswith("0x"):
|
|
573
|
-
tags_list.append(tag1)
|
|
574
|
-
if tag2 and not tag2.startswith("0x"):
|
|
575
|
-
tags_list.append(tag2)
|
|
511
|
+
if isinstance(tag1, str) and tag1:
|
|
512
|
+
tags_list.append(tag1)
|
|
513
|
+
if isinstance(tag2, str) and tag2:
|
|
514
|
+
tags_list.append(tag2)
|
|
576
515
|
|
|
577
516
|
# Parse agentId from feedback ID
|
|
578
517
|
feedback_id = fb_data['id']
|
|
@@ -601,7 +540,8 @@ class FeedbackManager:
|
|
|
601
540
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
602
541
|
'txHash': feedback_file.get('proofOfPaymentTxHash'),
|
|
603
542
|
} if feedback_file.get('proofOfPaymentFromAddress') else None,
|
|
604
|
-
fileURI=fb_data.get('feedbackUri'),
|
|
543
|
+
fileURI=fb_data.get('feedbackURI') or fb_data.get('feedbackUri'), # Handle both old and new field names
|
|
544
|
+
endpoint=fb_data.get('endpoint'),
|
|
605
545
|
createdAt=fb_data.get('createdAt', int(time.time())),
|
|
606
546
|
answers=answers,
|
|
607
547
|
isRevoked=fb_data.get('isRevoked', False),
|
|
@@ -682,7 +622,7 @@ class FeedbackManager:
|
|
|
682
622
|
tokenId,
|
|
683
623
|
clientAddress,
|
|
684
624
|
feedbackIndex,
|
|
685
|
-
responseUri,
|
|
625
|
+
responseUri, # Note: contract uses responseURI but variable name kept for compatibility
|
|
686
626
|
responseHash
|
|
687
627
|
)
|
|
688
628
|
|
|
@@ -703,7 +643,46 @@ class FeedbackManager:
|
|
|
703
643
|
groupBy: Optional[List[str]] = None,
|
|
704
644
|
) -> Dict[str, Any]:
|
|
705
645
|
"""Get reputation summary for an agent with optional grouping."""
|
|
706
|
-
# Parse
|
|
646
|
+
# Parse chainId from agentId
|
|
647
|
+
chain_id = None
|
|
648
|
+
if ":" in agentId:
|
|
649
|
+
try:
|
|
650
|
+
chain_id = int(agentId.split(":", 1)[0])
|
|
651
|
+
except ValueError:
|
|
652
|
+
chain_id = None
|
|
653
|
+
|
|
654
|
+
# Try subgraph first (if available and indexer supports it)
|
|
655
|
+
if self.indexer and self.subgraph_client:
|
|
656
|
+
# Get correct subgraph client for the chain
|
|
657
|
+
subgraph_client = None
|
|
658
|
+
full_agent_id = agentId
|
|
659
|
+
|
|
660
|
+
if chain_id is not None:
|
|
661
|
+
subgraph_client = self.indexer._get_subgraph_client_for_chain(chain_id)
|
|
662
|
+
else:
|
|
663
|
+
# No chainId in agentId, use SDK's default
|
|
664
|
+
# Construct full agentId format for subgraph query
|
|
665
|
+
default_chain_id = self.web3_client.chain_id
|
|
666
|
+
token_id = agentId.split(":")[-1] if ":" in agentId else agentId
|
|
667
|
+
full_agent_id = f"{default_chain_id}:{token_id}"
|
|
668
|
+
subgraph_client = self.subgraph_client
|
|
669
|
+
|
|
670
|
+
if subgraph_client:
|
|
671
|
+
# Use subgraph to calculate reputation
|
|
672
|
+
return self._get_reputation_summary_from_subgraph(
|
|
673
|
+
full_agent_id, clientAddresses, tag1, tag2, groupBy
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
# Fallback to blockchain (requires chain-specific web3 client)
|
|
677
|
+
# For now, only works if chain matches SDK's default
|
|
678
|
+
if chain_id is not None and chain_id != self.web3_client.chain_id:
|
|
679
|
+
raise ValueError(
|
|
680
|
+
f"Blockchain reputation summary not supported for chain {chain_id}. "
|
|
681
|
+
f"SDK is configured for chain {self.web3_client.chain_id}. "
|
|
682
|
+
f"Use subgraph-based summary instead."
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Parse agent ID for blockchain call
|
|
707
686
|
if ":" in agentId:
|
|
708
687
|
tokenId = int(agentId.split(":")[-1])
|
|
709
688
|
else:
|
|
@@ -711,16 +690,16 @@ class FeedbackManager:
|
|
|
711
690
|
|
|
712
691
|
try:
|
|
713
692
|
client_list = clientAddresses if clientAddresses else []
|
|
714
|
-
|
|
715
|
-
|
|
693
|
+
tag1_str = tag1 if tag1 else ""
|
|
694
|
+
tag2_str = tag2 if tag2 else ""
|
|
716
695
|
|
|
717
696
|
result = self.web3_client.call_contract(
|
|
718
697
|
self.reputation_registry,
|
|
719
698
|
"getSummary",
|
|
720
699
|
tokenId,
|
|
721
700
|
client_list,
|
|
722
|
-
|
|
723
|
-
|
|
701
|
+
tag1_str,
|
|
702
|
+
tag2_str
|
|
724
703
|
)
|
|
725
704
|
|
|
726
705
|
count, average_score = result
|
|
@@ -765,6 +744,67 @@ class FeedbackManager:
|
|
|
765
744
|
except Exception as e:
|
|
766
745
|
raise ValueError(f"Failed to get reputation summary: {e}")
|
|
767
746
|
|
|
747
|
+
def _get_reputation_summary_from_subgraph(
|
|
748
|
+
self,
|
|
749
|
+
agentId: AgentId,
|
|
750
|
+
clientAddresses: Optional[List[Address]] = None,
|
|
751
|
+
tag1: Optional[str] = None,
|
|
752
|
+
tag2: Optional[str] = None,
|
|
753
|
+
groupBy: Optional[List[str]] = None,
|
|
754
|
+
) -> Dict[str, Any]:
|
|
755
|
+
"""Get reputation summary from subgraph."""
|
|
756
|
+
# Build tags list
|
|
757
|
+
tags = []
|
|
758
|
+
if tag1:
|
|
759
|
+
tags.append(tag1)
|
|
760
|
+
if tag2:
|
|
761
|
+
tags.append(tag2)
|
|
762
|
+
|
|
763
|
+
# Get all feedback for the agent using indexer (which handles multi-chain)
|
|
764
|
+
# Use searchFeedback with a large limit to get all feedback
|
|
765
|
+
all_feedback = self.searchFeedback(
|
|
766
|
+
agentId=agentId,
|
|
767
|
+
clientAddresses=clientAddresses,
|
|
768
|
+
tags=tags if tags else None,
|
|
769
|
+
include_revoked=False,
|
|
770
|
+
first=1000, # Large limit to get all feedback
|
|
771
|
+
skip=0
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# Calculate summary statistics
|
|
775
|
+
count = len(all_feedback)
|
|
776
|
+
scores = [fb.score for fb in all_feedback if fb.score is not None]
|
|
777
|
+
average_score = sum(scores) / len(scores) if scores else 0.0
|
|
778
|
+
|
|
779
|
+
# If no grouping requested, return simple summary
|
|
780
|
+
if not groupBy:
|
|
781
|
+
return {
|
|
782
|
+
"agentId": agentId,
|
|
783
|
+
"count": count,
|
|
784
|
+
"averageScore": average_score,
|
|
785
|
+
"filters": {
|
|
786
|
+
"clientAddresses": clientAddresses,
|
|
787
|
+
"tag1": tag1,
|
|
788
|
+
"tag2": tag2
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
# Group feedback by requested dimensions
|
|
793
|
+
grouped_data = self._groupFeedback(all_feedback, groupBy)
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
"agentId": agentId,
|
|
797
|
+
"totalCount": count,
|
|
798
|
+
"totalAverageScore": average_score,
|
|
799
|
+
"groupedData": grouped_data,
|
|
800
|
+
"filters": {
|
|
801
|
+
"clientAddresses": clientAddresses,
|
|
802
|
+
"tag1": tag1,
|
|
803
|
+
"tag2": tag2
|
|
804
|
+
},
|
|
805
|
+
"groupBy": groupBy
|
|
806
|
+
}
|
|
807
|
+
|
|
768
808
|
def _groupFeedback(self, feedbackList: List[Feedback], groupBy: List[str]) -> Dict[str, Any]:
|
|
769
809
|
"""Group feedback by specified dimensions."""
|
|
770
810
|
grouped = {}
|
|
@@ -848,39 +888,28 @@ class FeedbackManager:
|
|
|
848
888
|
|
|
849
889
|
return "|".join(key_parts)
|
|
850
890
|
|
|
851
|
-
def
|
|
852
|
-
"""
|
|
853
|
-
if not text:
|
|
854
|
-
return b"\x00" * 32
|
|
855
|
-
|
|
856
|
-
# Encode as UTF-8 and pad/truncate to 32 bytes
|
|
857
|
-
encoded = text.encode('utf-8')
|
|
858
|
-
if len(encoded) > 32:
|
|
859
|
-
encoded = encoded[:32]
|
|
860
|
-
else:
|
|
861
|
-
encoded = encoded.ljust(32, b'\x00')
|
|
862
|
-
|
|
863
|
-
return encoded
|
|
864
|
-
|
|
865
|
-
def _bytes32ToTags(self, tag1: bytes, tag2: bytes) -> List[str]:
|
|
866
|
-
"""Convert bytes32 tags back to strings."""
|
|
867
|
-
tags = []
|
|
868
|
-
|
|
869
|
-
if tag1 and tag1 != b"\x00" * 32:
|
|
870
|
-
tag1_str = tag1.rstrip(b'\x00').decode('utf-8', errors='ignore')
|
|
871
|
-
if tag1_str:
|
|
872
|
-
tags.append(tag1_str)
|
|
891
|
+
def _normalizeTag(self, tag: str) -> str:
|
|
892
|
+
"""Normalize string tag (trim, validate length if needed).
|
|
873
893
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
894
|
+
Args:
|
|
895
|
+
tag: Tag string to normalize
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
Normalized tag string
|
|
899
|
+
"""
|
|
900
|
+
if not tag:
|
|
901
|
+
return ""
|
|
902
|
+
# Trim whitespace
|
|
903
|
+
normalized = tag.strip()
|
|
904
|
+
# Tags are now strings with no length limit, but we can validate if needed
|
|
905
|
+
return normalized
|
|
880
906
|
|
|
881
907
|
def _hexBytes32ToTags(self, tag1: str, tag2: str) -> List[str]:
|
|
882
908
|
"""Convert hex bytes32 tags back to strings, or return plain strings as-is.
|
|
883
909
|
|
|
910
|
+
DEPRECATED: This method is kept for backward compatibility with old data
|
|
911
|
+
that may have bytes32 tags. New tags are strings and don't need conversion.
|
|
912
|
+
|
|
884
913
|
The subgraph now stores tags as human-readable strings (not hex),
|
|
885
914
|
so this method handles both formats for backwards compatibility.
|
|
886
915
|
"""
|