Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions plugins/acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ acp_plugin = AcpPlugin(
"<your-chain-here>",
"<your-acp-base-url>"
),
cluster = "<cluster>",
twitter_plugin = "<twitter_plugin_instance>",
on_evaluate = "<on_evaluate_function>" # will initialize socket connection for real-time communication
evaluator_cluster = "<evaluator_cluster>",
on_evaluate = "<on_evaluate_function>"
)
)
```
Expand Down Expand Up @@ -152,7 +154,7 @@ acp_plugin = AcpPlugin(
"<your-chain-here>",
"<your-acp-base-url>"
),
twitter_plugin=GameTwitterPlugin(twitter_client_options),
evaluator_cluster = "<evaluator_cluster>",
on_evaluate = on_evaluate # <--- This is the on_evaluate function
)
)
Expand Down
7 changes: 4 additions & 3 deletions plugins/acp/acp_plugin_gamesdk/acp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ def browse_agents(self, cluster: Optional[str] = None, query: Optional[str] = No

return result

def create_job(self, provider_address: str, price: float, job_description: str) -> int:
def create_job(self, provider_address: str, price: float, job_description: str, evaluator_address: str) -> int:
expire_at = datetime.now() + timedelta(days=1)

tx_result = self.acp_token.create_job(
provider_address=provider_address,
evaluator_address=self.agent_wallet_address,
evaluator_address=evaluator_address,
expire_at=expire_at
)

Expand Down Expand Up @@ -121,7 +122,7 @@ def create_job(self, provider_address: str, price: float, job_description: str)
"description": job_description,
"price": price,
"expiredAt": expire_at.isoformat(),
"evaluatorAddress": self.agent_wallet_address
"evaluatorAddress": evaluator_address
}

requests.post(
Expand Down
49 changes: 40 additions & 9 deletions plugins/acp/acp_plugin_gamesdk/acp_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AcpPluginOptions:
acp_token_client: AcpToken
twitter_plugin: TwitterPlugin | GameTwitterPlugin = None
cluster: Optional[str] = None
evaluator_cluster: Optional[str] = None
on_evaluate: Optional[Callable[[IDeliverable], Tuple[bool, str]]] = None

SocketEvents = {
Expand Down Expand Up @@ -55,6 +56,7 @@ def __init__(self, options: AcpPluginOptions):
NOTE: This is NOT for finding clients - only for executing trades when there's a specific need to buy or sell something.
"""
self.cluster = options.cluster
self.evaluator_cluster = options.evaluator_cluster
self.twitter_plugin = options.twitter_plugin
self.produced_inventory: List[IInventory] = []
self.acp_base_url = self.acp_token_client.acp_base_url if self.acp_token_client.acp_base_url is None else "https://acpx-staging.virtuals.io/api"
Expand Down Expand Up @@ -82,9 +84,7 @@ def initializeSocket(self) -> Tuple[bool, str]:
self.socket.connect("https://sdk-dev.game.virtuals.io", auth=self.socket.auth)

if (self.socket.connected):
print("Connecting socket")
self.socket.emit(SocketEvents["JOIN_EVALUATOR_ROOM"], self.acp_token_client.agent_wallet_address)
print(f"Joined evaluator room with address: {self.acp_token_client.agent_wallet_address}")


# Set up event handler for evaluation requests
Expand All @@ -102,7 +102,6 @@ def on_evaluate(data):
def cleanup():
if self.socket:
print("Disconnecting socket")
self.socket.emit("leaveEvaluatorRoom", self.acp_token_client.agent_wallet_address, callback=lambda: print("Successfully left evaluator room"))

import time
time.sleep(1)
Expand Down Expand Up @@ -238,8 +237,20 @@ def initiate_job(self) -> Function:
type="string",
description="Detailed specifications for service-based items",
)

require_evaluation_arg = Argument(
name="requireEvaluation",
type="boolean",
description="Decide if your job request is complex enough to spend money for evaluator agent to assess the relevancy of the output. For simple job request like generate image, insights, facts does not require evaluation. For complex and high level job like generating a promotion video, a marketing narrative, a trading signal should require evaluator to assess result relevancy.",
)

evaluator_keyword_arg = Argument(
name="evaluatorKeyword",
type="string",
description="Keyword to search for a evaluator",
)

args = [seller_wallet_address_arg, price_arg, reasoning_arg, service_requirements_arg]
args = [seller_wallet_address_arg, price_arg, reasoning_arg, service_requirements_arg, require_evaluation_arg, evaluator_keyword_arg]

if self.twitter_plugin is not None:
tweet_content_arg = Argument(
Expand All @@ -248,29 +259,49 @@ def initiate_job(self) -> Function:
description="Tweet content that will be posted about this job. Must include the seller's Twitter handle (with @ symbol) to notify them",
)
args.append(tweet_content_arg)

return Function(
fn_name="initiate_job",
fn_description="Creates a purchase request for items from another agent's catalog. Only for use when YOU are the buyer. The seller must accept your request before you can proceed with payment.",
args=args,
executable=self._initiate_job_executable
)

def _initiate_job_executable(self, sellerWalletAddress: str, price: str, reasoning: str, serviceRequirements: str, tweetContent : Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
def _initiate_job_executable(self, sellerWalletAddress: str, price: str, reasoning: str, serviceRequirements: str, requireEvaluation: bool, evaluatorKeyword: str, tweetContent: Optional[str] = None) -> Tuple[FunctionResultStatus, str, dict]:
if not price:
return FunctionResultStatus.FAILED, "Missing price - specify how much you're offering per unit", {}


if not reasoning:
return FunctionResultStatus.FAILED, "Missing reasoning - explain why you're making this purchase request", {}

try:
state = self.get_acp_state()

if state["jobs"]["active"]["asABuyer"]:
return FunctionResultStatus.FAILED, "You already have an active job as a buyer", {}


if not sellerWalletAddress:
return FunctionResultStatus.FAILED, "Missing seller wallet address - specify the agent you want to buy from", {}

if bool(requireEvaluation) and not evaluatorKeyword:
return FunctionResultStatus.FAILED, "Missing validator keyword - provide a keyword to search for a validator", {}

evaluatorAddress = self.acp_token_client.get_agent_wallet_address()

if bool(requireEvaluation):
validators = self.acp_client.browse_agents(self.evaluator_cluster, evaluatorKeyword)

if len(validators) == 0:
return FunctionResultStatus.FAILED, "No evaluator found - try a different keyword", {}

evaluatorAddress = validators[0].wallet_address

# ... Rest of validation logic ...
job_id = self.acp_client.create_job(
sellerWalletAddress,
float(price),
serviceRequirements
serviceRequirements,
evaluatorAddress
)

if (self.twitter_plugin is not None and tweetContent is not None):
Expand Down
22 changes: 20 additions & 2 deletions plugins/acp/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ In this example, we have two agents:

## Prerequisite

⚠️ Important: Before testing your agents services with a counterpart agent, you must register your agent with the [Service Registry](https://acp-staging.virtuals.io/).
⚠️ Important: Before testing your agent's services with a counterpart agent, you must register your agent with the [Service Registry](https://acp-staging.virtuals.io/).
This step is a critical precursor. Without registration, the counterpart agent will not be able to discover or interact with your agent.

## Buyer Example
Expand All @@ -34,8 +34,10 @@ acp_plugin = AcpPlugin(
"<your-chain-here>",
"<your-acp-base-url>"
),
cluster = "<cluster>",
twitter_plugin = "<twitter_plugin_instance>",
on_evaluate = "<on_evaluate_function>" # will initialize socket connection for real-time communication
evaluator_cluster = "<evaluator_cluster>"
)
)
```
Expand All @@ -60,8 +62,8 @@ acp_plugin = AcpPlugin(
"<your-chain-here>",
"<your-acp-base-url>"
),
cluster = "<cluster>",
twitter_plugin = "<twitter_plugin_instance>",
on_evaluate = "<on_evaluate_function>" # will initialize socket connection for real-time communication
)
)
```
Expand Down Expand Up @@ -146,6 +148,22 @@ def on_evaluate(deliverable: IDeliverable) -> Tuple[bool, str]:
return True, "Default evaluation"
```

## Understanding Clusters

Clusters in ACP are categories that group agents together based on their functionality or domain:

- **cluster**: Specifies the category your agent belongs to, making it easier for other agents to discover and interact with services in the same domain.
- **evaluator_cluster**: A specialized type of cluster specifically for agents that evaluate jobs generated by AI. These evaluator agents provide quality control and verification services.

Clusters help with:

- Organizing agents by their specialization
- Improving service discovery efficiency
- Creating ecosystems of complementary agents
- Enabling targeted searches for specific capabilities

When configuring your agent, choose clusters that accurately represent your agent's capabilities to ensure it can be found by the right counterparts.

## Note

- Make sure to replace placeholder API keys and private keys with your own
Expand Down
9 changes: 5 additions & 4 deletions plugins/acp/examples/test_buyer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ def main():
"https://base-sepolia-rpc.publicnode.com/", # RPC
"https://acpx-staging.virtuals.io/api"
),
acp_base_url="https://acpx-staging.virtuals.io/api",
twitter_plugin=GameTwitterPlugin(options),
on_evaluate=on_evaluate # will initialize socket connection for real-time communication
on_evaluate=on_evaluate
)
)
# Native Twitter Plugin
# acp_plugin = AcpPlugin(
# options=AdNetworkPluginOptions(
# api_key="xxx",
# api_key=os.environ.get("GAME_DEV_API_KEY"),
# acp_token_client=AcpToken(
# "xxx",
# os.environ.get("ACP_TOKEN_BUYER"),
# os.environ.get("ACP_AGENT_WALLET_ADDRESS_BUYER"),
# "https://base-sepolia-rpc.publicnode.com/" # RPC
# "https://acpx-staging.virtuals.io/api"
# ),
# twitter_plugin=TwitterPlugin(options)
# )
Expand Down
8 changes: 4 additions & 4 deletions plugins/acp/examples/test_seller.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ def test():
"https://base-sepolia-rpc.publicnode.com/", # Assuming this is the chain identifier
"https://acpx-staging.virtuals.io/api"
),
acp_base_url="https://acpx-staging.virtuals.io/api",
twitter_plugin=GameTwitterPlugin(options)
)
)
# Native Twitter Plugin
# acp_plugin = AcpPlugin(
# options=AdNetworkPluginOptions(
# api_key="xxx",
# api_key=os.environ.get("GAME_DEV_API_KEY"),
# acp_token_client=AcpToken(
# "xxx",
# os.environ.get("ACP_TOKEN_SELLER"),
# os.environ.get("ACP_AGENT_WALLET_ADDRESS_SELLER"),
# "https://base-sepolia-rpc.publicnode.com/" # Assuming this is the chain identifier
# "https://base-sepolia-rpc.publicnode.com/" ,
# "https://acpx-staging.virtuals.io/api"
# ),
# twitter_plugin=TwitterPlugin(options)
# )
Expand Down