agent0-sdk 1.0.1__py3-none-any.whl → 1.2.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.
@@ -8,7 +8,7 @@ import json
8
8
  import logging
9
9
  import time
10
10
  from typing import Any, Dict, List, Optional, Tuple, Union
11
- from datetime import datetime
11
+ from datetime import datetime, timezone
12
12
 
13
13
  from .models import (
14
14
  AgentId, Address, URI, Timestamp, IdemKey,
@@ -16,6 +16,7 @@ from .models import (
16
16
  )
17
17
  from .web3_client import Web3Client
18
18
  from .ipfs_client import IPFSClient
19
+ from .value_encoding import encode_feedback_value, decode_feedback_value
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -40,77 +41,66 @@ class FeedbackManager:
40
41
  self.subgraph_client = subgraph_client
41
42
  self.indexer = indexer
42
43
 
43
- def prepareFeedback(
44
- self,
45
- agentId: AgentId,
46
- score: Optional[int] = None, # 0-100
47
- tags: List[str] = None,
48
- text: Optional[str] = None,
49
- capability: Optional[str] = None,
50
- name: Optional[str] = None,
51
- skill: Optional[str] = None,
52
- task: Optional[str] = None,
53
- endpoint: Optional[str] = None,
54
- domain: Optional[str] = None,
55
- context: Optional[Dict[str, Any]] = None,
56
- proofOfPayment: Optional[Dict[str, Any]] = None,
57
- extra: Optional[Dict[str, Any]] = None,
58
- ) -> Dict[str, Any]:
59
- """Prepare feedback file (local file/object) according to spec."""
60
- if tags is None:
61
- tags = []
62
-
63
- # Parse agent ID to get token ID
64
- if ":" in agentId:
65
- tokenId = int(agentId.split(":")[-1])
66
- else:
67
- tokenId = int(agentId)
68
-
69
- # Get current timestamp in ISO format
70
- createdAt = datetime.fromtimestamp(time.time()).isoformat() + "Z"
71
-
72
- # Build feedback data according to spec
73
- feedbackData = {
74
- # MUST FIELDS
75
- "agentRegistry": f"eip155:{self.web3_client.chain_id}:{self.identity_registry.address if self.identity_registry else '0x0'}",
76
- "agentId": tokenId,
77
- "clientAddress": f"eip155:{self.web3_client.chain_id}:{self.web3_client.account.address}",
78
- "createdAt": createdAt,
79
- "score": int(score) if score else 0, # Score as integer (0-100)
80
-
81
- # MAY FIELDS
82
- "tag1": tags[0] if tags else None,
83
- "tag2": tags[1] if len(tags) > 1 else None,
84
- "endpoint": endpoint,
85
- "domain": domain,
86
- "skill": skill,
87
- "context": context,
88
- "task": task,
89
- "capability": capability,
90
- "name": name,
91
- "proofOfPayment": proofOfPayment,
92
- }
93
-
94
- # Remove None values to keep the structure clean
95
- feedbackData = {k: v for k, v in feedbackData.items() if v is not None}
44
+ def prepareFeedbackFile(self, input: Dict[str, Any]) -> Dict[str, Any]:
45
+ """Prepare an off-chain feedback file payload (no on-chain fields).
96
46
 
97
- if extra:
98
- feedbackData.update(extra)
47
+ This intentionally does NOT attempt to represent on-chain fields like:
48
+ value/tag1/tag2/endpoint (on-chain value), or registry-derived fields.
99
49
 
100
- return feedbackData
50
+ It may validate/normalize and remove None values.
51
+ """
52
+ if input is None:
53
+ raise ValueError("prepareFeedbackFile input cannot be None")
54
+ if not isinstance(input, dict):
55
+ raise TypeError(f"prepareFeedbackFile input must be a dict, got {type(input)}")
56
+
57
+ # Shallow copy and strip None values
58
+ out: Dict[str, Any] = {k: v for k, v in dict(input).items() if v is not None}
59
+
60
+ # Minimal normalization for known optional fields
61
+ if "endpoint" in out and out["endpoint"] is not None and not isinstance(out["endpoint"], str):
62
+ out["endpoint"] = str(out["endpoint"])
63
+ if "domain" in out and out["domain"] is not None and not isinstance(out["domain"], str):
64
+ out["domain"] = str(out["domain"])
65
+
66
+ return out
101
67
 
102
68
  def giveFeedback(
103
69
  self,
104
70
  agentId: AgentId,
105
- feedbackFile: Dict[str, Any],
106
- idem: Optional[IdemKey] = None,
71
+ value: Union[int, float, str],
72
+ tag1: Optional[str] = None,
73
+ tag2: Optional[str] = None,
74
+ endpoint: Optional[str] = None,
75
+ feedbackFile: Optional[Dict[str, Any]] = None,
107
76
  ) -> Feedback:
108
77
  """Give feedback (maps 8004 endpoint)."""
109
- # Parse agent ID
110
- if ":" in agentId:
111
- tokenId = int(agentId.split(":")[-1])
78
+ # Parse agentId into (chainId, tokenId)
79
+ agent_chain_id: Optional[int] = None
80
+ tokenId: int
81
+ if isinstance(agentId, str) and agentId.startswith("eip155:"):
82
+ parts = agentId.split(":")
83
+ if len(parts) != 3:
84
+ raise ValueError(f"Invalid AgentId (expected eip155:chainId:tokenId): {agentId}")
85
+ agent_chain_id = int(parts[1])
86
+ tokenId = int(parts[2])
87
+ elif isinstance(agentId, str) and ":" in agentId:
88
+ parts = agentId.split(":")
89
+ if len(parts) != 2:
90
+ raise ValueError(f"Invalid AgentId (expected chainId:tokenId): {agentId}")
91
+ agent_chain_id = int(parts[0])
92
+ tokenId = int(parts[1])
112
93
  else:
113
94
  tokenId = int(agentId)
95
+ agent_chain_id = int(self.web3_client.chain_id)
96
+
97
+ # Ensure we are submitting the tx on the agent's chain
98
+ if int(self.web3_client.chain_id) != int(agent_chain_id):
99
+ raise ValueError(
100
+ f"Chain mismatch for giveFeedback: agentId={agentId} targets chainId={agent_chain_id}, "
101
+ f"but web3 client is connected to chainId={self.web3_client.chain_id}. "
102
+ f"Initialize the SDK/Web3Client for chainId={agent_chain_id}."
103
+ )
114
104
 
115
105
  # Get client address (the one giving feedback)
116
106
  # Keep in checksum format for blockchain calls (web3.py requirement)
@@ -128,41 +118,105 @@ class FeedbackManager:
128
118
  except Exception as e:
129
119
  raise ValueError(f"Failed to get feedback index: {e}")
130
120
 
131
- # Prepare on-chain data
132
- score = feedbackFile.get("score", 0) # Already in 0-100 range
133
- tag1 = feedbackFile.get("tag1", "") or ""
134
- tag2 = feedbackFile.get("tag2", "") or ""
135
- endpoint = feedbackFile.get("endpoint", "") or ""
121
+ value_raw, value_decimals, _normalized = encode_feedback_value(value)
122
+
123
+ tag1 = tag1 or ""
124
+ tag2 = tag2 or ""
125
+
126
+ feedback_file: Optional[Dict[str, Any]] = feedbackFile
127
+ if feedback_file is not None and not isinstance(feedback_file, dict):
128
+ raise TypeError(f"feedbackFile must be a dict when provided, got {type(feedback_file)}")
129
+
130
+ # Endpoint precedence: explicit arg > file endpoint > empty string
131
+ if endpoint:
132
+ endpoint_onchain = endpoint
133
+ elif feedback_file and isinstance(feedback_file.get("endpoint"), str) and feedback_file.get("endpoint"):
134
+ endpoint_onchain = feedback_file.get("endpoint")
135
+ else:
136
+ endpoint_onchain = ""
137
+
138
+ # If uploading a file and we have an explicit endpoint, inject it for consistency
139
+ if feedback_file is not None and endpoint and isinstance(endpoint, str):
140
+ feedback_file = dict(feedback_file)
141
+ feedback_file["endpoint"] = endpoint
136
142
 
137
143
  # Handle off-chain file storage
138
144
  feedbackUri = ""
139
145
  feedbackHash = b"\x00" * 32 # Default empty hash
140
146
 
141
- if self.ipfs_client:
142
- # Store feedback file on IPFS using Filecoin Pin
147
+ if feedback_file is not None:
148
+ if not self.ipfs_client:
149
+ raise ValueError("feedbackFile was provided, but no IPFS client is configured")
150
+
151
+ # Store an ERC-8004 compliant feedback file on IPFS (explicit opt-in)
143
152
  try:
144
153
  logger.debug("Storing feedback file on IPFS")
145
- cid = self.ipfs_client.add_json(feedbackFile)
154
+ # createdAt MUST be present in the off-chain file; use provided value if valid, else now (UTC).
155
+ created_at = feedback_file.get("createdAt")
156
+ if not isinstance(created_at, str) or not created_at:
157
+ created_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
158
+
159
+ identity_registry_address = "0x0"
160
+ try:
161
+ if self.identity_registry is not None:
162
+ identity_registry_address = str(getattr(self.identity_registry, "address", "0x0"))
163
+ except Exception:
164
+ identity_registry_address = "0x0"
165
+
166
+ # Remove any user-provided copies of the envelope keys; SDK-owned values must win
167
+ rich = dict(feedback_file)
168
+ for k in [
169
+ "agentRegistry",
170
+ "agentId",
171
+ "clientAddress",
172
+ "createdAt",
173
+ "value",
174
+ "valueDecimals",
175
+ "tag1",
176
+ "tag2",
177
+ "endpoint",
178
+ ]:
179
+ rich.pop(k, None)
180
+
181
+ file_for_storage: Dict[str, Any] = {
182
+ # MUST fields (spec)
183
+ "agentRegistry": f"eip155:{agent_chain_id}:{identity_registry_address}",
184
+ "agentId": tokenId,
185
+ "clientAddress": f"eip155:{agent_chain_id}:{clientAddress}",
186
+ "createdAt": created_at,
187
+ # On-chain fields (store raw+decimals for precision)
188
+ "value": str(value_raw),
189
+ "valueDecimals": int(value_decimals),
190
+
191
+ # OPTIONAL fields that mirror on-chain
192
+ **({"tag1": tag1} if tag1 else {}),
193
+ **({"tag2": tag2} if tag2 else {}),
194
+ **({"endpoint": endpoint_onchain} if endpoint_onchain else {}),
195
+
196
+ # Rich/off-chain fields
197
+ **rich,
198
+ }
199
+
200
+ cid = self.ipfs_client.addFeedbackFile(file_for_storage)
146
201
  feedbackUri = f"ipfs://{cid}"
147
- feedbackHash = self.web3_client.keccak256(json.dumps(feedbackFile, sort_keys=True).encode())
202
+ feedbackHash = self.web3_client.keccak256(
203
+ json.dumps(file_for_storage, sort_keys=True).encode()
204
+ )
148
205
  logger.debug(f"Feedback file stored on IPFS: {cid}")
149
206
  except Exception as e:
150
- logger.warning(f"Failed to store feedback on IPFS: {e}")
151
- # Continue without IPFS storage
152
- elif feedbackFile.get("context") or feedbackFile.get("capability") or feedbackFile.get("name"):
153
- # If we have rich data but no IPFS, we need to store it somewhere
154
- raise ValueError("Rich feedback data requires IPFS client for storage")
207
+ raise ValueError(f"Failed to store feedback on IPFS: {e}")
155
208
 
156
- # Submit to blockchain with new signature: giveFeedback(agentId, score, tag1, tag2, endpoint, feedbackURI, feedbackHash)
209
+ # Submit to blockchain with new signature: giveFeedback(agentId, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash)
157
210
  try:
158
211
  txHash = self.web3_client.transact_contract(
159
212
  self.reputation_registry,
160
213
  "giveFeedback",
161
214
  tokenId,
162
- score,
215
+ value_raw,
216
+ value_decimals,
163
217
  tag1,
164
218
  tag2,
165
- endpoint,
219
+ endpoint_onchain,
166
220
  feedbackUri,
167
221
  feedbackHash
168
222
  )
@@ -176,24 +230,25 @@ class FeedbackManager:
176
230
  # Create feedback object (address normalization happens in Feedback.create_id)
177
231
  feedbackId = Feedback.create_id(agentId, clientAddress, feedbackIndex)
178
232
 
233
+ ff: Dict[str, Any] = feedback_file or {}
179
234
  return Feedback(
180
235
  id=feedbackId,
181
236
  agentId=agentId,
182
237
  reviewer=clientAddress, # create_id normalizes the ID; reviewer field can remain as-is
183
- score=int(score) if score and score > 0 else None,
238
+ value=decode_feedback_value(value_raw, value_decimals),
184
239
  tags=[tag1, tag2] if tag1 or tag2 else [],
185
- text=feedbackFile.get("text"),
186
- context=feedbackFile.get("context"),
187
- proofOfPayment=feedbackFile.get("proofOfPayment"),
240
+ text=ff.get("text"),
241
+ context=ff.get("context"),
242
+ proofOfPayment=ff.get("proofOfPayment"),
188
243
  fileURI=feedbackUri if feedbackUri else None,
189
- endpoint=endpoint if endpoint else None,
244
+ endpoint=endpoint_onchain if endpoint_onchain else None,
190
245
  createdAt=int(time.time()),
191
246
  isRevoked=False,
192
247
  # Off-chain only fields
193
- capability=feedbackFile.get("capability"),
194
- name=feedbackFile.get("name"),
195
- skill=feedbackFile.get("skill"),
196
- task=feedbackFile.get("task")
248
+ capability=ff.get("capability"),
249
+ name=ff.get("name"),
250
+ skill=ff.get("skill"),
251
+ task=ff.get("task")
197
252
  )
198
253
 
199
254
  def getFeedback(
@@ -210,14 +265,14 @@ class FeedbackManager:
210
265
  except Exception as e:
211
266
  logger.debug(f"Indexer/subgraph get_feedback failed, falling back to blockchain: {e}")
212
267
  return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
213
-
268
+
214
269
  if self.subgraph_client:
215
270
  try:
216
271
  return self._get_feedback_from_subgraph(agentId, clientAddress, feedbackIndex)
217
272
  except Exception as e:
218
273
  logger.debug(f"Subgraph get feedback failed, falling back to blockchain: {e}")
219
274
  return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
220
-
275
+
221
276
  return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
222
277
 
223
278
  def _get_feedback_from_subgraph(
@@ -267,15 +322,15 @@ class FeedbackManager:
267
322
  tag1 = feedback_data.get('tag1') or feedback_file.get('tag1')
268
323
  tag2 = feedback_data.get('tag2') or feedback_file.get('tag2')
269
324
  if isinstance(tag1, str) and tag1:
270
- tags.append(tag1)
325
+ tags.append(tag1)
271
326
  if isinstance(tag2, str) and tag2:
272
- tags.append(tag2)
327
+ tags.append(tag2)
273
328
 
274
329
  return Feedback(
275
330
  id=Feedback.create_id(agentId, clientAddress, feedbackIndex), # create_id now normalizes
276
331
  agentId=agentId,
277
332
  reviewer=self.web3_client.normalize_address(clientAddress), # Also normalize reviewer field
278
- score=feedback_data.get('score'),
333
+ value=float(feedback_data.get("value")) if feedback_data.get("value") is not None else None,
279
334
  tags=tags,
280
335
  text=feedback_file.get('text'),
281
336
  capability=feedback_file.get('capability'),
@@ -287,7 +342,8 @@ class FeedbackManager:
287
342
  'txHash': feedback_file.get('proofOfPaymentTxHash'),
288
343
  } if feedback_file.get('proofOfPaymentFromAddress') else None,
289
344
  fileURI=feedback_data.get('feedbackURI') or feedback_data.get('feedbackUri'), # Handle both old and new field names
290
- endpoint=feedback_data.get('endpoint'),
345
+ # Prefer on-chain endpoint; fall back to off-chain file endpoint if missing
346
+ endpoint=feedback_data.get('endpoint') or feedback_file.get('endpoint'),
291
347
  createdAt=feedback_data.get('createdAt', int(time.time())),
292
348
  answers=answers,
293
349
  isRevoked=feedback_data.get('isRevoked', False),
@@ -322,7 +378,7 @@ class FeedbackManager:
322
378
  feedbackIndex
323
379
  )
324
380
 
325
- score, tag1, tag2, is_revoked = result
381
+ value_raw, value_decimals, tag1, tag2, is_revoked = result
326
382
 
327
383
  # Create feedback object (normalize address for consistency)
328
384
  normalized_address = self.web3_client.normalize_address(clientAddress)
@@ -339,7 +395,7 @@ class FeedbackManager:
339
395
  id=feedbackId,
340
396
  agentId=agentId,
341
397
  reviewer=normalized_address,
342
- score=int(score) if score and score > 0 else None,
398
+ value=decode_feedback_value(int(value_raw), int(value_decimals)),
343
399
  tags=tags,
344
400
  text=None, # Not stored on-chain
345
401
  capability=None, # Not stored on-chain
@@ -363,8 +419,8 @@ class FeedbackManager:
363
419
  skills: Optional[List[str]] = None,
364
420
  tasks: Optional[List[str]] = None,
365
421
  names: Optional[List[str]] = None,
366
- minScore: Optional[int] = None,
367
- maxScore: Optional[int] = None,
422
+ minValue: Optional[float] = None,
423
+ maxValue: Optional[float] = None,
368
424
  include_revoked: bool = False,
369
425
  first: int = 100,
370
426
  skip: int = 0,
@@ -376,14 +432,14 @@ class FeedbackManager:
376
432
  # This enables future semantic search capabilities
377
433
  return self.indexer.search_feedback(
378
434
  agentId, clientAddresses, tags, capabilities, skills, tasks, names,
379
- minScore, maxScore, include_revoked, first, skip
435
+ minValue, maxValue, include_revoked, first, skip
380
436
  )
381
437
 
382
438
  # Fallback: direct subgraph access (if indexer not available)
383
439
  if self.subgraph_client:
384
440
  return self._search_feedback_subgraph(
385
441
  agentId, clientAddresses, tags, capabilities, skills, tasks, names,
386
- minScore, maxScore, include_revoked, first, skip
442
+ minValue, maxValue, include_revoked, first, skip
387
443
  )
388
444
 
389
445
  # Fallback to blockchain
@@ -399,7 +455,7 @@ class FeedbackManager:
399
455
  tag1_filter = tags[0] if tags else ""
400
456
  tag2_filter = tags[1] if tags and len(tags) > 1 else ""
401
457
 
402
- # Read from blockchain - new signature returns: (clientAddresses, feedbackIndexes, scores, tag1s, tag2s, revokedStatuses)
458
+ # Read from blockchain - signature returns: (clients, feedbackIndexes, values, valueDecimals, tag1s, tag2s, revokedStatuses)
403
459
  result = self.web3_client.call_contract(
404
460
  self.reputation_registry,
405
461
  "readAllFeedback",
@@ -410,7 +466,7 @@ class FeedbackManager:
410
466
  include_revoked
411
467
  )
412
468
 
413
- clients, feedback_indexes, scores, tag1s, tag2s, revoked_statuses = result
469
+ clients, feedback_indexes, values, value_decimals, tag1s, tag2s, revoked_statuses = result
414
470
 
415
471
  # Convert to Feedback objects
416
472
  feedbacks = []
@@ -429,7 +485,7 @@ class FeedbackManager:
429
485
  id=feedbackId,
430
486
  agentId=agentId,
431
487
  reviewer=clients[i],
432
- score=int(scores[i]) if scores[i] and scores[i] > 0 else None,
488
+ value=decode_feedback_value(int(values[i]), int(value_decimals[i])),
433
489
  tags=tags_list,
434
490
  text=None,
435
491
  capability=None,
@@ -456,8 +512,8 @@ class FeedbackManager:
456
512
  skills: Optional[List[str]],
457
513
  tasks: Optional[List[str]],
458
514
  names: Optional[List[str]],
459
- minScore: Optional[int],
460
- maxScore: Optional[int],
515
+ minValue: Optional[float],
516
+ maxValue: Optional[float],
461
517
  include_revoked: bool,
462
518
  first: int,
463
519
  skip: int,
@@ -472,8 +528,8 @@ class FeedbackManager:
472
528
  skills=skills,
473
529
  tasks=tasks,
474
530
  names=names,
475
- minScore=minScore,
476
- maxScore=maxScore,
531
+ minValue=minValue,
532
+ maxValue=maxValue,
477
533
  includeRevoked=include_revoked
478
534
  )
479
535
 
@@ -509,9 +565,9 @@ class FeedbackManager:
509
565
  tag1 = fb_data.get('tag1') or feedback_file.get('tag1')
510
566
  tag2 = fb_data.get('tag2') or feedback_file.get('tag2')
511
567
  if isinstance(tag1, str) and tag1:
512
- tags_list.append(tag1)
568
+ tags_list.append(tag1)
513
569
  if isinstance(tag2, str) and tag2:
514
- tags_list.append(tag2)
570
+ tags_list.append(tag2)
515
571
 
516
572
  # Parse agentId from feedback ID
517
573
  feedback_id = fb_data['id']
@@ -529,7 +585,7 @@ class FeedbackManager:
529
585
  id=Feedback.create_id(agent_id_str, client_addr, feedback_idx),
530
586
  agentId=agent_id_str,
531
587
  reviewer=client_addr,
532
- score=fb_data.get('score'),
588
+ value=float(fb_data.get("value")) if fb_data.get("value") is not None else None,
533
589
  tags=tags_list,
534
590
  text=feedback_file.get('text'),
535
591
  capability=feedback_file.get('capability'),
@@ -702,14 +758,15 @@ class FeedbackManager:
702
758
  tag2_str
703
759
  )
704
760
 
705
- count, average_score = result
761
+ count, summary_value, summary_value_decimals = result
762
+ average_value = decode_feedback_value(int(summary_value), int(summary_value_decimals))
706
763
 
707
764
  # If no grouping requested, return simple summary
708
765
  if not groupBy:
709
766
  return {
710
767
  "agentId": agentId,
711
768
  "count": count,
712
- "averageScore": float(average_score) / 100.0 if average_score > 0 else 0.0,
769
+ "averageValue": average_value,
713
770
  "filters": {
714
771
  "clientAddresses": clientAddresses,
715
772
  "tag1": tag1,
@@ -731,7 +788,7 @@ class FeedbackManager:
731
788
  return {
732
789
  "agentId": agentId,
733
790
  "totalCount": count,
734
- "totalAverageScore": float(average_score) / 100.0 if average_score > 0 else 0.0,
791
+ "totalAverageValue": average_value,
735
792
  "groupedData": grouped_data,
736
793
  "filters": {
737
794
  "clientAddresses": clientAddresses,
@@ -773,15 +830,15 @@ class FeedbackManager:
773
830
 
774
831
  # Calculate summary statistics
775
832
  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
833
+ values = [fb.value for fb in all_feedback if fb.value is not None]
834
+ average_value = sum(values) / len(values) if values else 0.0
778
835
 
779
836
  # If no grouping requested, return simple summary
780
837
  if not groupBy:
781
838
  return {
782
839
  "agentId": agentId,
783
840
  "count": count,
784
- "averageScore": average_score,
841
+ "averageValue": average_value,
785
842
  "filters": {
786
843
  "clientAddresses": clientAddresses,
787
844
  "tag1": tag1,
@@ -795,7 +852,7 @@ class FeedbackManager:
795
852
  return {
796
853
  "agentId": agentId,
797
854
  "totalCount": count,
798
- "totalAverageScore": average_score,
855
+ "totalAverageValue": average_value,
799
856
  "groupedData": grouped_data,
800
857
  "filters": {
801
858
  "clientAddresses": clientAddresses,
@@ -816,23 +873,23 @@ class FeedbackManager:
816
873
  if group_key not in grouped:
817
874
  grouped[group_key] = {
818
875
  "count": 0,
819
- "totalScore": 0.0,
820
- "averageScore": 0.0,
821
- "scores": [],
876
+ "totalValue": 0.0,
877
+ "averageValue": 0.0,
878
+ "values": [],
822
879
  "feedback": []
823
880
  }
824
881
 
825
882
  # Add feedback to group
826
883
  grouped[group_key]["count"] += 1
827
- if feedback.score is not None:
828
- grouped[group_key]["totalScore"] += feedback.score
829
- grouped[group_key]["scores"].append(feedback.score)
884
+ if feedback.value is not None:
885
+ grouped[group_key]["totalValue"] += float(feedback.value)
886
+ grouped[group_key]["values"].append(float(feedback.value))
830
887
  grouped[group_key]["feedback"].append(feedback)
831
888
 
832
889
  # Calculate averages for each group
833
890
  for group_data in grouped.values():
834
891
  if group_data["count"] > 0:
835
- group_data["averageScore"] = group_data["totalScore"] / group_data["count"]
892
+ group_data["averageValue"] = group_data["totalValue"] / group_data["count"]
836
893
 
837
894
  return grouped
838
895
 
@@ -87,7 +87,7 @@ class AgentIndexer:
87
87
  def _create_default_embeddings(self):
88
88
  """Create default embeddings model."""
89
89
  try:
90
- from sentence_transformers import SentenceTransformer
90
+ from sentence_transformers import SentenceTransformer # type: ignore[import-not-found]
91
91
  return SentenceTransformer('all-MiniLM-L6-v2')
92
92
  except ImportError:
93
93
  # Return None if sentence-transformers is not available
@@ -260,7 +260,7 @@ class AgentIndexer:
260
260
  raise ValueError(f"Failed to get agent data: {e}")
261
261
 
262
262
  # Load registration file
263
- registration_data = await self._load_registration_data(token_uri)
263
+ registration_data = await self._load_registration_data(agent_uri)
264
264
 
265
265
  # Create agent summary
266
266
  summary = self._create_agent_summary(
@@ -1082,7 +1082,7 @@ class AgentIndexer:
1082
1082
  id=Feedback.create_id(agentId, clientAddress, feedbackIndex),
1083
1083
  agentId=agentId,
1084
1084
  reviewer=self.web3_client.normalize_address(clientAddress),
1085
- score=feedback_data.get('score'),
1085
+ value=float(feedback_data.get("value")) if feedback_data.get("value") is not None else None,
1086
1086
  tags=tags,
1087
1087
  text=feedback_file.get('text'),
1088
1088
  capability=feedback_file.get('capability'),
@@ -1094,7 +1094,8 @@ class AgentIndexer:
1094
1094
  'txHash': feedback_file.get('proofOfPaymentTxHash'),
1095
1095
  } if feedback_file.get('proofOfPaymentFromAddress') else None,
1096
1096
  fileURI=feedback_data.get('feedbackURI') or feedback_data.get('feedbackUri'), # Handle both old and new field names
1097
- endpoint=feedback_data.get('endpoint'),
1097
+ # Prefer on-chain endpoint; fall back to off-chain file endpoint if missing
1098
+ endpoint=feedback_data.get('endpoint') or feedback_file.get('endpoint'),
1098
1099
  createdAt=feedback_data.get('createdAt', int(time.time())),
1099
1100
  answers=answers,
1100
1101
  isRevoked=feedback_data.get('isRevoked', False),
@@ -1112,8 +1113,8 @@ class AgentIndexer:
1112
1113
  skills: Optional[List[str]] = None,
1113
1114
  tasks: Optional[List[str]] = None,
1114
1115
  names: Optional[List[str]] = None,
1115
- minScore: Optional[int] = None,
1116
- maxScore: Optional[int] = None,
1116
+ minValue: Optional[float] = None,
1117
+ maxValue: Optional[float] = None,
1117
1118
  include_revoked: bool = False,
1118
1119
  first: int = 100,
1119
1120
  skip: int = 0,
@@ -1139,7 +1140,7 @@ class AgentIndexer:
1139
1140
  if subgraph_client:
1140
1141
  return self._search_feedback_subgraph(
1141
1142
  full_agent_id, clientAddresses, tags, capabilities, skills, tasks, names,
1142
- minScore, maxScore, include_revoked, first, skip, subgraph_client
1143
+ minValue, maxValue, include_revoked, first, skip, subgraph_client
1143
1144
  )
1144
1145
 
1145
1146
  # Fallback not implemented (would require blockchain queries)
@@ -1155,8 +1156,8 @@ class AgentIndexer:
1155
1156
  skills: Optional[List[str]],
1156
1157
  tasks: Optional[List[str]],
1157
1158
  names: Optional[List[str]],
1158
- minScore: Optional[int],
1159
- maxScore: Optional[int],
1159
+ minValue: Optional[float],
1160
+ maxValue: Optional[float],
1160
1161
  include_revoked: bool,
1161
1162
  first: int,
1162
1163
  skip: int,
@@ -1177,8 +1178,8 @@ class AgentIndexer:
1177
1178
  skills=skills,
1178
1179
  tasks=tasks,
1179
1180
  names=names,
1180
- minScore=minScore,
1181
- maxScore=maxScore,
1181
+ minValue=minValue,
1182
+ maxValue=maxValue,
1182
1183
  includeRevoked=include_revoked
1183
1184
  )
1184
1185
 
@@ -1664,7 +1665,7 @@ class AgentIndexer:
1664
1665
  - updatedAt (timestamp)
1665
1666
  - totalFeedback (count)
1666
1667
  - name (alphabetical)
1667
- - averageScore (reputation, if available)
1668
+ - averageValue (reputation, if available)
1668
1669
  """
1669
1670
  if not sort or len(sort) == 0:
1670
1671
  # Default: sort by createdAt descending (newest first)
@@ -1699,9 +1700,9 @@ class AgentIndexer:
1699
1700
  reg_file = agent.get('registrationFile', {})
1700
1701
  return reg_file.get('name', '').lower()
1701
1702
 
1702
- elif field == 'averageScore':
1703
- # If reputation search was done, averageScore may be available
1704
- return agent.get('averageScore', 0)
1703
+ elif field == 'averageValue':
1704
+ # If reputation search was done, averageValue may be available
1705
+ return agent.get('averageValue', 0)
1705
1706
 
1706
1707
  else:
1707
1708
  logger.warning(f"Unknown sort field: {field}, defaulting to createdAt")