Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ chosen_agent = relevant_agents[0]
chosen_agent_offering = chosen_agent.offerings[0]
job_id = chosen_agent_offering.initiate_job(
service_requirement,
expired_at,
evaluator_address
)

Expand Down
4 changes: 1 addition & 3 deletions examples/acp_base/external_evaluation/buyer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import threading
from datetime import datetime, timedelta
from typing import Optional

from dotenv import load_dotenv
Expand Down Expand Up @@ -85,8 +84,7 @@ def on_new_task(job: ACPJob, memo_to_sign: Optional[ACPMemo] = None):
"<your-schema-key-1>": "<your-schema-value-1>",
"<your-schema-key-2>": "<your-schema-value-2>",
},
evaluator_address=env.EVALUATOR_AGENT_WALLET_ADDRESS, # evaluator address
expired_at=datetime.now() + timedelta(minutes=3.1) # job expiry duration, minimum 3 minutes
evaluator_address=env.EVALUATOR_AGENT_WALLET_ADDRESS, # evaluator address
)

logger.info(f"Job {job_id} initiated")
Expand Down
2 changes: 0 additions & 2 deletions examples/acp_base/polling_mode/buyer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import time
from datetime import datetime, timedelta

from dotenv import load_dotenv

Expand Down Expand Up @@ -66,7 +65,6 @@ def buyer():
"<your-schema-key-2>": "<your-schema-value-2>",
},
evaluator_address=env.EVALUATOR_AGENT_WALLET_ADDRESS, # evaluator address
expired_at=datetime.now() + timedelta(minutes=3.1), # job expiry duration, minimum 3 minutes
)

logger.info(f"Job {job_id} initiated")
Expand Down
2 changes: 0 additions & 2 deletions examples/acp_base/skip_evaluation/buyer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import threading
from datetime import datetime, timedelta
from typing import Optional

from dotenv import load_dotenv
Expand Down Expand Up @@ -87,7 +86,6 @@ def on_new_task(job: ACPJob, memo_to_sign: Optional[ACPMemo] = None):
"<your-schema-key-1>": "<your-schema-value-1>",
"<your-schema-key-2>": "<your-schema-value-2>",
},
expired_at=datetime.now() + timedelta(minutes=5), # job expiry duration, minimum 3 minutes
)
logger.info(f"Job {job_id} initiated")
logger.info("Listening for next steps...")
Expand Down
35 changes: 9 additions & 26 deletions tests/unit/test_job_offering.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def basic_offering(self, mock_acp_client, mock_contract_client):
name="Test Service",
price=10.0,
price_type=PriceType.FIXED,
sla_minutes=60,
requirement=None,
deliverable=None
)
Expand All @@ -71,6 +72,7 @@ def offering_with_schema(self, mock_acp_client, mock_contract_client):
name="Test Service",
price=10.0,
price_type=PriceType.FIXED,
sla_minutes=60,
requirement={
"type": "object",
"properties": {
Expand All @@ -96,6 +98,7 @@ def test_should_initialize_with_required_parameters(
name="Test Service",
price=10.0,
price_type=PriceType.FIXED,
sla_minutes=60,
requirement=None,
deliverable=None
)
Expand Down Expand Up @@ -123,6 +126,7 @@ def test_should_initialize_with_optional_parameters(
name="Test Service",
price=10.0,
price_type=PriceType.PERCENTAGE,
sla_minutes=60,
requirement=requirement,
deliverable=deliverable,
)
Expand All @@ -145,6 +149,7 @@ def test_should_parse_valid_json_string(
name="Test Service",
price=10.0,
price_type=PriceType.FIXED,
sla_minutes=60,
requirement='{"type": "string"}',
deliverable=None
)
Expand All @@ -165,6 +170,7 @@ def test_should_keep_dict_requirement_as_is(
name="Test Service",
price=10.0,
price_type=PriceType.FIXED,
sla_minutes=60,
requirement=requirement,
deliverable=None
)
Expand Down Expand Up @@ -195,10 +201,10 @@ class TestInitiateJob:
class TestExpiryHandling:
"""Test expiry date handling"""

def test_should_use_default_expiry_when_none(
def test_should_use_sla_minutes_for_expiry(
self, basic_offering, mock_contract_client
):
"""Should use default 1 day expiry when not provided"""
"""Should use offering sla_minutes for job expiration"""
mock_contract_client.get_job_id.return_value = 123
mock_contract_client.handle_operation.return_value = {}
basic_offering.acp_client.get_by_client_and_provider.return_value = None
Expand All @@ -213,35 +219,12 @@ def test_should_use_default_expiry_when_none(
service_requirement={"task": "test"}
)

# Check that create_job was called
create_call = mock_contract_client.create_job.call_args
expired_at = create_call[0][2] # Third positional arg

# Should be 1 day after now
expected = mock_now + timedelta(days=1)
expected = mock_now + timedelta(minutes=basic_offering.sla_minutes)
assert expired_at == expected

def test_should_use_custom_expiry_when_provided(
self, basic_offering, mock_contract_client
):
"""Should use custom expiry when provided"""
mock_contract_client.get_job_id.return_value = 123
mock_contract_client.handle_operation.return_value = {}
basic_offering.acp_client.get_by_client_and_provider.return_value = None

custom_expiry = datetime(
2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

basic_offering.initiate_job(
service_requirement={"task": "test"},
expired_at=custom_expiry
)

create_call = mock_contract_client.create_job.call_args
expired_at = create_call[0][2]

assert expired_at == custom_expiry

class TestServiceRequirementValidation:
"""Test service requirement validation"""

Expand Down
1 change: 1 addition & 0 deletions virtuals_acp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ def _hydrate_agent(self, agent_data: Dict[str, Any]) -> IACPAgent:
price=price,
price_type=price_type,
required_funds=offering["requiredFunds"],
sla_minutes=offering["slaMinutes"],
requirement=offering.get("requirement", None),
deliverable=offering.get("deliverable", None),
)
Expand Down
7 changes: 3 additions & 4 deletions virtuals_acp/job_offering.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ACPJobOffering(BaseModel):
price: float
price_type: PriceType
required_funds: bool
sla_minutes: int
requirement: Optional[Union[Dict[str, Any], str]] = None
deliverable: Optional[Union[Dict[str, Any], str]] = None

Expand All @@ -54,10 +55,8 @@ def initiate_job(
self,
service_requirement: Union[Dict[str, Any], str],
evaluator_address: Optional[str] = None,
expired_at: Optional[datetime] = None,
) -> int:
if expired_at is None:
expired_at = datetime.now(timezone.utc) + timedelta(days=1)
expired_at = datetime.now(timezone.utc) + timedelta(minutes=self.sla_minutes)

# Validate against requirement schema if present
if self.requirement:
Expand Down Expand Up @@ -139,7 +138,7 @@ def initiate_job(
evaluator_address or self.contract_client.agent_wallet_address,
fare_amount.amount,
fare_amount.fare.contract_address,
expired_at or datetime.utcnow(),
expired_at,
is_x402_job=is_x402_job,
)

Expand Down