feat: sync latest ARR implementation
This commit is contained in:
@@ -9,7 +9,7 @@ from datetime import date, datetime, timedelta, timezone
|
||||
from typing import List, Optional, Union
|
||||
from pathlib import Path
|
||||
|
||||
from agent_integration.client import OpenAgentAPIError, OpenAgentTransportError
|
||||
from agent_integration.client import OpenAgentAPIError, OpenAgentEvent, OpenAgentTransportError
|
||||
from arr_ingestion.contracts import ArtifactRef, XLSX_MIME
|
||||
from arr_processing.contracts import (
|
||||
PROCESSING_REQUEST_VERSION,
|
||||
@@ -198,8 +198,11 @@ class ProcessingContractTests(unittest.TestCase):
|
||||
self.assertFalse(program_schema["additionalProperties"])
|
||||
self.assertEqual(
|
||||
program_schema["properties"]["contract_version"]["const"],
|
||||
"arr-opera-daily-program-input-2",
|
||||
"arr-opera-daily-program-input-3",
|
||||
)
|
||||
attachment_oss = program_schema["$defs"]["attachment"]["properties"]["oss"]
|
||||
self.assertIn("url", attachment_oss["required"])
|
||||
self.assertEqual(attachment_oss["properties"]["url"]["format"], "uri")
|
||||
self.assertEqual(
|
||||
program_schema["properties"]["attachment_fetch_policy"]
|
||||
["properties"]["tool_name"]["const"],
|
||||
@@ -243,12 +246,12 @@ class ProcessingContractTests(unittest.TestCase):
|
||||
ProcessingRequest.from_dict(payload)
|
||||
|
||||
def test_oss_program_message_matches_fetch_tool_attachment_contract(self):
|
||||
value = request(job_id="arrjob-source-message-001")
|
||||
value = request(job_id="arrjob-source:message-001")
|
||||
source = ArtifactRef(
|
||||
role="source_xml",
|
||||
file_kind="opera_xml",
|
||||
object_key=(
|
||||
"arr/jobs/arrjob-source-message-001/attempts/0001/"
|
||||
"arr/jobs/arrjob-source:message-001/attempts/0001/"
|
||||
"committed/source_xml/source.xml"
|
||||
),
|
||||
original_filename="source.xml",
|
||||
@@ -299,6 +302,13 @@ class ProcessingContractTests(unittest.TestCase):
|
||||
"oss-cn-guangzhou.aliyuncs.com",
|
||||
)
|
||||
self.assertEqual(attachment["oss"]["object_key"], source.object_key)
|
||||
self.assertEqual(
|
||||
attachment["oss"]["url"],
|
||||
"https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/"
|
||||
"arr/jobs/arrjob-source%3Amessage-001/attempts/0001/"
|
||||
"committed/source_xml/source.xml",
|
||||
)
|
||||
self.assertNotIn("?", attachment["oss"]["url"])
|
||||
for forbidden in ("access_key", "secret", "signed_url", "local_path"):
|
||||
self.assertNotIn(forbidden, rendered.lower())
|
||||
|
||||
@@ -753,13 +763,19 @@ class ProcessingOutputRegistrarTests(unittest.TestCase):
|
||||
|
||||
|
||||
class FakeOpenAgentService:
|
||||
def __init__(self, response=None, error: Optional[BaseException] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
response=None,
|
||||
error: Optional[BaseException] = None,
|
||||
trace_events: Optional[list[OpenAgentEvent]] = None,
|
||||
) -> None:
|
||||
self.response = response or {
|
||||
"run_id": "run-processing-001",
|
||||
"status": "running",
|
||||
}
|
||||
self.error = error
|
||||
self.calls = []
|
||||
self.trace_events = trace_events
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
self.calls.append((args, kwargs))
|
||||
@@ -767,6 +783,12 @@ class FakeOpenAgentService:
|
||||
raise self.error
|
||||
return self.response
|
||||
|
||||
def stream_message(self, *args, **kwargs):
|
||||
self.calls.append((args, kwargs))
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
yield from self.trace_events or []
|
||||
|
||||
def get_run(self, *_args, **_kwargs):
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
@@ -800,6 +822,113 @@ class OpenAgentProcessingTransportTests(unittest.TestCase):
|
||||
self.assertNotIn("object_key", rendered)
|
||||
self.assertNotIn("local_path", rendered)
|
||||
|
||||
def test_trace_adapter_returns_from_run_started_then_drains_safe_events(self):
|
||||
class MemoryTraceStore:
|
||||
def __init__(self) -> None:
|
||||
self.records = []
|
||||
|
||||
def append(self, job_id, record):
|
||||
self.records.append((job_id, dict(record)))
|
||||
|
||||
service = FakeOpenAgentService(
|
||||
trace_events=[
|
||||
OpenAgentEvent(
|
||||
event="trace",
|
||||
data={
|
||||
"event": "run.started",
|
||||
"run_id": "run-trace-001",
|
||||
"ts": "2026-07-29T13:00:00Z",
|
||||
},
|
||||
),
|
||||
OpenAgentEvent(
|
||||
event="trace",
|
||||
data={
|
||||
"event": "task.updated",
|
||||
"run_id": "run-trace-001",
|
||||
"ts": "2026-07-29T13:00:01Z",
|
||||
"data": {"summary": "开始处理任务"},
|
||||
},
|
||||
),
|
||||
OpenAgentEvent(
|
||||
event="trace",
|
||||
data={
|
||||
"event": "message.final",
|
||||
"run_id": "run-trace-001",
|
||||
"text": "submission_grant=must-not-escape",
|
||||
},
|
||||
),
|
||||
OpenAgentEvent(
|
||||
event="trace",
|
||||
data={
|
||||
"event": "step.updated",
|
||||
"run_id": "run-trace-001",
|
||||
"ts": "2026-07-29T13:00:02Z",
|
||||
"data": {"summary": "工具完成 token=must-not-escape"},
|
||||
},
|
||||
),
|
||||
OpenAgentEvent(
|
||||
event="trace",
|
||||
data={
|
||||
"event": "run.completed",
|
||||
"run_id": "run-trace-001",
|
||||
"status": "success",
|
||||
"ts": "2026-07-29T13:00:03Z",
|
||||
},
|
||||
),
|
||||
OpenAgentEvent(event="end", data=None),
|
||||
]
|
||||
)
|
||||
trace_store = MemoryTraceStore()
|
||||
transport = OpenAgentProcessingTransport(
|
||||
service, # type: ignore[arg-type]
|
||||
trace_store=trace_store,
|
||||
)
|
||||
|
||||
value = request()
|
||||
snapshot = transport.submit(value, "idem-trace-001")
|
||||
transport.close(timeout_seconds=1.0)
|
||||
|
||||
self.assertEqual(snapshot, RemoteRunSnapshot("run-trace-001", "running"))
|
||||
args, kwargs = service.calls[0]
|
||||
self.assertNotIn(value.job_id, args[0])
|
||||
self.assertEqual(json.loads(args[1]), value.to_dict())
|
||||
self.assertTrue(kwargs["include_trace"])
|
||||
codes = [record[1]["code"] for record in trace_store.records]
|
||||
self.assertEqual(
|
||||
codes,
|
||||
[
|
||||
"AGENT_RUN_STARTED",
|
||||
"AGENT_TASK_UPDATED",
|
||||
"AGENT_STEP_UPDATED",
|
||||
"AGENT_RUN_COMPLETED",
|
||||
"AGENT_TRACE_STREAM_ENDED",
|
||||
],
|
||||
)
|
||||
rendered = json.dumps(trace_store.records, ensure_ascii=False)
|
||||
self.assertNotIn("must-not-escape", rendered)
|
||||
|
||||
def test_trace_submit_transport_failure_is_not_retried_without_server_deduplication(self):
|
||||
class MemoryTraceStore:
|
||||
def append(self, _job_id, _record):
|
||||
pass
|
||||
|
||||
service = FakeOpenAgentService(
|
||||
error=OpenAgentTransportError("ambiguous after POST")
|
||||
)
|
||||
transport = OpenAgentProcessingTransport(
|
||||
service, # type: ignore[arg-type]
|
||||
trace_store=MemoryTraceStore(),
|
||||
)
|
||||
|
||||
with self.assertRaises(ProcessingTransportError) as raised:
|
||||
transport.submit(request(), "idem-trace-ambiguous")
|
||||
|
||||
self.assertEqual(
|
||||
raised.exception.code,
|
||||
"PROCESSING_REMOTE_SUBMISSION_AMBIGUOUS",
|
||||
)
|
||||
self.assertFalse(raised.exception.retryable)
|
||||
|
||||
def test_open_agent_409_429_and_timeout_are_normalized(self):
|
||||
errors = (
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user