agent0-sdk 1.4.2__py3-none-any.whl → 1.5.1b1__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 +7 -3
- agent0_sdk/core/contracts.py +1 -0
- agent0_sdk/core/feedback_manager.py +75 -78
- agent0_sdk/core/indexer.py +645 -635
- agent0_sdk/core/models.py +91 -12
- agent0_sdk/core/sdk.py +26 -314
- agent0_sdk/core/semantic_search_client.py +70 -0
- agent0_sdk/core/subgraph_client.py +182 -239
- {agent0_sdk-1.4.2.dist-info → agent0_sdk-1.5.1b1.dist-info}/METADATA +163 -9
- agent0_sdk-1.5.1b1.dist-info/RECORD +22 -0
- agent0_sdk-1.4.2.dist-info/RECORD +0 -21
- {agent0_sdk-1.4.2.dist-info → agent0_sdk-1.5.1b1.dist-info}/WHEEL +0 -0
- {agent0_sdk-1.4.2.dist-info → agent0_sdk-1.5.1b1.dist-info}/licenses/LICENSE +0 -0
- {agent0_sdk-1.4.2.dist-info → agent0_sdk-1.5.1b1.dist-info}/top_level.txt +0 -0
agent0_sdk/core/models.py
CHANGED
|
@@ -165,8 +165,6 @@ class AgentSummary:
|
|
|
165
165
|
description: str
|
|
166
166
|
owners: List[Address]
|
|
167
167
|
operators: List[Address]
|
|
168
|
-
mcp: bool
|
|
169
|
-
a2a: bool
|
|
170
168
|
ens: Optional[str]
|
|
171
169
|
did: Optional[str]
|
|
172
170
|
walletAddress: Optional[Address]
|
|
@@ -176,7 +174,22 @@ class AgentSummary:
|
|
|
176
174
|
mcpPrompts: List[str]
|
|
177
175
|
mcpResources: List[str]
|
|
178
176
|
active: bool
|
|
177
|
+
# Endpoint strings (new unified search + Jan 2026 schema)
|
|
178
|
+
mcp: Optional[str] = None
|
|
179
|
+
a2a: Optional[str] = None
|
|
180
|
+
web: Optional[str] = None
|
|
181
|
+
email: Optional[str] = None
|
|
182
|
+
oasfSkills: List[str] = field(default_factory=list)
|
|
183
|
+
oasfDomains: List[str] = field(default_factory=list)
|
|
179
184
|
x402support: bool = False
|
|
185
|
+
createdAt: Optional[int] = None
|
|
186
|
+
updatedAt: Optional[int] = None
|
|
187
|
+
lastActivity: Optional[int] = None
|
|
188
|
+
agentURI: Optional[str] = None
|
|
189
|
+
agentURIType: Optional[str] = None
|
|
190
|
+
feedbackCount: Optional[int] = None
|
|
191
|
+
averageValue: Optional[float] = None
|
|
192
|
+
semanticScore: Optional[float] = None
|
|
180
193
|
extras: Dict[str, Any] = field(default_factory=dict)
|
|
181
194
|
|
|
182
195
|
|
|
@@ -257,32 +270,98 @@ class Feedback:
|
|
|
257
270
|
|
|
258
271
|
|
|
259
272
|
@dataclass
|
|
260
|
-
class
|
|
261
|
-
|
|
273
|
+
class FeedbackFilters:
|
|
274
|
+
hasFeedback: Optional[bool] = None
|
|
275
|
+
hasNoFeedback: Optional[bool] = None
|
|
276
|
+
includeRevoked: Optional[bool] = None
|
|
277
|
+
minValue: Optional[float] = None
|
|
278
|
+
maxValue: Optional[float] = None
|
|
279
|
+
minCount: Optional[int] = None
|
|
280
|
+
maxCount: Optional[int] = None
|
|
281
|
+
fromReviewers: Optional[List[Address]] = None
|
|
282
|
+
endpoint: Optional[str] = None
|
|
283
|
+
hasResponse: Optional[bool] = None
|
|
284
|
+
tag1: Optional[str] = None
|
|
285
|
+
tag2: Optional[str] = None
|
|
286
|
+
tag: Optional[str] = None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
DateLike = Union[datetime, str, int]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@dataclass
|
|
293
|
+
class SearchFilters:
|
|
294
|
+
# Chain / identity
|
|
262
295
|
chains: Optional[Union[List[ChainId], Literal["all"]]] = None
|
|
263
|
-
|
|
264
|
-
|
|
296
|
+
agentIds: Optional[List[AgentId]] = None
|
|
297
|
+
|
|
298
|
+
# Text
|
|
299
|
+
name: Optional[str] = None
|
|
300
|
+
description: Optional[str] = None
|
|
301
|
+
|
|
302
|
+
# Owners / operators
|
|
265
303
|
owners: Optional[List[Address]] = None
|
|
266
304
|
operators: Optional[List[Address]] = None
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
305
|
+
|
|
306
|
+
# Endpoint existence
|
|
307
|
+
hasRegistrationFile: Optional[bool] = None
|
|
308
|
+
hasWeb: Optional[bool] = None
|
|
309
|
+
hasMCP: Optional[bool] = None
|
|
310
|
+
hasA2A: Optional[bool] = None
|
|
311
|
+
hasOASF: Optional[bool] = None
|
|
312
|
+
hasEndpoints: Optional[bool] = None
|
|
313
|
+
|
|
314
|
+
# Endpoint substring contains
|
|
315
|
+
webContains: Optional[str] = None
|
|
316
|
+
mcpContains: Optional[str] = None
|
|
317
|
+
a2aContains: Optional[str] = None
|
|
318
|
+
ensContains: Optional[str] = None
|
|
319
|
+
didContains: Optional[str] = None
|
|
320
|
+
|
|
321
|
+
# Wallet
|
|
271
322
|
walletAddress: Optional[Address] = None
|
|
323
|
+
|
|
324
|
+
# Capability arrays (ANY semantics)
|
|
272
325
|
supportedTrust: Optional[List[str]] = None
|
|
273
326
|
a2aSkills: Optional[List[str]] = None
|
|
274
327
|
mcpTools: Optional[List[str]] = None
|
|
275
328
|
mcpPrompts: Optional[List[str]] = None
|
|
276
329
|
mcpResources: Optional[List[str]] = None
|
|
277
|
-
|
|
330
|
+
oasfSkills: Optional[List[str]] = None
|
|
331
|
+
oasfDomains: Optional[List[str]] = None
|
|
332
|
+
|
|
333
|
+
# Status
|
|
334
|
+
active: Optional[bool] = None
|
|
278
335
|
x402support: Optional[bool] = None
|
|
279
|
-
|
|
336
|
+
|
|
337
|
+
# Time filters
|
|
338
|
+
registeredAtFrom: Optional[DateLike] = None
|
|
339
|
+
registeredAtTo: Optional[DateLike] = None
|
|
340
|
+
updatedAtFrom: Optional[DateLike] = None
|
|
341
|
+
updatedAtTo: Optional[DateLike] = None
|
|
342
|
+
|
|
343
|
+
# Metadata filters (two-phase)
|
|
344
|
+
hasMetadataKey: Optional[str] = None
|
|
345
|
+
metadataValue: Optional[Dict[str, str]] = None # { key, value }
|
|
346
|
+
|
|
347
|
+
# Semantic search
|
|
348
|
+
keyword: Optional[str] = None
|
|
349
|
+
|
|
350
|
+
# Feedback filters (two-phase)
|
|
351
|
+
feedback: Optional[FeedbackFilters] = None
|
|
280
352
|
|
|
281
353
|
def to_dict(self) -> Dict[str, Any]:
|
|
282
354
|
"""Convert to dictionary, filtering out None values."""
|
|
283
355
|
return {k: v for k, v in self.__dict__.items() if v is not None}
|
|
284
356
|
|
|
285
357
|
|
|
358
|
+
@dataclass
|
|
359
|
+
class SearchOptions:
|
|
360
|
+
sort: Optional[List[str]] = None
|
|
361
|
+
semanticMinScore: Optional[float] = None
|
|
362
|
+
semanticTopK: Optional[int] = None
|
|
363
|
+
|
|
364
|
+
|
|
286
365
|
@dataclass
|
|
287
366
|
class SearchFeedbackParams:
|
|
288
367
|
"""Parameters for feedback search."""
|
agent0_sdk/core/sdk.py
CHANGED
|
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
from .models import (
|
|
17
17
|
AgentId, ChainId, Address, URI, Timestamp, IdemKey,
|
|
18
18
|
EndpointType, TrustModel, Endpoint, RegistrationFile,
|
|
19
|
-
AgentSummary, Feedback,
|
|
19
|
+
AgentSummary, Feedback, SearchFilters, SearchOptions, FeedbackFilters
|
|
20
20
|
)
|
|
21
21
|
from .web3_client import Web3Client
|
|
22
22
|
from .contracts import (
|
|
@@ -460,12 +460,10 @@ class SDK:
|
|
|
460
460
|
|
|
461
461
|
def searchAgents(
|
|
462
462
|
self,
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
page_size: int = 50,
|
|
466
|
-
cursor: Optional[str] = None,
|
|
463
|
+
filters: Union[SearchFilters, Dict[str, Any], None] = None,
|
|
464
|
+
options: Union[SearchOptions, Dict[str, Any], None] = None,
|
|
467
465
|
**kwargs # Accept search criteria as kwargs for better DX
|
|
468
|
-
) ->
|
|
466
|
+
) -> List[AgentSummary]:
|
|
469
467
|
"""Search for agents.
|
|
470
468
|
|
|
471
469
|
Examples:
|
|
@@ -475,313 +473,31 @@ class SDK:
|
|
|
475
473
|
|
|
476
474
|
# Explicit SearchParams (for complex queries or IDE autocomplete)
|
|
477
475
|
sdk.searchAgents(SearchParams(name="Test", mcpTools=["code_generation"]))
|
|
478
|
-
|
|
479
|
-
# With pagination
|
|
480
|
-
sdk.searchAgents(name="Test", page_size=10)
|
|
481
476
|
"""
|
|
482
|
-
#
|
|
483
|
-
if kwargs and
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
elif
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
477
|
+
# Allow kwargs to populate filters for better DX.
|
|
478
|
+
if kwargs and filters is None:
|
|
479
|
+
if isinstance(kwargs.get("feedback"), dict):
|
|
480
|
+
kwargs["feedback"] = FeedbackFilters(**kwargs["feedback"])
|
|
481
|
+
filters = SearchFilters(**kwargs)
|
|
482
|
+
elif filters is None:
|
|
483
|
+
filters = SearchFilters()
|
|
484
|
+
elif isinstance(filters, dict):
|
|
485
|
+
if isinstance(filters.get("feedback"), dict):
|
|
486
|
+
filters["feedback"] = FeedbackFilters(**filters["feedback"])
|
|
487
|
+
filters = SearchFilters(**filters)
|
|
488
|
+
|
|
489
|
+
if options is None:
|
|
490
|
+
options = SearchOptions()
|
|
491
|
+
elif isinstance(options, dict):
|
|
492
|
+
options = SearchOptions(**options)
|
|
493
|
+
|
|
494
|
+
# Do not force a default sort here; the indexer chooses keyword-aware defaults.
|
|
495
|
+
out = self.indexer.search_agents(filters, options)
|
|
496
|
+
if isinstance(out, dict):
|
|
497
|
+
return out.get("items") or []
|
|
498
|
+
return out or []
|
|
496
499
|
|
|
497
500
|
# Feedback methods are defined later in this class (single authoritative API).
|
|
498
|
-
|
|
499
|
-
def searchAgentsByReputation(
|
|
500
|
-
self,
|
|
501
|
-
agents: Optional[List[AgentId]] = None,
|
|
502
|
-
tags: Optional[List[str]] = None,
|
|
503
|
-
reviewers: Optional[List[Address]] = None,
|
|
504
|
-
capabilities: Optional[List[str]] = None,
|
|
505
|
-
skills: Optional[List[str]] = None,
|
|
506
|
-
tasks: Optional[List[str]] = None,
|
|
507
|
-
names: Optional[List[str]] = None,
|
|
508
|
-
minAverageValue: Optional[float] = None,
|
|
509
|
-
includeRevoked: bool = False,
|
|
510
|
-
page_size: int = 50,
|
|
511
|
-
cursor: Optional[str] = None,
|
|
512
|
-
sort: Optional[List[str]] = None,
|
|
513
|
-
chains: Optional[Union[List[ChainId], Literal["all"]]] = None,
|
|
514
|
-
) -> Dict[str, Any]:
|
|
515
|
-
"""Search agents filtered by reputation criteria."""
|
|
516
|
-
# Handle multi-chain search
|
|
517
|
-
if chains:
|
|
518
|
-
# Expand "all" if needed
|
|
519
|
-
if chains == "all":
|
|
520
|
-
chains = self.indexer._get_all_configured_chains()
|
|
521
|
-
|
|
522
|
-
# If multiple chains or single chain different from default
|
|
523
|
-
if isinstance(chains, list) and len(chains) > 0:
|
|
524
|
-
if len(chains) > 1 or (len(chains) == 1 and chains[0] != self.chainId):
|
|
525
|
-
return asyncio.run(
|
|
526
|
-
self._search_agents_by_reputation_across_chains(
|
|
527
|
-
agents, tags, reviewers, capabilities, skills, tasks, names,
|
|
528
|
-
minAverageValue, includeRevoked, page_size, cursor, sort, chains
|
|
529
|
-
)
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
# Single chain search (existing behavior)
|
|
533
|
-
if not self.subgraph_client:
|
|
534
|
-
raise ValueError("Subgraph client required for searchAgentsByReputation")
|
|
535
|
-
|
|
536
|
-
if sort is None:
|
|
537
|
-
sort = ["createdAt:desc"]
|
|
538
|
-
|
|
539
|
-
skip = 0
|
|
540
|
-
if cursor:
|
|
541
|
-
try:
|
|
542
|
-
skip = int(cursor)
|
|
543
|
-
except ValueError:
|
|
544
|
-
skip = 0
|
|
545
|
-
|
|
546
|
-
order_by = "createdAt"
|
|
547
|
-
order_direction = "desc"
|
|
548
|
-
if sort and len(sort) > 0:
|
|
549
|
-
sort_field = sort[0].split(":")
|
|
550
|
-
order_by = sort_field[0] if len(sort_field) >= 1 else order_by
|
|
551
|
-
order_direction = sort_field[1] if len(sort_field) >= 2 else order_direction
|
|
552
|
-
|
|
553
|
-
try:
|
|
554
|
-
agents_data = self.subgraph_client.search_agents_by_reputation(
|
|
555
|
-
agents=agents,
|
|
556
|
-
tags=tags,
|
|
557
|
-
reviewers=reviewers,
|
|
558
|
-
capabilities=capabilities,
|
|
559
|
-
skills=skills,
|
|
560
|
-
tasks=tasks,
|
|
561
|
-
names=names,
|
|
562
|
-
minAverageValue=minAverageValue,
|
|
563
|
-
includeRevoked=includeRevoked,
|
|
564
|
-
first=page_size,
|
|
565
|
-
skip=skip,
|
|
566
|
-
order_by=order_by,
|
|
567
|
-
order_direction=order_direction
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
from .models import AgentSummary
|
|
571
|
-
results = []
|
|
572
|
-
for agent_data in agents_data:
|
|
573
|
-
reg_file = agent_data.get('registrationFile') or {}
|
|
574
|
-
if not isinstance(reg_file, dict):
|
|
575
|
-
reg_file = {}
|
|
576
|
-
|
|
577
|
-
agent_summary = AgentSummary(
|
|
578
|
-
chainId=int(agent_data.get('chainId', 0)),
|
|
579
|
-
agentId=agent_data.get('id'),
|
|
580
|
-
name=reg_file.get('name', f"Agent {agent_data.get('id')}"),
|
|
581
|
-
image=reg_file.get('image'),
|
|
582
|
-
description=reg_file.get('description', ''),
|
|
583
|
-
owners=[agent_data.get('owner', '')],
|
|
584
|
-
operators=agent_data.get('operators', []),
|
|
585
|
-
mcp=reg_file.get('mcpEndpoint') is not None,
|
|
586
|
-
a2a=reg_file.get('a2aEndpoint') is not None,
|
|
587
|
-
ens=reg_file.get('ens'),
|
|
588
|
-
did=reg_file.get('did'),
|
|
589
|
-
walletAddress=reg_file.get('agentWallet'),
|
|
590
|
-
supportedTrusts=reg_file.get('supportedTrusts', []),
|
|
591
|
-
a2aSkills=reg_file.get('a2aSkills', []),
|
|
592
|
-
mcpTools=reg_file.get('mcpTools', []),
|
|
593
|
-
mcpPrompts=reg_file.get('mcpPrompts', []),
|
|
594
|
-
mcpResources=reg_file.get('mcpResources', []),
|
|
595
|
-
active=reg_file.get('active', True),
|
|
596
|
-
x402support=reg_file.get('x402Support', reg_file.get('x402support', False)),
|
|
597
|
-
extras={'averageValue': agent_data.get('averageValue')}
|
|
598
|
-
)
|
|
599
|
-
results.append(agent_summary)
|
|
600
|
-
|
|
601
|
-
next_cursor = str(skip + len(results)) if len(results) == page_size else None
|
|
602
|
-
return {"items": results, "nextCursor": next_cursor}
|
|
603
|
-
|
|
604
|
-
except Exception as e:
|
|
605
|
-
raise ValueError(f"Failed to search agents by reputation: {e}")
|
|
606
|
-
|
|
607
|
-
async def _search_agents_by_reputation_across_chains(
|
|
608
|
-
self,
|
|
609
|
-
agents: Optional[List[AgentId]],
|
|
610
|
-
tags: Optional[List[str]],
|
|
611
|
-
reviewers: Optional[List[Address]],
|
|
612
|
-
capabilities: Optional[List[str]],
|
|
613
|
-
skills: Optional[List[str]],
|
|
614
|
-
tasks: Optional[List[str]],
|
|
615
|
-
names: Optional[List[str]],
|
|
616
|
-
minAverageValue: Optional[float],
|
|
617
|
-
includeRevoked: bool,
|
|
618
|
-
page_size: int,
|
|
619
|
-
cursor: Optional[str],
|
|
620
|
-
sort: Optional[List[str]],
|
|
621
|
-
chains: List[ChainId],
|
|
622
|
-
) -> Dict[str, Any]:
|
|
623
|
-
"""
|
|
624
|
-
Search agents by reputation across multiple chains in parallel.
|
|
625
|
-
|
|
626
|
-
Similar to indexer._search_agents_across_chains() but for reputation-based search.
|
|
627
|
-
"""
|
|
628
|
-
import time
|
|
629
|
-
start_time = time.time()
|
|
630
|
-
|
|
631
|
-
if sort is None:
|
|
632
|
-
sort = ["createdAt:desc"]
|
|
633
|
-
|
|
634
|
-
order_by = "createdAt"
|
|
635
|
-
order_direction = "desc"
|
|
636
|
-
if sort and len(sort) > 0:
|
|
637
|
-
sort_field = sort[0].split(":")
|
|
638
|
-
order_by = sort_field[0] if len(sort_field) >= 1 else order_by
|
|
639
|
-
order_direction = sort_field[1] if len(sort_field) >= 2 else order_direction
|
|
640
|
-
|
|
641
|
-
skip = 0
|
|
642
|
-
if cursor:
|
|
643
|
-
try:
|
|
644
|
-
skip = int(cursor)
|
|
645
|
-
except ValueError:
|
|
646
|
-
skip = 0
|
|
647
|
-
|
|
648
|
-
# Define async function for querying a single chain
|
|
649
|
-
async def query_single_chain(chain_id: int) -> Dict[str, Any]:
|
|
650
|
-
"""Query one chain and return its results with metadata."""
|
|
651
|
-
try:
|
|
652
|
-
# Get subgraph client for this chain
|
|
653
|
-
subgraph_client = self.indexer._get_subgraph_client_for_chain(chain_id)
|
|
654
|
-
|
|
655
|
-
if subgraph_client is None:
|
|
656
|
-
logger.warning(f"No subgraph client available for chain {chain_id}")
|
|
657
|
-
return {
|
|
658
|
-
"chainId": chain_id,
|
|
659
|
-
"status": "unavailable",
|
|
660
|
-
"agents": [],
|
|
661
|
-
"error": f"No subgraph configured for chain {chain_id}"
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
# Execute reputation search query
|
|
665
|
-
try:
|
|
666
|
-
agents_data = subgraph_client.search_agents_by_reputation(
|
|
667
|
-
agents=agents,
|
|
668
|
-
tags=tags,
|
|
669
|
-
reviewers=reviewers,
|
|
670
|
-
capabilities=capabilities,
|
|
671
|
-
skills=skills,
|
|
672
|
-
tasks=tasks,
|
|
673
|
-
names=names,
|
|
674
|
-
minAverageValue=minAverageValue,
|
|
675
|
-
includeRevoked=includeRevoked,
|
|
676
|
-
first=page_size * 3, # Fetch extra to allow for filtering/sorting
|
|
677
|
-
skip=0, # We'll handle pagination after aggregation
|
|
678
|
-
order_by=order_by,
|
|
679
|
-
order_direction=order_direction
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
logger.info(f"Chain {chain_id}: fetched {len(agents_data)} agents by reputation")
|
|
683
|
-
except Exception as e:
|
|
684
|
-
logger.error(f"Error in search_agents_by_reputation for chain {chain_id}: {e}", exc_info=True)
|
|
685
|
-
agents_data = []
|
|
686
|
-
|
|
687
|
-
return {
|
|
688
|
-
"chainId": chain_id,
|
|
689
|
-
"status": "success",
|
|
690
|
-
"agents": agents_data,
|
|
691
|
-
"count": len(agents_data),
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
except Exception as e:
|
|
695
|
-
logger.error(f"Error querying chain {chain_id} for reputation search: {e}", exc_info=True)
|
|
696
|
-
return {
|
|
697
|
-
"chainId": chain_id,
|
|
698
|
-
"status": "error",
|
|
699
|
-
"agents": [],
|
|
700
|
-
"error": str(e),
|
|
701
|
-
"count": 0
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
# Execute queries in parallel
|
|
705
|
-
chain_tasks = [query_single_chain(chain_id) for chain_id in chains]
|
|
706
|
-
chain_results = await asyncio.gather(*chain_tasks)
|
|
707
|
-
|
|
708
|
-
# Aggregate results from all chains
|
|
709
|
-
all_agents = []
|
|
710
|
-
successful_chains = []
|
|
711
|
-
failed_chains = []
|
|
712
|
-
|
|
713
|
-
for result in chain_results:
|
|
714
|
-
chain_id = result["chainId"]
|
|
715
|
-
if result["status"] == "success":
|
|
716
|
-
successful_chains.append(chain_id)
|
|
717
|
-
agents_count = len(result.get("agents", []))
|
|
718
|
-
logger.debug(f"Chain {chain_id}: aggregating {agents_count} agents")
|
|
719
|
-
all_agents.extend(result["agents"])
|
|
720
|
-
else:
|
|
721
|
-
failed_chains.append(chain_id)
|
|
722
|
-
logger.warning(f"Chain {chain_id}: status={result.get('status')}, error={result.get('error', 'N/A')}")
|
|
723
|
-
|
|
724
|
-
logger.debug(f"Total agents aggregated: {len(all_agents)} from {len(successful_chains)} chains")
|
|
725
|
-
|
|
726
|
-
# Transform to AgentSummary objects
|
|
727
|
-
from .models import AgentSummary
|
|
728
|
-
results = []
|
|
729
|
-
for agent_data in all_agents:
|
|
730
|
-
reg_file = agent_data.get('registrationFile') or {}
|
|
731
|
-
if not isinstance(reg_file, dict):
|
|
732
|
-
reg_file = {}
|
|
733
|
-
|
|
734
|
-
agent_summary = AgentSummary(
|
|
735
|
-
chainId=int(agent_data.get('chainId', 0)),
|
|
736
|
-
agentId=agent_data.get('id'),
|
|
737
|
-
name=reg_file.get('name', f"Agent {agent_data.get('id')}"),
|
|
738
|
-
image=reg_file.get('image'),
|
|
739
|
-
description=reg_file.get('description', ''),
|
|
740
|
-
owners=[agent_data.get('owner', '')],
|
|
741
|
-
operators=agent_data.get('operators', []),
|
|
742
|
-
mcp=reg_file.get('mcpEndpoint') is not None,
|
|
743
|
-
a2a=reg_file.get('a2aEndpoint') is not None,
|
|
744
|
-
ens=reg_file.get('ens'),
|
|
745
|
-
did=reg_file.get('did'),
|
|
746
|
-
walletAddress=reg_file.get('agentWallet'),
|
|
747
|
-
supportedTrusts=reg_file.get('supportedTrusts', []),
|
|
748
|
-
a2aSkills=reg_file.get('a2aSkills', []),
|
|
749
|
-
mcpTools=reg_file.get('mcpTools', []),
|
|
750
|
-
mcpPrompts=reg_file.get('mcpPrompts', []),
|
|
751
|
-
mcpResources=reg_file.get('mcpResources', []),
|
|
752
|
-
active=reg_file.get('active', True),
|
|
753
|
-
x402support=reg_file.get('x402Support', reg_file.get('x402support', False)),
|
|
754
|
-
extras={'averageValue': agent_data.get('averageValue')}
|
|
755
|
-
)
|
|
756
|
-
results.append(agent_summary)
|
|
757
|
-
|
|
758
|
-
# Sort by averageValue (descending) if available, otherwise by createdAt
|
|
759
|
-
results.sort(
|
|
760
|
-
key=lambda x: (
|
|
761
|
-
x.extras.get('averageValue') if x.extras.get('averageValue') is not None else 0,
|
|
762
|
-
x.chainId,
|
|
763
|
-
x.agentId
|
|
764
|
-
),
|
|
765
|
-
reverse=True
|
|
766
|
-
)
|
|
767
|
-
|
|
768
|
-
# Apply pagination
|
|
769
|
-
paginated_results = results[skip:skip + page_size]
|
|
770
|
-
next_cursor = str(skip + len(paginated_results)) if len(paginated_results) == page_size and skip + len(paginated_results) < len(results) else None
|
|
771
|
-
|
|
772
|
-
elapsed_ms = int((time.time() - start_time) * 1000)
|
|
773
|
-
|
|
774
|
-
return {
|
|
775
|
-
"items": paginated_results,
|
|
776
|
-
"nextCursor": next_cursor,
|
|
777
|
-
"meta": {
|
|
778
|
-
"chains": chains,
|
|
779
|
-
"successfulChains": successful_chains,
|
|
780
|
-
"failedChains": failed_chains,
|
|
781
|
-
"totalResults": len(results),
|
|
782
|
-
"timing": {"totalMs": elapsed_ms}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
501
|
|
|
786
502
|
# Feedback methods - delegate to feedback_manager
|
|
787
503
|
def prepareFeedbackFile(self, input: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -838,8 +554,6 @@ class SDK:
|
|
|
838
554
|
minValue: Optional[float] = None,
|
|
839
555
|
maxValue: Optional[float] = None,
|
|
840
556
|
include_revoked: bool = False,
|
|
841
|
-
first: int = 100,
|
|
842
|
-
skip: int = 0,
|
|
843
557
|
agents: Optional[List["AgentId"]] = None,
|
|
844
558
|
) -> List["Feedback"]:
|
|
845
559
|
"""Search feedback.
|
|
@@ -881,8 +595,6 @@ class SDK:
|
|
|
881
595
|
minValue=minValue,
|
|
882
596
|
maxValue=maxValue,
|
|
883
597
|
include_revoked=include_revoked,
|
|
884
|
-
first=first,
|
|
885
|
-
skip=skip,
|
|
886
598
|
)
|
|
887
599
|
|
|
888
600
|
def revokeFeedback(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Semantic search client (external endpoint).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SemanticSearchResult:
|
|
15
|
+
chainId: int
|
|
16
|
+
agentId: str
|
|
17
|
+
score: float
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SemanticSearchClient:
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
base_url: str = "https://semantic-search.ag0.xyz",
|
|
24
|
+
timeout_seconds: float = 20.0,
|
|
25
|
+
):
|
|
26
|
+
self.base_url = base_url.rstrip("/")
|
|
27
|
+
self.timeout_seconds = timeout_seconds
|
|
28
|
+
|
|
29
|
+
def search(self, query: str, *, min_score: Optional[float] = None, top_k: Optional[int] = None) -> List[SemanticSearchResult]:
|
|
30
|
+
if not query or not query.strip():
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
# SDK defaults (applied here so developers don't need to pass them).
|
|
34
|
+
# - minScore: 0.5
|
|
35
|
+
# - topK: 5000 (API expects "limit" not "topK" in v1)
|
|
36
|
+
if min_score is None:
|
|
37
|
+
min_score = 0.5
|
|
38
|
+
if top_k is None:
|
|
39
|
+
top_k = 5000
|
|
40
|
+
|
|
41
|
+
body = {"query": query.strip(), "minScore": min_score, "limit": top_k}
|
|
42
|
+
|
|
43
|
+
resp = requests.post(
|
|
44
|
+
f"{self.base_url}/api/v1/search",
|
|
45
|
+
json=body,
|
|
46
|
+
headers={"Content-Type": "application/json"},
|
|
47
|
+
timeout=self.timeout_seconds,
|
|
48
|
+
)
|
|
49
|
+
resp.raise_for_status()
|
|
50
|
+
data = resp.json()
|
|
51
|
+
|
|
52
|
+
results = data.get("results") if isinstance(data, dict) else data
|
|
53
|
+
if not isinstance(results, list):
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
out: List[SemanticSearchResult] = []
|
|
57
|
+
for r in results:
|
|
58
|
+
if not isinstance(r, dict):
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
chain_id = int(r.get("chainId"))
|
|
62
|
+
agent_id = str(r.get("agentId"))
|
|
63
|
+
score = float(r.get("score"))
|
|
64
|
+
except Exception:
|
|
65
|
+
continue
|
|
66
|
+
if ":" not in agent_id:
|
|
67
|
+
continue
|
|
68
|
+
out.append(SemanticSearchResult(chainId=chain_id, agentId=agent_id, score=score))
|
|
69
|
+
return out
|
|
70
|
+
|