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.
- agent0_sdk/__init__.py +1 -1
- agent0_sdk/core/agent.py +10 -9
- agent0_sdk/core/contracts.py +23 -8
- agent0_sdk/core/feedback_manager.py +181 -124
- agent0_sdk/core/indexer.py +16 -15
- agent0_sdk/core/ipfs_client.py +8 -6
- agent0_sdk/core/models.py +5 -3
- agent0_sdk/core/sdk.py +64 -147
- agent0_sdk/core/subgraph_client.py +22 -27
- agent0_sdk/core/value_encoding.py +91 -0
- {agent0_sdk-1.0.1.dist-info → agent0_sdk-1.2.0.dist-info}/METADATA +29 -17
- agent0_sdk-1.2.0.dist-info/RECORD +20 -0
- agent0_sdk-1.0.1.dist-info/RECORD +0 -19
- {agent0_sdk-1.0.1.dist-info → agent0_sdk-1.2.0.dist-info}/WHEEL +0 -0
- {agent0_sdk-1.0.1.dist-info → agent0_sdk-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {agent0_sdk-1.0.1.dist-info → agent0_sdk-1.2.0.dist-info}/top_level.txt +0 -0
agent0_sdk/core/ipfs_client.py
CHANGED
|
@@ -160,7 +160,7 @@ class IPFSClient:
|
|
|
160
160
|
# add_str returns the CID directly as a string
|
|
161
161
|
return result if isinstance(result, str) else result['Hash']
|
|
162
162
|
|
|
163
|
-
def _pin_to_pinata(self, data: str) -> str:
|
|
163
|
+
def _pin_to_pinata(self, data: str, file_name: str = "file.json") -> str:
|
|
164
164
|
"""Pin data to Pinata using JWT authentication with v3 API."""
|
|
165
165
|
import requests
|
|
166
166
|
import tempfile
|
|
@@ -185,7 +185,7 @@ class IPFSClient:
|
|
|
185
185
|
# Prepare the file for upload with public network setting
|
|
186
186
|
with open(temp_path, 'rb') as file:
|
|
187
187
|
files = {
|
|
188
|
-
'file': (
|
|
188
|
+
'file': (file_name, file, 'application/json')
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
# Add network parameter to make file public
|
|
@@ -233,8 +233,9 @@ class IPFSClient:
|
|
|
233
233
|
|
|
234
234
|
def add(self, data: str, **kwargs) -> str:
|
|
235
235
|
"""Add data to IPFS and return CID."""
|
|
236
|
+
file_name = kwargs.pop("file_name", None)
|
|
236
237
|
if self.pinata_enabled:
|
|
237
|
-
return self._pin_to_pinata(data)
|
|
238
|
+
return self._pin_to_pinata(data, file_name=file_name or "file.json")
|
|
238
239
|
elif self.filecoin_pin_enabled:
|
|
239
240
|
# Create temporary file for Filecoin Pin
|
|
240
241
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
@@ -251,11 +252,12 @@ class IPFSClient:
|
|
|
251
252
|
|
|
252
253
|
def add_file(self, filepath: str, **kwargs) -> str:
|
|
253
254
|
"""Add file to IPFS and return CID."""
|
|
255
|
+
file_name = kwargs.pop("file_name", None)
|
|
254
256
|
if self.pinata_enabled:
|
|
255
257
|
# Read file and send to Pinata
|
|
256
258
|
with open(filepath, 'r') as f:
|
|
257
259
|
data = f.read()
|
|
258
|
-
return self._pin_to_pinata(data)
|
|
260
|
+
return self._pin_to_pinata(data, file_name=file_name or "file.json")
|
|
259
261
|
elif self.filecoin_pin_enabled:
|
|
260
262
|
return self._pin_to_filecoin(filepath)
|
|
261
263
|
else:
|
|
@@ -332,7 +334,7 @@ class IPFSClient:
|
|
|
332
334
|
def addRegistrationFile(self, registrationFile: "RegistrationFile", chainId: Optional[int] = None, identityRegistryAddress: Optional[str] = None, **kwargs) -> str:
|
|
333
335
|
"""Add registration file to IPFS and return CID."""
|
|
334
336
|
data = registrationFile.to_dict(chain_id=chainId, identity_registry_address=identityRegistryAddress)
|
|
335
|
-
return self.add_json(data, **kwargs)
|
|
337
|
+
return self.add_json(data, file_name="agent-registration.json", **kwargs)
|
|
336
338
|
|
|
337
339
|
def getRegistrationFile(self, cid: str) -> "RegistrationFile":
|
|
338
340
|
"""Get registration file from IPFS by CID."""
|
|
@@ -342,7 +344,7 @@ class IPFSClient:
|
|
|
342
344
|
|
|
343
345
|
def addFeedbackFile(self, feedbackData: Dict[str, Any], **kwargs) -> str:
|
|
344
346
|
"""Add feedback file to IPFS and return CID."""
|
|
345
|
-
return self.add_json(feedbackData, **kwargs)
|
|
347
|
+
return self.add_json(feedbackData, file_name="feedback.json", **kwargs)
|
|
346
348
|
|
|
347
349
|
def getFeedbackFile(self, cid: str) -> Dict[str, Any]:
|
|
348
350
|
"""Get feedback file from IPFS by CID."""
|
agent0_sdk/core/models.py
CHANGED
|
@@ -185,7 +185,9 @@ class Feedback:
|
|
|
185
185
|
id: tuple # (agentId, clientAddress, feedbackIndex) - tuple for efficiency
|
|
186
186
|
agentId: AgentId
|
|
187
187
|
reviewer: Address
|
|
188
|
-
|
|
188
|
+
# ReputationRegistry Jan 2026: decimal value computed as (value:int256 / 10^valueDecimals).
|
|
189
|
+
# SDK exposes ONLY the computed value.
|
|
190
|
+
value: Optional[float]
|
|
189
191
|
tags: List[str] = field(default_factory=list)
|
|
190
192
|
text: Optional[str] = None
|
|
191
193
|
context: Optional[Dict[str, Any]] = None
|
|
@@ -291,8 +293,8 @@ class SearchFeedbackParams:
|
|
|
291
293
|
tasks: Optional[List[str]] = None
|
|
292
294
|
names: Optional[List[str]] = None # MCP tool/resource/prompt names
|
|
293
295
|
endpoint: Optional[str] = None # Filter by endpoint URI
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
minValue: Optional[float] = None
|
|
297
|
+
maxValue: Optional[float] = None
|
|
296
298
|
includeRevoked: bool = False
|
|
297
299
|
|
|
298
300
|
def to_dict(self) -> Dict[str, Any]:
|
agent0_sdk/core/sdk.py
CHANGED
|
@@ -491,111 +491,8 @@ class SDK:
|
|
|
491
491
|
|
|
492
492
|
return self.indexer.search_agents(params, sort, page_size, cursor)
|
|
493
493
|
|
|
494
|
-
# Feedback methods
|
|
495
|
-
def prepareFeedback(
|
|
496
|
-
self,
|
|
497
|
-
agentId: AgentId,
|
|
498
|
-
score: Optional[int] = None, # 0-100
|
|
499
|
-
tags: List[str] = None,
|
|
500
|
-
text: Optional[str] = None,
|
|
501
|
-
capability: Optional[str] = None,
|
|
502
|
-
name: Optional[str] = None,
|
|
503
|
-
skill: Optional[str] = None,
|
|
504
|
-
task: Optional[str] = None,
|
|
505
|
-
context: Optional[Dict[str, Any]] = None,
|
|
506
|
-
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
507
|
-
extra: Optional[Dict[str, Any]] = None,
|
|
508
|
-
) -> Dict[str, Any]:
|
|
509
|
-
"""Prepare feedback file (local file/object)."""
|
|
510
|
-
return self.feedback_manager.prepareFeedback(
|
|
511
|
-
agentId=agentId,
|
|
512
|
-
score=score,
|
|
513
|
-
tags=tags,
|
|
514
|
-
text=text,
|
|
515
|
-
capability=capability,
|
|
516
|
-
name=name,
|
|
517
|
-
skill=skill,
|
|
518
|
-
task=task,
|
|
519
|
-
context=context,
|
|
520
|
-
proofOfPayment=proofOfPayment,
|
|
521
|
-
extra=extra
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
def giveFeedback(
|
|
525
|
-
self,
|
|
526
|
-
agentId: AgentId,
|
|
527
|
-
feedbackFile: Dict[str, Any],
|
|
528
|
-
idem: Optional[IdemKey] = None,
|
|
529
|
-
feedback_auth: Optional[bytes] = None,
|
|
530
|
-
) -> Feedback:
|
|
531
|
-
"""Give feedback (maps 8004 endpoint)."""
|
|
532
|
-
return self.feedback_manager.giveFeedback(
|
|
533
|
-
agentId=agentId,
|
|
534
|
-
feedbackFile=feedbackFile,
|
|
535
|
-
idem=idem,
|
|
536
|
-
feedback_auth=feedback_auth
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
def getFeedback(self, feedbackId: str) -> Feedback:
|
|
540
|
-
"""Get single feedback by ID string."""
|
|
541
|
-
# Parse feedback ID
|
|
542
|
-
agentId, clientAddress, feedbackIndex = Feedback.from_id_string(feedbackId)
|
|
543
|
-
return self.feedback_manager.getFeedback(agentId, clientAddress, feedbackIndex)
|
|
544
|
-
|
|
545
|
-
def searchFeedback(
|
|
546
|
-
self,
|
|
547
|
-
agentId: AgentId,
|
|
548
|
-
reviewers: Optional[List[Address]] = None,
|
|
549
|
-
tags: Optional[List[str]] = None,
|
|
550
|
-
capabilities: Optional[List[str]] = None,
|
|
551
|
-
skills: Optional[List[str]] = None,
|
|
552
|
-
tasks: Optional[List[str]] = None,
|
|
553
|
-
names: Optional[List[str]] = None,
|
|
554
|
-
minScore: Optional[int] = None,
|
|
555
|
-
maxScore: Optional[int] = None,
|
|
556
|
-
include_revoked: bool = False,
|
|
557
|
-
first: int = 100,
|
|
558
|
-
skip: int = 0,
|
|
559
|
-
) -> List[Feedback]:
|
|
560
|
-
"""Search feedback for an agent."""
|
|
561
|
-
return self.feedback_manager.searchFeedback(
|
|
562
|
-
agentId=agentId,
|
|
563
|
-
clientAddresses=reviewers,
|
|
564
|
-
tags=tags,
|
|
565
|
-
capabilities=capabilities,
|
|
566
|
-
skills=skills,
|
|
567
|
-
tasks=tasks,
|
|
568
|
-
names=names,
|
|
569
|
-
minScore=minScore,
|
|
570
|
-
maxScore=maxScore,
|
|
571
|
-
include_revoked=include_revoked,
|
|
572
|
-
first=first,
|
|
573
|
-
skip=skip
|
|
574
|
-
)
|
|
575
|
-
|
|
576
|
-
def revokeFeedback(
|
|
577
|
-
self,
|
|
578
|
-
feedbackId: str,
|
|
579
|
-
reason: Optional[str] = None,
|
|
580
|
-
idem: Optional[IdemKey] = None,
|
|
581
|
-
) -> Dict[str, Any]:
|
|
582
|
-
"""Revoke feedback."""
|
|
583
|
-
# Parse feedback ID
|
|
584
|
-
agentId, clientAddress, feedbackIndex = Feedback.from_id_string(feedbackId)
|
|
585
|
-
return self.feedback_manager.revokeFeedback(agentId, feedbackIndex)
|
|
586
|
-
|
|
587
|
-
def appendResponse(
|
|
588
|
-
self,
|
|
589
|
-
feedbackId: str,
|
|
590
|
-
response: Dict[str, Any],
|
|
591
|
-
idem: Optional[IdemKey] = None,
|
|
592
|
-
) -> Feedback:
|
|
593
|
-
"""Append a response/follow-up to existing feedback."""
|
|
594
|
-
# Parse feedback ID
|
|
595
|
-
agentId, clientAddress, feedbackIndex = Feedback.from_id_string(feedbackId)
|
|
596
|
-
return self.feedback_manager.appendResponse(agentId, clientAddress, feedbackIndex, response)
|
|
494
|
+
# Feedback methods are defined later in this class (single authoritative API).
|
|
597
495
|
|
|
598
|
-
|
|
599
496
|
def searchAgentsByReputation(
|
|
600
497
|
self,
|
|
601
498
|
agents: Optional[List[AgentId]] = None,
|
|
@@ -605,7 +502,7 @@ class SDK:
|
|
|
605
502
|
skills: Optional[List[str]] = None,
|
|
606
503
|
tasks: Optional[List[str]] = None,
|
|
607
504
|
names: Optional[List[str]] = None,
|
|
608
|
-
|
|
505
|
+
minAverageValue: Optional[float] = None,
|
|
609
506
|
includeRevoked: bool = False,
|
|
610
507
|
page_size: int = 50,
|
|
611
508
|
cursor: Optional[str] = None,
|
|
@@ -625,7 +522,7 @@ class SDK:
|
|
|
625
522
|
return asyncio.run(
|
|
626
523
|
self._search_agents_by_reputation_across_chains(
|
|
627
524
|
agents, tags, reviewers, capabilities, skills, tasks, names,
|
|
628
|
-
|
|
525
|
+
minAverageValue, includeRevoked, page_size, cursor, sort, chains
|
|
629
526
|
)
|
|
630
527
|
)
|
|
631
528
|
|
|
@@ -659,7 +556,7 @@ class SDK:
|
|
|
659
556
|
skills=skills,
|
|
660
557
|
tasks=tasks,
|
|
661
558
|
names=names,
|
|
662
|
-
|
|
559
|
+
minAverageValue=minAverageValue,
|
|
663
560
|
includeRevoked=includeRevoked,
|
|
664
561
|
first=page_size,
|
|
665
562
|
skip=skip,
|
|
@@ -694,7 +591,7 @@ class SDK:
|
|
|
694
591
|
mcpResources=reg_file.get('mcpResources', []),
|
|
695
592
|
active=reg_file.get('active', True),
|
|
696
593
|
x402support=reg_file.get('x402support', False),
|
|
697
|
-
extras={'
|
|
594
|
+
extras={'averageValue': agent_data.get('averageValue')}
|
|
698
595
|
)
|
|
699
596
|
results.append(agent_summary)
|
|
700
597
|
|
|
@@ -713,7 +610,7 @@ class SDK:
|
|
|
713
610
|
skills: Optional[List[str]],
|
|
714
611
|
tasks: Optional[List[str]],
|
|
715
612
|
names: Optional[List[str]],
|
|
716
|
-
|
|
613
|
+
minAverageValue: Optional[float],
|
|
717
614
|
includeRevoked: bool,
|
|
718
615
|
page_size: int,
|
|
719
616
|
cursor: Optional[str],
|
|
@@ -771,7 +668,7 @@ class SDK:
|
|
|
771
668
|
skills=skills,
|
|
772
669
|
tasks=tasks,
|
|
773
670
|
names=names,
|
|
774
|
-
|
|
671
|
+
minAverageValue=minAverageValue,
|
|
775
672
|
includeRevoked=includeRevoked,
|
|
776
673
|
first=page_size * 3, # Fetch extra to allow for filtering/sorting
|
|
777
674
|
skip=0, # We'll handle pagination after aggregation
|
|
@@ -851,14 +748,14 @@ class SDK:
|
|
|
851
748
|
mcpResources=reg_file.get('mcpResources', []),
|
|
852
749
|
active=reg_file.get('active', True),
|
|
853
750
|
x402support=reg_file.get('x402support', False),
|
|
854
|
-
extras={'
|
|
751
|
+
extras={'averageValue': agent_data.get('averageValue')}
|
|
855
752
|
)
|
|
856
753
|
results.append(agent_summary)
|
|
857
754
|
|
|
858
|
-
# Sort by
|
|
755
|
+
# Sort by averageValue (descending) if available, otherwise by createdAt
|
|
859
756
|
results.sort(
|
|
860
757
|
key=lambda x: (
|
|
861
|
-
x.extras.get('
|
|
758
|
+
x.extras.get('averageValue') if x.extras.get('averageValue') is not None else 0,
|
|
862
759
|
x.chainId,
|
|
863
760
|
x.agentId
|
|
864
761
|
),
|
|
@@ -884,46 +781,35 @@ class SDK:
|
|
|
884
781
|
}
|
|
885
782
|
|
|
886
783
|
# Feedback methods - delegate to feedback_manager
|
|
887
|
-
def
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
"""Sign feedback authorization for a client."""
|
|
895
|
-
return self.feedback_manager.signFeedbackAuth(
|
|
896
|
-
agentId, clientAddress, indexLimit, expiryHours
|
|
897
|
-
)
|
|
898
|
-
|
|
899
|
-
def prepareFeedback(
|
|
900
|
-
self,
|
|
901
|
-
agentId: "AgentId",
|
|
902
|
-
score: Optional[int] = None, # 0-100
|
|
903
|
-
tags: List[str] = None,
|
|
904
|
-
text: Optional[str] = None,
|
|
905
|
-
capability: Optional[str] = None,
|
|
906
|
-
name: Optional[str] = None,
|
|
907
|
-
skill: Optional[str] = None,
|
|
908
|
-
task: Optional[str] = None,
|
|
909
|
-
context: Optional[Dict[str, Any]] = None,
|
|
910
|
-
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
911
|
-
extra: Optional[Dict[str, Any]] = None,
|
|
912
|
-
) -> Dict[str, Any]:
|
|
913
|
-
"""Prepare feedback file (local file/object) according to spec."""
|
|
914
|
-
return self.feedback_manager.prepareFeedback(
|
|
915
|
-
agentId, score, tags, text, capability, name, skill, task, context, proofOfPayment, extra
|
|
916
|
-
)
|
|
784
|
+
def prepareFeedbackFile(self, input: Dict[str, Any]) -> Dict[str, Any]:
|
|
785
|
+
"""Prepare an off-chain feedback file payload.
|
|
786
|
+
|
|
787
|
+
This is intentionally off-chain-only; it does not attempt to represent
|
|
788
|
+
the on-chain fields (value/tag1/tag2/endpoint-on-chain).
|
|
789
|
+
"""
|
|
790
|
+
return self.feedback_manager.prepareFeedbackFile(input)
|
|
917
791
|
|
|
918
792
|
def giveFeedback(
|
|
919
793
|
self,
|
|
920
794
|
agentId: "AgentId",
|
|
921
|
-
|
|
922
|
-
|
|
795
|
+
value: Union[int, float, str],
|
|
796
|
+
tag1: Optional[str] = None,
|
|
797
|
+
tag2: Optional[str] = None,
|
|
798
|
+
endpoint: Optional[str] = None,
|
|
799
|
+
feedbackFile: Optional[Dict[str, Any]] = None,
|
|
923
800
|
) -> "Feedback":
|
|
924
|
-
"""Give feedback (
|
|
801
|
+
"""Give feedback (on-chain first; optional off-chain file upload).
|
|
802
|
+
|
|
803
|
+
- If feedbackFile is None: submit on-chain only (no upload even if IPFS is configured).
|
|
804
|
+
- If feedbackFile is provided: requires IPFS configured; uploads and commits URI/hash on-chain.
|
|
805
|
+
"""
|
|
925
806
|
return self.feedback_manager.giveFeedback(
|
|
926
|
-
agentId,
|
|
807
|
+
agentId=agentId,
|
|
808
|
+
value=value,
|
|
809
|
+
tag1=tag1,
|
|
810
|
+
tag2=tag2,
|
|
811
|
+
endpoint=endpoint,
|
|
812
|
+
feedbackFile=feedbackFile,
|
|
927
813
|
)
|
|
928
814
|
|
|
929
815
|
def getFeedback(
|
|
@@ -936,6 +822,37 @@ class SDK:
|
|
|
936
822
|
return self.feedback_manager.getFeedback(
|
|
937
823
|
agentId, clientAddress, feedbackIndex
|
|
938
824
|
)
|
|
825
|
+
|
|
826
|
+
def searchFeedback(
|
|
827
|
+
self,
|
|
828
|
+
agentId: "AgentId",
|
|
829
|
+
reviewers: Optional[List["Address"]] = None,
|
|
830
|
+
tags: Optional[List[str]] = None,
|
|
831
|
+
capabilities: Optional[List[str]] = None,
|
|
832
|
+
skills: Optional[List[str]] = None,
|
|
833
|
+
tasks: Optional[List[str]] = None,
|
|
834
|
+
names: Optional[List[str]] = None,
|
|
835
|
+
minValue: Optional[float] = None,
|
|
836
|
+
maxValue: Optional[float] = None,
|
|
837
|
+
include_revoked: bool = False,
|
|
838
|
+
first: int = 100,
|
|
839
|
+
skip: int = 0,
|
|
840
|
+
) -> List["Feedback"]:
|
|
841
|
+
"""Search feedback for an agent."""
|
|
842
|
+
return self.feedback_manager.searchFeedback(
|
|
843
|
+
agentId=agentId,
|
|
844
|
+
clientAddresses=reviewers,
|
|
845
|
+
tags=tags,
|
|
846
|
+
capabilities=capabilities,
|
|
847
|
+
skills=skills,
|
|
848
|
+
tasks=tasks,
|
|
849
|
+
names=names,
|
|
850
|
+
minValue=minValue,
|
|
851
|
+
maxValue=maxValue,
|
|
852
|
+
include_revoked=include_revoked,
|
|
853
|
+
first=first,
|
|
854
|
+
skip=skip,
|
|
855
|
+
)
|
|
939
856
|
|
|
940
857
|
def revokeFeedback(
|
|
941
858
|
self,
|
|
@@ -260,7 +260,7 @@ class SubgraphClient:
|
|
|
260
260
|
orderDirection: desc
|
|
261
261
|
) {{
|
|
262
262
|
id
|
|
263
|
-
|
|
263
|
+
value
|
|
264
264
|
feedbackIndex
|
|
265
265
|
tag1
|
|
266
266
|
tag2
|
|
@@ -326,8 +326,7 @@ class SubgraphClient:
|
|
|
326
326
|
agentId
|
|
327
327
|
}}
|
|
328
328
|
totalFeedback
|
|
329
|
-
|
|
330
|
-
scoreDistribution
|
|
329
|
+
averageFeedbackValue
|
|
331
330
|
totalValidations
|
|
332
331
|
completedValidations
|
|
333
332
|
averageValidationScore
|
|
@@ -416,7 +415,7 @@ class SubgraphClient:
|
|
|
416
415
|
agent { id agentId chainId }
|
|
417
416
|
clientAddress
|
|
418
417
|
feedbackIndex
|
|
419
|
-
|
|
418
|
+
value
|
|
420
419
|
tag1
|
|
421
420
|
tag2
|
|
422
421
|
endpoint
|
|
@@ -516,11 +515,11 @@ class SubgraphClient:
|
|
|
516
515
|
# Join all tag alternatives (each already contains complete filter set)
|
|
517
516
|
tag_filter_condition = ", ".join([f"{{ {item} }}" for item in tag_where_items])
|
|
518
517
|
|
|
519
|
-
if params.
|
|
520
|
-
where_conditions.append(f'
|
|
518
|
+
if params.minValue is not None:
|
|
519
|
+
where_conditions.append(f'value_gte: "{params.minValue}"')
|
|
521
520
|
|
|
522
|
-
if params.
|
|
523
|
-
where_conditions.append(f'
|
|
521
|
+
if params.maxValue is not None:
|
|
522
|
+
where_conditions.append(f'value_lte: "{params.maxValue}"')
|
|
524
523
|
|
|
525
524
|
# Feedback file filters
|
|
526
525
|
feedback_file_filters = []
|
|
@@ -566,7 +565,7 @@ class SubgraphClient:
|
|
|
566
565
|
agent {{ id agentId chainId }}
|
|
567
566
|
clientAddress
|
|
568
567
|
feedbackIndex
|
|
569
|
-
|
|
568
|
+
value
|
|
570
569
|
tag1
|
|
571
570
|
tag2
|
|
572
571
|
endpoint
|
|
@@ -616,7 +615,7 @@ class SubgraphClient:
|
|
|
616
615
|
skills: Optional[List[str]] = None,
|
|
617
616
|
tasks: Optional[List[str]] = None,
|
|
618
617
|
names: Optional[List[str]] = None,
|
|
619
|
-
|
|
618
|
+
minAverageValue: Optional[float] = None,
|
|
620
619
|
includeRevoked: bool = False,
|
|
621
620
|
first: int = 100,
|
|
622
621
|
skip: int = 0,
|
|
@@ -633,7 +632,7 @@ class SubgraphClient:
|
|
|
633
632
|
capabilities: List of capabilities to filter feedback by
|
|
634
633
|
skills: List of skills to filter feedback by
|
|
635
634
|
tasks: List of tasks to filter feedback by
|
|
636
|
-
|
|
635
|
+
minAverageValue: Minimum average value for included agents
|
|
637
636
|
includeRevoked: Whether to include revoked feedback in calculations
|
|
638
637
|
first: Number of results to return
|
|
639
638
|
skip: Number of results to skip
|
|
@@ -641,7 +640,7 @@ class SubgraphClient:
|
|
|
641
640
|
order_direction: Sort direction (asc/desc)
|
|
642
641
|
|
|
643
642
|
Returns:
|
|
644
|
-
List of agents with
|
|
643
|
+
List of agents with averageValue field calculated from filtered feedback
|
|
645
644
|
"""
|
|
646
645
|
# Build feedback filter
|
|
647
646
|
feedback_filters = []
|
|
@@ -790,7 +789,7 @@ class SubgraphClient:
|
|
|
790
789
|
createdAt
|
|
791
790
|
}}
|
|
792
791
|
feedback(where: {feedback_where_for_agents}) {{
|
|
793
|
-
|
|
792
|
+
value
|
|
794
793
|
isRevoked
|
|
795
794
|
feedbackFile {{
|
|
796
795
|
capability
|
|
@@ -813,37 +812,33 @@ class SubgraphClient:
|
|
|
813
812
|
|
|
814
813
|
agents_result = result.get('agents', [])
|
|
815
814
|
|
|
816
|
-
# Calculate average
|
|
815
|
+
# Calculate average values
|
|
817
816
|
for agent in agents_result:
|
|
818
817
|
feedbacks = agent.get('feedback', [])
|
|
819
818
|
if feedbacks:
|
|
820
|
-
|
|
821
|
-
if
|
|
822
|
-
avg_score = sum(scores) / len(scores)
|
|
823
|
-
agent['averageScore'] = avg_score
|
|
824
|
-
else:
|
|
825
|
-
agent['averageScore'] = None
|
|
819
|
+
values = [float(fb["value"]) for fb in feedbacks if fb.get("value") is not None]
|
|
820
|
+
agent["averageValue"] = (sum(values) / len(values)) if values else None
|
|
826
821
|
else:
|
|
827
|
-
agent[
|
|
822
|
+
agent["averageValue"] = None
|
|
828
823
|
|
|
829
|
-
# Filter by
|
|
830
|
-
if
|
|
824
|
+
# Filter by minAverageValue
|
|
825
|
+
if minAverageValue is not None:
|
|
831
826
|
agents_result = [
|
|
832
827
|
agent for agent in agents_result
|
|
833
|
-
if agent.get(
|
|
828
|
+
if agent.get("averageValue") is not None and agent["averageValue"] >= minAverageValue
|
|
834
829
|
]
|
|
835
830
|
|
|
836
831
|
# For reputation search, filter logic:
|
|
837
|
-
# - If specific agents were requested, return them even if
|
|
832
|
+
# - If specific agents were requested, return them even if averageValue is None
|
|
838
833
|
# (the user explicitly asked for these agents, so return them)
|
|
839
834
|
# - If general search (no specific agents), only return agents with reputation data
|
|
840
835
|
if agents is None or len(agents) == 0:
|
|
841
836
|
# General search - only return agents with reputation
|
|
842
837
|
agents_result = [
|
|
843
838
|
agent for agent in agents_result
|
|
844
|
-
if agent.get(
|
|
839
|
+
if agent.get("averageValue") is not None
|
|
845
840
|
]
|
|
846
|
-
# else: specific agents requested - return all requested agents (even if
|
|
841
|
+
# else: specific agents requested - return all requested agents (even if averageValue is None)
|
|
847
842
|
|
|
848
843
|
return agents_result
|
|
849
844
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Value encoding utilities for ReputationRegistry (Jan 2026).
|
|
3
|
+
|
|
4
|
+
On-chain representation: (value:int256, valueDecimals:uint8)
|
|
5
|
+
Human representation: value / 10^valueDecimals
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from decimal import Decimal, ROUND_HALF_UP, getcontext
|
|
12
|
+
from typing import Tuple, Union
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Plenty of headroom for scaling and clamping checks
|
|
17
|
+
getcontext().prec = 120
|
|
18
|
+
|
|
19
|
+
MAX_DECIMALS = 18
|
|
20
|
+
# Solidity constant (raw int256 magnitude)
|
|
21
|
+
MAX_ABS_VALUE_RAW = 10**50
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encode_feedback_value(input_value: Union[int, float, str, Decimal]) -> Tuple[int, int, str]:
|
|
25
|
+
"""
|
|
26
|
+
Encode a user-facing value into the on-chain (value, valueDecimals) pair.
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
- str: parsed using Decimal (no float casting). If >18 decimals, it is rounded half-up to 18 decimals.
|
|
30
|
+
- float: accepted and rounded half-up to 18 decimals (never rejected).
|
|
31
|
+
- int/Decimal: treated similarly; Decimal preserves precision.
|
|
32
|
+
|
|
33
|
+
Returns: (value_raw:int, value_decimals:int, normalized:str)
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(input_value, Decimal):
|
|
36
|
+
dec = input_value
|
|
37
|
+
normalized = format(dec, "f")
|
|
38
|
+
elif isinstance(input_value, int):
|
|
39
|
+
dec = Decimal(input_value)
|
|
40
|
+
normalized = str(input_value)
|
|
41
|
+
elif isinstance(input_value, float):
|
|
42
|
+
# Avoid binary float artifacts by going through Decimal(str(x)), then quantize to 18 places.
|
|
43
|
+
dec = Decimal(str(input_value)).quantize(Decimal("1e-18"), rounding=ROUND_HALF_UP)
|
|
44
|
+
normalized = format(dec, "f")
|
|
45
|
+
elif isinstance(input_value, str):
|
|
46
|
+
s = input_value.strip()
|
|
47
|
+
if s == "":
|
|
48
|
+
raise ValueError("value cannot be an empty string")
|
|
49
|
+
dec = Decimal(s)
|
|
50
|
+
# Expand to plain decimal string (no exponent) for determining decimals
|
|
51
|
+
normalized = format(dec, "f")
|
|
52
|
+
else:
|
|
53
|
+
raise TypeError(f"value must be int|float|str|Decimal, got {type(input_value)}")
|
|
54
|
+
|
|
55
|
+
# Determine decimals from the normalized representation.
|
|
56
|
+
# This preserves trailing zeros for string inputs like "1.2300".
|
|
57
|
+
if "." in normalized:
|
|
58
|
+
decimals = len(normalized.split(".", 1)[1])
|
|
59
|
+
else:
|
|
60
|
+
decimals = 0
|
|
61
|
+
|
|
62
|
+
if decimals > MAX_DECIMALS:
|
|
63
|
+
dec = dec.quantize(Decimal("1e-18"), rounding=ROUND_HALF_UP)
|
|
64
|
+
normalized = format(dec, "f") # keeps fixed 18 decimals
|
|
65
|
+
decimals = MAX_DECIMALS
|
|
66
|
+
|
|
67
|
+
scale = Decimal(10) ** decimals
|
|
68
|
+
raw_decimal = dec * scale
|
|
69
|
+
raw_int = int(raw_decimal.to_integral_value(rounding=ROUND_HALF_UP))
|
|
70
|
+
|
|
71
|
+
if abs(raw_int) > MAX_ABS_VALUE_RAW:
|
|
72
|
+
raw_int = MAX_ABS_VALUE_RAW if raw_int > 0 else -MAX_ABS_VALUE_RAW
|
|
73
|
+
clamped = Decimal(raw_int) / (Decimal(10) ** decimals)
|
|
74
|
+
normalized = format(clamped, "f")
|
|
75
|
+
logger.warning(
|
|
76
|
+
"Feedback value %r exceeds on-chain max magnitude; clamped to %s (decimals=%s)",
|
|
77
|
+
input_value,
|
|
78
|
+
normalized,
|
|
79
|
+
decimals,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return raw_int, decimals, normalized
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def decode_feedback_value(value_raw: int, value_decimals: int) -> float:
|
|
86
|
+
"""Decode (value, valueDecimals) into a Python float."""
|
|
87
|
+
if value_decimals < 0:
|
|
88
|
+
raise ValueError("valueDecimals cannot be negative")
|
|
89
|
+
return float(Decimal(value_raw) / (Decimal(10) ** int(value_decimals)))
|
|
90
|
+
|
|
91
|
+
|