agent0-sdk 0.2.1__tar.gz → 0.3rc1__tar.gz
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-0.2.1 → agent0_sdk-0.3rc1}/PKG-INFO +3 -5
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/README.md +0 -1
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/__init__.py +1 -1
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/contracts.py +7 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/endpoint_crawler.py +79 -19
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/feedback_manager.py +101 -1
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/indexer.py +750 -12
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/ipfs_client.py +1 -1
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/models.py +3 -2
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/sdk.py +207 -4
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/subgraph_client.py +23 -3
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk.egg-info/PKG-INFO +3 -5
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk.egg-info/SOURCES.txt +2 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk.egg-info/requires.txt +3 -3
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/pyproject.toml +4 -4
- agent0_sdk-0.3rc1/tests/discover_test_data.py +445 -0
- agent0_sdk-0.3rc1/tests/test_multi_chain.py +588 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_search.py +145 -1
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/LICENSE +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/agent.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk/core/web3_client.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk.egg-info/dependency_links.txt +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/agent0_sdk.egg-info/top_level.txt +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/setup.cfg +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/__init__.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/config.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/conftest.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_feedback.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_models.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_real_public_servers.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_registration.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_registrationIpfs.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_sdk.py +0 -0
- {agent0_sdk-0.2.1 → agent0_sdk-0.3rc1}/tests/test_transfer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent0-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3rc1
|
|
4
4
|
Summary: Python SDK for agent portability, discovery and trust based on ERC-8004
|
|
5
5
|
Author-email: Marco De Rossi <marco.derossi@consensys.net>
|
|
6
6
|
License: MIT License
|
|
@@ -47,13 +47,12 @@ Requires-Dist: eth-account>=0.8.0
|
|
|
47
47
|
Requires-Dist: requests>=2.28.0
|
|
48
48
|
Requires-Dist: pydantic>=2.0.0
|
|
49
49
|
Requires-Dist: ipfshttpclient>=0.8.0a2
|
|
50
|
-
Requires-Dist: numpy>=1.21.0
|
|
51
|
-
Requires-Dist: scikit-learn>=1.0.0
|
|
52
|
-
Requires-Dist: sentence-transformers>=2.2.0
|
|
53
50
|
Requires-Dist: aiohttp>=3.8.0
|
|
54
51
|
Requires-Dist: asyncio-throttle>=1.0.0
|
|
55
52
|
Requires-Dist: python-dotenv>=1.0.0
|
|
56
53
|
Requires-Dist: typing-extensions>=4.0.0
|
|
54
|
+
Provides-Extra: indexer
|
|
55
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "indexer"
|
|
57
56
|
Provides-Extra: dev
|
|
58
57
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
59
58
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -275,7 +274,6 @@ agent.register("https://example.com/agent-registration.json")
|
|
|
275
274
|
|
|
276
275
|
- More chains (currently Ethereum Sepolia only)
|
|
277
276
|
- Support for validations
|
|
278
|
-
- Multi-chain agents search
|
|
279
277
|
- Enhanced x402 payments
|
|
280
278
|
- Semantic/Vectorial search
|
|
281
279
|
- Advanced reputation aggregation
|
|
@@ -204,7 +204,6 @@ agent.register("https://example.com/agent-registration.json")
|
|
|
204
204
|
|
|
205
205
|
- More chains (currently Ethereum Sepolia only)
|
|
206
206
|
- Support for validations
|
|
207
|
-
- Multi-chain agents search
|
|
208
207
|
- Enhanced x402 payments
|
|
209
208
|
- Semantic/Vectorial search
|
|
210
209
|
- Advanced reputation aggregation
|
|
@@ -477,6 +477,11 @@ DEFAULT_REGISTRIES: Dict[int, Dict[str, str]] = {
|
|
|
477
477
|
"REPUTATION": "0x8004bd8daB57f14Ed299135749a5CB5c42d341BF",
|
|
478
478
|
"VALIDATION": "0x8004C269D0A5647E51E121FeB226200ECE932d55",
|
|
479
479
|
},
|
|
480
|
+
80002: { # Polygon Amoy
|
|
481
|
+
"IDENTITY": "0x8004ad19E14B9e0654f73353e8a0B600D46C2898",
|
|
482
|
+
"REPUTATION": "0x8004B12F4C2B42d00c46479e859C92e39044C930",
|
|
483
|
+
"VALIDATION": "0x8004C11C213ff7BaD36489bcBDF947ba5eee289B",
|
|
484
|
+
},
|
|
480
485
|
59141: { # Linea Sepolia
|
|
481
486
|
"IDENTITY": "0x8004aa7C931bCE1233973a0C6A667f73F66282e7",
|
|
482
487
|
"REPUTATION": "0x8004bd8483b99310df121c46ED8858616b2Bba02",
|
|
@@ -487,4 +492,6 @@ DEFAULT_REGISTRIES: Dict[int, Dict[str, str]] = {
|
|
|
487
492
|
# Default subgraph URLs for different chains
|
|
488
493
|
DEFAULT_SUBGRAPH_URLS: Dict[int, str] = {
|
|
489
494
|
11155111: "https://gateway.thegraph.com/api/00a452ad3cd1900273ea62c1bf283f93/subgraphs/id/6wQRC7geo9XYAhckfmfo8kbMRLeWU8KQd3XsJqFKmZLT", # Ethereum Sepolia
|
|
495
|
+
84532: "https://gateway.thegraph.com/api/00a452ad3cd1900273ea62c1bf283f93/subgraphs/id/GjQEDgEKqoh5Yc8MUgxoQoRATEJdEiH7HbocfR1aFiHa", # Base Sepolia
|
|
496
|
+
80002: "https://gateway.thegraph.com/api/00a452ad3cd1900273ea62c1bf283f93/subgraphs/id/2A1JB18r1mF2VNP4QBH4mmxd74kbHoM6xLXC8ABAKf7j", # Polygon Amoy
|
|
490
497
|
}
|
|
@@ -178,13 +178,13 @@ class EndpointCrawler:
|
|
|
178
178
|
def fetch_a2a_capabilities(self, endpoint: str) -> Optional[Dict[str, Any]]:
|
|
179
179
|
"""
|
|
180
180
|
Fetch A2A capabilities (skills) from an A2A server.
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
A2A Protocol uses agent cards to describe agent capabilities.
|
|
183
|
-
Tries multiple well-known paths: agentcard.json, .well-known/agent.json
|
|
184
|
-
|
|
183
|
+
Tries multiple well-known paths: agentcard.json, .well-known/agent.json, .well-known/agent-card.json
|
|
184
|
+
|
|
185
185
|
Args:
|
|
186
186
|
endpoint: A2A endpoint URL (must be http:// or https://)
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
Returns:
|
|
189
189
|
Dict with key: 'a2aSkills'
|
|
190
190
|
Returns None if unable to fetch
|
|
@@ -194,37 +194,97 @@ class EndpointCrawler:
|
|
|
194
194
|
if not endpoint.startswith(('http://', 'https://')):
|
|
195
195
|
logger.warning(f"A2A endpoint must be HTTP/HTTPS, got: {endpoint}")
|
|
196
196
|
return None
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
# Try multiple well-known paths for A2A agent cards
|
|
199
|
+
# Per ERC-8004, endpoint may already be full URL to agent card
|
|
200
|
+
# Per A2A spec section 5.3, recommended discovery path is /.well-known/agent-card.json
|
|
199
201
|
agentcard_urls = [
|
|
200
|
-
|
|
201
|
-
f"{endpoint}/.well-known/agent.json",
|
|
202
|
-
f"{endpoint.rstrip('/')}/.well-known/agent.json"
|
|
202
|
+
endpoint, # Try exact URL first (ERC-8004 format: full path to agent card)
|
|
203
|
+
f"{endpoint}/.well-known/agent-card.json", # Spec-recommended discovery path
|
|
204
|
+
f"{endpoint.rstrip('/')}/.well-known/agent-card.json",
|
|
205
|
+
f"{endpoint}/.well-known/agent.json", # Alternative well-known path
|
|
206
|
+
f"{endpoint.rstrip('/')}/.well-known/agent.json",
|
|
207
|
+
f"{endpoint}/agentcard.json" # Legacy path
|
|
203
208
|
]
|
|
204
|
-
|
|
209
|
+
|
|
205
210
|
for agentcard_url in agentcard_urls:
|
|
206
211
|
logger.debug(f"Attempting to fetch A2A capabilities from {agentcard_url}")
|
|
207
|
-
|
|
212
|
+
|
|
208
213
|
try:
|
|
209
214
|
response = requests.get(agentcard_url, timeout=self.timeout, allow_redirects=True)
|
|
210
|
-
|
|
215
|
+
|
|
211
216
|
if response.status_code == 200:
|
|
212
217
|
data = response.json()
|
|
213
|
-
|
|
214
|
-
# Extract
|
|
215
|
-
skills = self.
|
|
216
|
-
|
|
218
|
+
|
|
219
|
+
# Extract skill tags from agentcard
|
|
220
|
+
skills = self._extract_a2a_skills(data)
|
|
221
|
+
|
|
217
222
|
if skills:
|
|
218
|
-
logger.info(f"Successfully fetched A2A capabilities from {
|
|
223
|
+
logger.info(f"Successfully fetched A2A capabilities from {agentcard_url}: {len(skills)} skills")
|
|
219
224
|
return {'a2aSkills': skills}
|
|
220
|
-
except requests.exceptions.RequestException:
|
|
225
|
+
except requests.exceptions.RequestException as e:
|
|
221
226
|
# Try next URL
|
|
227
|
+
logger.debug(f"Failed to fetch from {agentcard_url}: {e}")
|
|
222
228
|
continue
|
|
223
|
-
|
|
229
|
+
|
|
224
230
|
except Exception as e:
|
|
225
231
|
logger.debug(f"Unexpected error fetching A2A capabilities from {endpoint}: {e}")
|
|
226
|
-
|
|
232
|
+
|
|
227
233
|
return None
|
|
234
|
+
|
|
235
|
+
def _extract_a2a_skills(self, data: Dict[str, Any]) -> List[str]:
|
|
236
|
+
"""
|
|
237
|
+
Extract skill tags from A2A agent card.
|
|
238
|
+
|
|
239
|
+
Per A2A Protocol spec (v0.3.0), agent cards should have:
|
|
240
|
+
skills: AgentSkill[] where each AgentSkill has a tags[] array
|
|
241
|
+
|
|
242
|
+
This method also handles non-standard formats for backward compatibility:
|
|
243
|
+
- detailedSkills[].tags[] (custom extension)
|
|
244
|
+
- skills: ["tag1", "tag2"] (non-compliant flat array)
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
data: Agent card JSON data
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of unique skill tags (strings)
|
|
251
|
+
"""
|
|
252
|
+
result = []
|
|
253
|
+
|
|
254
|
+
# Try spec-compliant format first: skills[].tags[]
|
|
255
|
+
if 'skills' in data and isinstance(data['skills'], list):
|
|
256
|
+
for skill in data['skills']:
|
|
257
|
+
if isinstance(skill, dict) and 'tags' in skill:
|
|
258
|
+
# Spec-compliant: AgentSkill object with tags
|
|
259
|
+
tags = skill['tags']
|
|
260
|
+
if isinstance(tags, list):
|
|
261
|
+
for tag in tags:
|
|
262
|
+
if isinstance(tag, str):
|
|
263
|
+
result.append(tag)
|
|
264
|
+
elif isinstance(skill, str):
|
|
265
|
+
# Non-compliant: flat string array (fallback)
|
|
266
|
+
result.append(skill)
|
|
267
|
+
|
|
268
|
+
# Fallback to detailedSkills if no tags found in skills
|
|
269
|
+
# (custom extension used by some implementations)
|
|
270
|
+
if not result and 'detailedSkills' in data and isinstance(data['detailedSkills'], list):
|
|
271
|
+
for skill in data['detailedSkills']:
|
|
272
|
+
if isinstance(skill, dict) and 'tags' in skill:
|
|
273
|
+
tags = skill['tags']
|
|
274
|
+
if isinstance(tags, list):
|
|
275
|
+
for tag in tags:
|
|
276
|
+
if isinstance(tag, str):
|
|
277
|
+
result.append(tag)
|
|
278
|
+
|
|
279
|
+
# Remove duplicates while preserving order
|
|
280
|
+
seen = set()
|
|
281
|
+
unique_result = []
|
|
282
|
+
for item in result:
|
|
283
|
+
if item not in seen:
|
|
284
|
+
seen.add(item)
|
|
285
|
+
unique_result.append(item)
|
|
286
|
+
|
|
287
|
+
return unique_result
|
|
228
288
|
|
|
229
289
|
def _extract_list(self, data: Dict[str, Any], key: str) -> List[str]:
|
|
230
290
|
"""
|
|
@@ -703,7 +703,46 @@ class FeedbackManager:
|
|
|
703
703
|
groupBy: Optional[List[str]] = None,
|
|
704
704
|
) -> Dict[str, Any]:
|
|
705
705
|
"""Get reputation summary for an agent with optional grouping."""
|
|
706
|
-
# Parse
|
|
706
|
+
# Parse chainId from agentId
|
|
707
|
+
chain_id = None
|
|
708
|
+
if ":" in agentId:
|
|
709
|
+
try:
|
|
710
|
+
chain_id = int(agentId.split(":", 1)[0])
|
|
711
|
+
except ValueError:
|
|
712
|
+
chain_id = None
|
|
713
|
+
|
|
714
|
+
# Try subgraph first (if available and indexer supports it)
|
|
715
|
+
if self.indexer and self.subgraph_client:
|
|
716
|
+
# Get correct subgraph client for the chain
|
|
717
|
+
subgraph_client = None
|
|
718
|
+
full_agent_id = agentId
|
|
719
|
+
|
|
720
|
+
if chain_id is not None:
|
|
721
|
+
subgraph_client = self.indexer._get_subgraph_client_for_chain(chain_id)
|
|
722
|
+
else:
|
|
723
|
+
# No chainId in agentId, use SDK's default
|
|
724
|
+
# Construct full agentId format for subgraph query
|
|
725
|
+
default_chain_id = self.web3_client.chain_id
|
|
726
|
+
token_id = agentId.split(":")[-1] if ":" in agentId else agentId
|
|
727
|
+
full_agent_id = f"{default_chain_id}:{token_id}"
|
|
728
|
+
subgraph_client = self.subgraph_client
|
|
729
|
+
|
|
730
|
+
if subgraph_client:
|
|
731
|
+
# Use subgraph to calculate reputation
|
|
732
|
+
return self._get_reputation_summary_from_subgraph(
|
|
733
|
+
full_agent_id, clientAddresses, tag1, tag2, groupBy
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
# Fallback to blockchain (requires chain-specific web3 client)
|
|
737
|
+
# For now, only works if chain matches SDK's default
|
|
738
|
+
if chain_id is not None and chain_id != self.web3_client.chain_id:
|
|
739
|
+
raise ValueError(
|
|
740
|
+
f"Blockchain reputation summary not supported for chain {chain_id}. "
|
|
741
|
+
f"SDK is configured for chain {self.web3_client.chain_id}. "
|
|
742
|
+
f"Use subgraph-based summary instead."
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
# Parse agent ID for blockchain call
|
|
707
746
|
if ":" in agentId:
|
|
708
747
|
tokenId = int(agentId.split(":")[-1])
|
|
709
748
|
else:
|
|
@@ -765,6 +804,67 @@ class FeedbackManager:
|
|
|
765
804
|
except Exception as e:
|
|
766
805
|
raise ValueError(f"Failed to get reputation summary: {e}")
|
|
767
806
|
|
|
807
|
+
def _get_reputation_summary_from_subgraph(
|
|
808
|
+
self,
|
|
809
|
+
agentId: AgentId,
|
|
810
|
+
clientAddresses: Optional[List[Address]] = None,
|
|
811
|
+
tag1: Optional[str] = None,
|
|
812
|
+
tag2: Optional[str] = None,
|
|
813
|
+
groupBy: Optional[List[str]] = None,
|
|
814
|
+
) -> Dict[str, Any]:
|
|
815
|
+
"""Get reputation summary from subgraph."""
|
|
816
|
+
# Build tags list
|
|
817
|
+
tags = []
|
|
818
|
+
if tag1:
|
|
819
|
+
tags.append(tag1)
|
|
820
|
+
if tag2:
|
|
821
|
+
tags.append(tag2)
|
|
822
|
+
|
|
823
|
+
# Get all feedback for the agent using indexer (which handles multi-chain)
|
|
824
|
+
# Use searchFeedback with a large limit to get all feedback
|
|
825
|
+
all_feedback = self.searchFeedback(
|
|
826
|
+
agentId=agentId,
|
|
827
|
+
clientAddresses=clientAddresses,
|
|
828
|
+
tags=tags if tags else None,
|
|
829
|
+
include_revoked=False,
|
|
830
|
+
first=1000, # Large limit to get all feedback
|
|
831
|
+
skip=0
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
# Calculate summary statistics
|
|
835
|
+
count = len(all_feedback)
|
|
836
|
+
scores = [fb.score for fb in all_feedback if fb.score is not None]
|
|
837
|
+
average_score = sum(scores) / len(scores) if scores else 0.0
|
|
838
|
+
|
|
839
|
+
# If no grouping requested, return simple summary
|
|
840
|
+
if not groupBy:
|
|
841
|
+
return {
|
|
842
|
+
"agentId": agentId,
|
|
843
|
+
"count": count,
|
|
844
|
+
"averageScore": average_score,
|
|
845
|
+
"filters": {
|
|
846
|
+
"clientAddresses": clientAddresses,
|
|
847
|
+
"tag1": tag1,
|
|
848
|
+
"tag2": tag2
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
# Group feedback by requested dimensions
|
|
853
|
+
grouped_data = self._groupFeedback(all_feedback, groupBy)
|
|
854
|
+
|
|
855
|
+
return {
|
|
856
|
+
"agentId": agentId,
|
|
857
|
+
"totalCount": count,
|
|
858
|
+
"totalAverageScore": average_score,
|
|
859
|
+
"groupedData": grouped_data,
|
|
860
|
+
"filters": {
|
|
861
|
+
"clientAddresses": clientAddresses,
|
|
862
|
+
"tag1": tag1,
|
|
863
|
+
"tag2": tag2
|
|
864
|
+
},
|
|
865
|
+
"groupBy": groupBy
|
|
866
|
+
}
|
|
867
|
+
|
|
768
868
|
def _groupFeedback(self, feedbackList: List[Feedback], groupBy: List[str]) -> Dict[str, Any]:
|
|
769
869
|
"""Group feedback by specified dimensions."""
|
|
770
870
|
grouped = {}
|