a2a-source-edit: write agent.py
This commit is contained in:
43
agent.py
43
agent.py
@@ -26,13 +26,15 @@ from a2a_pack import (
|
||||
AgentDatabaseEnv,
|
||||
AgentDatabaseMigrations,
|
||||
AgentPlatformResources,
|
||||
FileUpload,
|
||||
PlatformUserAuth,
|
||||
Pricing,
|
||||
Resources,
|
||||
RunContext,
|
||||
State,
|
||||
UploadedFile,
|
||||
FileUpload,
|
||||
WorkspaceAccess,
|
||||
WorkspaceMode,
|
||||
)
|
||||
|
||||
MAX_TEXT_CHARS = 200_000
|
||||
@@ -133,7 +135,7 @@ class ContractClockStudioV1(A2AAgent[ContractClockStudioV1Config, PlatformUserAu
|
||||
config_model = ContractClockStudioV1Config
|
||||
auth_model = PlatformUserAuth
|
||||
|
||||
# Deterministic local logic only: no LLM credential is required.
|
||||
# Deterministic local logic only: no LLM credential is read or required.
|
||||
pricing = Pricing(
|
||||
price_per_call_usd=0.0,
|
||||
caller_pays_llm=False,
|
||||
@@ -142,6 +144,12 @@ class ContractClockStudioV1(A2AAgent[ContractClockStudioV1Config, PlatformUserAu
|
||||
resources = Resources(cpu="500m", memory="512Mi", max_runtime_seconds=120)
|
||||
state = State.DURABLE
|
||||
tools_used = ("postgres", "mcp")
|
||||
workspace_access = WorkspaceAccess.dynamic(
|
||||
max_files=4,
|
||||
allowed_modes=(WorkspaceMode.READ_ONLY,),
|
||||
require_reason=False,
|
||||
max_total_size_bytes=MAX_UPLOAD_BYTES,
|
||||
)
|
||||
platform_resources = AgentPlatformResources(
|
||||
databases=(
|
||||
AgentDatabase(
|
||||
@@ -194,7 +202,6 @@ class ContractClockStudioV1(A2AAgent[ContractClockStudioV1Config, PlatformUserAu
|
||||
persist_contract(tenant, clean_id, clean_title, payload, receipt_id)
|
||||
persist_receipt(tenant, receipt_id, "analyze_contract", clean_id, payload, "ok")
|
||||
except Exception:
|
||||
# Fail closed: analysis without durable storage is not a success for this product.
|
||||
return AnalyzeResult(
|
||||
ok=False,
|
||||
contract_id=clean_id,
|
||||
@@ -262,20 +269,16 @@ class ContractClockStudioV1(A2AAgent[ContractClockStudioV1Config, PlatformUserAu
|
||||
raise ValidationFailure("file_too_large", "Upload is larger than 256 KiB.")
|
||||
if document.media_type not in ALLOWED_MEDIA_TYPES:
|
||||
raise ValidationFailure("unsupported_media_type", "Only plain text uploads are supported.")
|
||||
grant = await ctx.workspace.request_access(
|
||||
await ctx.workspace.request_access(
|
||||
files=[document.path],
|
||||
mode=a2a.WorkspaceMode.READ_ONLY,
|
||||
mode=WorkspaceMode.READ_ONLY,
|
||||
reason="Read the caller-uploaded contract text for deadline extraction.",
|
||||
purpose="contract upload analysis",
|
||||
)
|
||||
view = await ctx.workspace.open_view(
|
||||
purpose="contract upload analysis",
|
||||
hints=[document.path],
|
||||
max_files=1,
|
||||
mode=a2a.WorkspaceMode.READ_ONLY,
|
||||
reason=f"Read uploaded file under grant {grant.grant_id}.",
|
||||
)
|
||||
raw = await view.read(document.path)
|
||||
read_bytes = getattr(ctx.workspace, "read_bytes", None)
|
||||
if read_bytes is None:
|
||||
raise RuntimeError("workspace backend does not expose direct file reads")
|
||||
raw = read_bytes(document.path)
|
||||
if len(raw) > MAX_UPLOAD_BYTES:
|
||||
raise ValidationFailure("file_too_large", "Upload is larger than 256 KiB.")
|
||||
text = raw.decode("utf-8")
|
||||
@@ -406,15 +409,14 @@ def extract_timeline(text: str) -> ExtractedTimeline:
|
||||
warnings: list[str] = []
|
||||
renewal_dates = []
|
||||
for parsed, excerpt in dates:
|
||||
if RENEWAL_RE.search(excerpt) or RENEWAL_RE.search(text[max(0, text.find(parsed.isoformat()) - 160): text.find(parsed.isoformat()) + 160]):
|
||||
date_token = parsed.isoformat()
|
||||
date_index = text.find(date_token)
|
||||
local_context = text[max(0, date_index - 160): date_index + 160] if date_index >= 0 else excerpt
|
||||
if RENEWAL_RE.search(excerpt) or RENEWAL_RE.search(local_context):
|
||||
renewal_dates.append(parsed)
|
||||
deadlines.append(
|
||||
Deadline(kind="renewal", date=parsed.isoformat(), summary="Automatic renewal date explicitly stated in contract.", source_text=excerpt)
|
||||
)
|
||||
deadlines.append(Deadline(kind="renewal", date=date_token, summary="Automatic renewal date explicitly stated in contract.", source_text=excerpt))
|
||||
else:
|
||||
deadlines.append(
|
||||
Deadline(kind="obligation", date=parsed.isoformat(), summary="Explicit dated obligation stated in contract.", source_text=excerpt)
|
||||
)
|
||||
deadlines.append(Deadline(kind="obligation", date=date_token, summary="Explicit dated obligation stated in contract.", source_text=excerpt))
|
||||
|
||||
notice_match = NOTICE_RE.search(text)
|
||||
if notice_match:
|
||||
@@ -434,7 +436,6 @@ def extract_timeline(text: str) -> ExtractedTimeline:
|
||||
elif RENEWAL_RE.search(text):
|
||||
warnings.append("No explicit notice-window duration was found for the renewal date.")
|
||||
|
||||
# Acceptance and UI prefer renewal before notice before other dated obligations.
|
||||
order = {"renewal": 0, "notice": 1, "obligation": 2}
|
||||
deduped: dict[tuple[str, str], Deadline] = {}
|
||||
for deadline in sorted(deadlines, key=lambda item: (order[item.kind], item.date, item.summary)):
|
||||
|
||||
Reference in New Issue
Block a user