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.
@@ -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 feedback auth (use provided auth or create new one)
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 = self._stringToBytes32(feedbackFile.get("tag1", ""))
198
- tag2 = self._stringToBytes32(feedbackFile.get("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=[feedbackFile.get("tag1"), feedbackFile.get("tag2")] if feedbackFile.get("tag1") else [],
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
- # Use indexer for subgraph queries (unified search interface)
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
- # Indexer handles subgraph queries for unified search architecture
271
- # This enables future semantic search capabilities
272
- return self.indexer.get_feedback(agentId, clientAddress, feedbackIndex)
273
-
274
- # Fallback: direct subgraph access (if indexer not available)
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
- return self._get_feedback_from_subgraph(agentId, clientAddress, feedbackIndex)
277
-
278
- # Fallback to blockchain (direct contract query)
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 - check if they're hex bytes32 or plain strings
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
- # Convert hex bytes32 to readable tags
329
- if tag1 or tag2:
330
- tags = self._hexBytes32ToTags(
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=self._bytes32ToTags(tag1, tag2),
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 = self._stringToBytes32(tags[0] if tags else "")
459
- tag2_filter = self._stringToBytes32(tags[1] if tags and len(tags) > 1 else "")
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
- feedbackId = Feedback.create_id(agentId, clients[i], i + 1) # Assuming 1-indexed
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=self._bytes32ToTags(tag1s[i], tag2s[i]),
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 - check if they're hex bytes32 or plain strings
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
- # Convert hex bytes32 to readable tags
564
- if tag1 or tag2:
565
- tags_list = self._hexBytes32ToTags(
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 agent ID
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
- tag1_bytes = self._stringToBytes32(tag1) if tag1 else b"\x00" * 32
715
- tag2_bytes = self._stringToBytes32(tag2) if tag2 else b"\x00" * 32
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
- tag1_bytes,
723
- tag2_bytes
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 _stringToBytes32(self, text: str) -> bytes:
852
- """Convert string to bytes32 for blockchain storage."""
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
- if tag2 and tag2 != b"\x00" * 32:
875
- tag2_str = tag2.rstrip(b'\x00').decode('utf-8', errors='ignore')
876
- if tag2_str:
877
- tags.append(tag2_str)
878
-
879
- return tags
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
  """