code editor: Agent Studio improvement iteration 1 for quote-judge-studio-v1. Goal:
This commit is contained in:
2
a2a.yaml
2
a2a.yaml
@@ -1,5 +1,5 @@
|
|||||||
name: quote-judge-studio-v1
|
name: quote-judge-studio-v1
|
||||||
version: 0.1.1
|
version: 0.1.2
|
||||||
entrypoint: agent:QuoteJudgeStudioV1
|
entrypoint: agent:QuoteJudgeStudioV1
|
||||||
expose:
|
expose:
|
||||||
public: false
|
public: false
|
||||||
|
|||||||
41
agent.py
41
agent.py
@@ -48,6 +48,7 @@ MAX_QUOTES = 20
|
|||||||
MAX_VENDOR_LENGTH = 120
|
MAX_VENDOR_LENGTH = 120
|
||||||
MAX_UPLOADS = 5
|
MAX_UPLOADS = 5
|
||||||
MAX_UPLOAD_BYTES = 128_000
|
MAX_UPLOAD_BYTES = 128_000
|
||||||
|
MAX_UPLOAD_BASE64_CHARS = ((MAX_UPLOAD_BYTES + 2) // 3) * 4
|
||||||
ALLOWED_UPLOAD_MEDIA_TYPES = {"text/csv", "text/plain", "application/json"}
|
ALLOWED_UPLOAD_MEDIA_TYPES = {"text/csv", "text/plain", "application/json"}
|
||||||
_DATABASE_NAME = "quote-judge-studio-v1-data"
|
_DATABASE_NAME = "quote-judge-studio-v1-data"
|
||||||
_LOCAL_COMPARISONS: dict[tuple[str, str], dict[str, Any]] = {}
|
_LOCAL_COMPARISONS: dict[tuple[str, str], dict[str, Any]] = {}
|
||||||
@@ -79,20 +80,12 @@ class QuoteWeights(BaseModel):
|
|||||||
delivery: float = Field(..., ge=0, le=100)
|
delivery: float = Field(..., ge=0, le=100)
|
||||||
warranty: float = Field(..., ge=0, le=100)
|
warranty: float = Field(..., ge=0, le=100)
|
||||||
|
|
||||||
@field_validator("warranty")
|
|
||||||
@classmethod
|
|
||||||
def weights_must_be_positive(cls, value: float, info: Any) -> float:
|
|
||||||
values = info.data
|
|
||||||
total = float(values.get("price", 0)) + float(values.get("delivery", 0)) + float(value)
|
|
||||||
if total <= 0:
|
|
||||||
raise ValueError("at least one weight must be positive")
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class BrowserQuoteDocument(BaseModel):
|
class BrowserQuoteDocument(BaseModel):
|
||||||
filename: str = Field(..., min_length=1, max_length=160)
|
filename: str = Field(..., min_length=1, max_length=160)
|
||||||
media_type: str = Field(..., min_length=1, max_length=100)
|
media_type: str = Field(..., min_length=1, max_length=100)
|
||||||
data_base64: str = Field(..., min_length=1)
|
data_base64: str = Field(..., min_length=1, max_length=MAX_UPLOAD_BASE64_CHARS)
|
||||||
|
|
||||||
@field_validator("filename")
|
@field_validator("filename")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -115,7 +108,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
"QuoteJudge compares vendor quotes with weighted price, delivery, and "
|
"QuoteJudge compares vendor quotes with weighted price, delivery, and "
|
||||||
"warranty scoring, persists per-user comparisons, and serves a one-page product UI."
|
"warranty scoring, persists per-user comparisons, and serves a one-page product UI."
|
||||||
)
|
)
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
|
|
||||||
config_model = QuoteJudgeStudioV1Config
|
config_model = QuoteJudgeStudioV1Config
|
||||||
auth_model = PlatformUserAuth
|
auth_model = PlatformUserAuth
|
||||||
@@ -152,7 +145,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
"weights; persist the recommendation and a production receipt."
|
"weights; persist the recommendation and a production receipt."
|
||||||
),
|
),
|
||||||
timeout_seconds=30,
|
timeout_seconds=30,
|
||||||
idempotent=True,
|
idempotent=False,
|
||||||
cost_class="cheap",
|
cost_class="cheap",
|
||||||
)
|
)
|
||||||
async def compare_quotes(
|
async def compare_quotes(
|
||||||
@@ -171,6 +164,24 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
"weights": weights.model_dump(),
|
"weights": weights.model_dump(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if weights.price + weights.delivery + weights.warranty <= 0:
|
||||||
|
result = {
|
||||||
|
"ok": False,
|
||||||
|
"code": "positive_weight_required",
|
||||||
|
"message": "Set at least one weight above zero before comparing.",
|
||||||
|
"comparison_id": cleaned_id,
|
||||||
|
}
|
||||||
|
await _persist_receipt(
|
||||||
|
tenant_key=tenant,
|
||||||
|
comparison_id=cleaned_id,
|
||||||
|
skill_name="compare_quotes",
|
||||||
|
input_payload=input_payload,
|
||||||
|
result_payload=result,
|
||||||
|
status="validation_error",
|
||||||
|
started_at=started,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
if len(quotes) < 2:
|
if len(quotes) < 2:
|
||||||
result = {
|
result = {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
@@ -222,7 +233,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
@a2a.tool(
|
@a2a.tool(
|
||||||
description="Reopen a saved QuoteJudge comparison for the authenticated user.",
|
description="Reopen a saved QuoteJudge comparison for the authenticated user.",
|
||||||
timeout_seconds=15,
|
timeout_seconds=15,
|
||||||
idempotent=True,
|
idempotent=False,
|
||||||
cost_class="cheap",
|
cost_class="cheap",
|
||||||
)
|
)
|
||||||
async def get_comparison(
|
async def get_comparison(
|
||||||
@@ -261,7 +272,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
"compare extracted quotes, and persist the result."
|
"compare extracted quotes, and persist the result."
|
||||||
),
|
),
|
||||||
timeout_seconds=30,
|
timeout_seconds=30,
|
||||||
idempotent=True,
|
idempotent=False,
|
||||||
cost_class="cheap",
|
cost_class="cheap",
|
||||||
)
|
)
|
||||||
async def compare_uploaded_quotes_browser(
|
async def compare_uploaded_quotes_browser(
|
||||||
@@ -287,7 +298,7 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
"file and compare extracted quotes."
|
"file and compare extracted quotes."
|
||||||
),
|
),
|
||||||
timeout_seconds=30,
|
timeout_seconds=30,
|
||||||
idempotent=True,
|
idempotent=False,
|
||||||
cost_class="cheap",
|
cost_class="cheap",
|
||||||
)
|
)
|
||||||
async def compare_uploaded_quote_file(
|
async def compare_uploaded_quote_file(
|
||||||
@@ -342,6 +353,8 @@ class QuoteJudgeStudioV1(A2AAgent[QuoteJudgeStudioV1Config, PlatformUserAuth]):
|
|||||||
def _tenant_key(ctx: RunContext[PlatformUserAuth]) -> str:
|
def _tenant_key(ctx: RunContext[PlatformUserAuth]) -> str:
|
||||||
auth = ctx.auth
|
auth = ctx.auth
|
||||||
ident = auth.user_id if auth.user_id is not None else (auth.sub or auth.email)
|
ident = auth.user_id if auth.user_id is not None else (auth.sub or auth.email)
|
||||||
|
if ident is None:
|
||||||
|
raise ValueError("authenticated user identity is required")
|
||||||
return f"user:{ident}"
|
return f"user:{ident}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
db/migrations/002_quotejudge_schema_repair.sql
Normal file
10
db/migrations/002_quotejudge_schema_repair.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
ALTER TABLE quote_comparisons
|
||||||
|
ADD COLUMN IF NOT EXISTS recommended_vendor TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS score DOUBLE PRECISION,
|
||||||
|
ADD COLUMN IF NOT EXISTS normalized_total DOUBLE PRECISION;
|
||||||
|
|
||||||
|
ALTER TABLE quote_execution_receipts
|
||||||
|
ADD COLUMN IF NOT EXISTS skill_name TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS status TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS input_hash TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS result_hash TEXT;
|
||||||
Reference in New Issue
Block a user