diff --git a/.env.example b/.env.example index 06b1ab3c..32f26319 100644 --- a/.env.example +++ b/.env.example @@ -31,7 +31,6 @@ A2AT_PROMPT_RESOURCE_LOCAL_ROOT_DIR= # Prompt compliance A2AT_PROMPT_COMPLIANCE_ENABLED=false -A2AT_PROMPT_COMPLIANCE_GUARDRAIL_PROVIDER=noop # LLM runtime # Supported providers: openai (OpenAI-compatible APIs like DeepSeek, Azure OpenAI, etc.) @@ -55,12 +54,3 @@ A2AT_NEGOTIATION_STATE_STORE_TYPE=in_memory A2AT_CRED_KEY=your-32-byte-hex-key-here # Keep true for production HTTPS. Set false only for local/self-signed integration testing. -# For LoadPsop HTTPS, false skips both server trust and hostname checks; no local CA file is needed. -# The server still needs a TLS certificate. This does not bypass mTLS or change forwarding-platform TLS. -A2A_SSL_VERIFY=false -# Comma-separated classpath:, file: URI, or absolute file locations. Leave empty for bundled cards. -# For a live OMC, point the city AgentCards at files exported by the registry center. -A2A_AGENT_CARD_LOCATIONS= -# Embedded startup validates both effective AgentCards against local network interfaces. -# Set false for an external OMC, including one running on the same host. Empty defaults to true. -A2A_EMBEDDED_OMC_ENABLED= diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7795a2..f038705a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ - Separate authorization/notification lifecycle from workflow outcomes; never synthesize a subscription ACK. - Observe serialized HTTP/JSON-RPC, real gRPC metadata/protobuf and dev vendor SDK traffic with mandatory redaction and bounded SSE. -- Add local missing-port SpringSpnDemo negotiation tests for direct and dev Order; real LLM/platform/OMC validation - remains separate. +- Add missing-input negotiation and independent transport-path regression tests; live model and production endpoint + validation remains separate. - Refresh bilingual callback, architecture and integration contracts. No older SDK compatibility layer. ### Release hardening diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cc2e404..c6f9daed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ mvn -B clean verify workflow-engine-sdk-java/ |-- workflow-engine/ SDK engine module | +-- src/main/java/dev/openan/workflow/engine/ -| |-- client/ A2A transport, auth, extensions (package-private internals) +| |-- client/ A2A transport, authentication, extensions, runtime APIs | |-- control/ User-facing: ControlPoint, EventCallback, EventType | |-- core/ Internal: WorkflowExecutor, ContextBuilder (package-private) | |-- model/ Data models @@ -137,8 +137,9 @@ Every Java file must start with the Apache 2.0 license header: - Run the complete `mvn -B clean verify` reactor; sample failures block release. - Documentation code snippets are compiled by DocumentedCallbackExampleTest; HostQuickStartTest executes the minimal host flow. -- Check default single-city negotiation, final serialized protocol logs, independent extension lifecycle, cancellation and secret anonymization. +- Check required-input negotiation, final serialized protocol logs, independent extension lifecycle, cancellation and secret anonymization. - Verify published POM coordinates, sources/Javadoc artifacts and the absence of private configuration/reference documents. -- Record the tested commit and Surefire reports. Offline providers and local OMC simulators are not live acceptance. -- Live model/OMC validation requires authorized endpoints and a separate acceptance record. Never commit customer integration notes. +- Record the tested commit and Surefire reports. Offline providers and local protocol simulators are not live acceptance. +- Live model and dispatched-agent validation requires authorized endpoints and a separate acceptance record. Never + commit customer integration notes. - Do not publish, tag, or push as a side effect of running tests. diff --git a/README.md b/README.md index 6ee6339f..732e8392 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Java](https://img.shields.io/badge/Java-17+-orange.svg)](https://adoptium.net/) [![Maven](https://img.shields.io/badge/Maven-3.6+-red.svg)](https://maven.apache.org/) -A standalone SDK for executing multi-agent workflows over the [A2A protocol](https://a2aproject.github.io/a2a-java/) +A Java SDK embedded in a host agent for executing multi-agent workflows over the [A2A protocol](https://a2aproject.github.io/a2a-java/) with [A2A-T](https://projects.tmforum.org/a2aproject/telecommunication/extensions/) telecom extensions. The engine handles workflow scheduling, A2A envelopes, transport, task waiting, authentication and TLS. Host callbacks @@ -13,7 +13,7 @@ return final message content and own any A2A-T generation, schema, validation or ## Features - **A2A-T Extension Support**: Task-T (structured task prompts), Negotiation-T (stateless auto negotiation loop), - Authorization-T (independent whitelist operation), Notification-T (independent long-lived SSE subscription) + Authorization-T (independent authorization operation), Notification-T (independent long-lived SSE subscription) - **Content-neutral callbacks**: final MessageContent, complete ReceivedMessage, local multi-output TaskResult and explicit NegotiationReply.Send/Stop - **Minimal A2A-T dependency**: a2a-t-core only in the engine; content generation and template queries use the host's @@ -25,15 +25,14 @@ return final message content and own any A2A-T generation, schema, validation or - **Protocol Logging**: actual HTTP/JSON-RPC boundaries and gRPC metadata/protobuf views; bodies default on at DEBUG with mandatory secret-field redaction -The SPN sample treats the protocol document as an input, not as executable truth. The pinned A2A-T SDK templates, slot -schemas, canonical URIs, and validation results are authoritative. Protocol generation and validation fail closed; raw -text is never sent under an A2A-T URI as a fallback. +Protocol documents are integration inputs, not executable truth. The pinned A2A-T SDK templates, slot schemas, +canonical URIs, and validation results are authoritative. Protocol generation and validation fail closed; raw text is +never sent under an A2A-T URI as a fallback. -In `SpringSpnDemo`, WAIMO sends a Task-T complaint to the integrator. The integrator loads the PSOP, dispatches two -city-specific OMC diagnoses in parallel, joins both branches exactly once, and returns the real merged result over -direct HTTP+JSON transport. Task-T, Authorization-T, and Notification-T each use an independent -transport/runtime/context. Authorization and Notification are independently triggered integrator operations rather than -DAG nodes; Notification keeps an explicit long-lived subscription until the recovery result, cancellation, or shutdown. +The SDK is embedded in a host agent. It schedules workflow tasks to dispatched agents and returns complete results to +the host callbacks. Task-T, Authorization-T, and Notification-T use independent transport/runtime/context instances. +Authorization and Notification are independently triggered host-agent operations rather than DAG nodes; a notification +subscription remains open until the host-defined terminal event, explicit cancellation, or shutdown. ## Quick Start @@ -41,9 +40,10 @@ DAG nodes; Notification keeps an explicit long-lived subscription until the reco A2A-T SDK `1.1.0` is published to Maven Central. Maven resolves it automatically; no SDK source checkout or local SDK build is required. The engine depends only on -`a2a-t-core`; hosts using content generation explicitly add `a2a-t-client:1.1.0` -(and `a2a-t-server:1.1.0` for receiving-side validation). See -the [A2A-T SDK dependency guide](docs/zh/A2AT-SDK-DEPENDENCY.md) for IDEA setup and upgrade checks. +`a2a-t-core`; host agents using content generation explicitly add `a2a-t-client:1.1.0`. A dispatched-agent service that +validates received extension content adds `a2a-t-server:1.1.0`. See the bilingual +[Developer Guide](docs/en/DEVELOPER_GUIDE.md) / [开发者指南](docs/zh/DEVELOPER_GUIDE.md) for dependency and +upgrade guidance. ```xml @@ -80,7 +80,7 @@ HostQuickStart.main(new String[] { ``` This minimal example sends plain A2A content. For Task-T, Negotiation-T, Authorization-T and Notification-T content -generation/validation in host business callbacks, follow SpringSpnDemo and the business callback guide. +generation/validation in host-agent business callbacks, follow the business callback guide. Final content callbacks, complete dependency inputs and negotiation Send/Stop: [English](docs/en/BUSINESS_CALLBACKS.md) / [中文](docs/zh/BUSINESS_CALLBACKS.md). @@ -127,7 +127,7 @@ graph TD | Package | Key Classes | Description | |------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------| -| `client` | `WorkflowEngineClient`, `DefaultWorkflowEngineClient`, `ExtensionSender`, `A2ATransport`, `AuthProvider`, `AgentAuthManager`, `WorkflowEngineClientConfig`, `CredentialCrypto`, `AgentCardJacksonModule` | A2A transport, auth, extensions (package-private internals) | +| `client` | `WorkflowEngineClient`, `DefaultWorkflowEngineClient`, `ExtensionSender`, `A2ATransport`, `AuthProvider`, `WorkflowEngineClientConfig`, `CredentialCrypto`, `AgentCardJacksonModule` | Public transport, authentication, extension, and runtime APIs | | `control` | `ControlPoint`, `DefaultControlPoint`, `EventCallback`, `EventType`, `NegotiationStrategy` | User-facing decision interfaces | | `core` | `WorkflowExecutor`, `ContextBuilder` | DAG traversal, context assembly | | `model` | `Workflow`, `WorkflowStep`, `Task`, `TaskRequest`, `MessageContent`, `TaskResult`, `NegotiationRequest`, `NegotiationReply`, `ExecutionResult` | Data models | @@ -143,6 +143,7 @@ graph TD - [Integration Guide](docs/en/INTEGRATION_GUIDE.md) - Setup, configuration, secondary development - [API Reference](docs/en/API_REFERENCE.md) - Public interface and class documentation +- [Business Callback Contract](docs/en/BUSINESS_CALLBACKS.md) - Host-agent callback inputs, outputs, and ownership - [Design Document](docs/en/DESIGN.md) - Architecture, module structure, design decisions - [Developer Guide](docs/en/DEVELOPER_GUIDE.md) - Internal architecture, contribution, debugging @@ -150,9 +151,9 @@ graph TD - [集成指南](docs/zh/INTEGRATION_GUIDE.md) - 安装、配置、二次开发 - [API 参考](docs/zh/API_REFERENCE.md) - 公共接口和类文档 +- [业务回调集成契约](docs/zh/BUSINESS_CALLBACKS.md) - 宿主智能体回调的输入、输出与职责边界 - [架构设计](docs/zh/DESIGN.md) - 架构、模块结构、设计决策 - [开发者指南](docs/zh/DEVELOPER_GUIDE.md) - 内部架构、贡献、调试 -- [集成方集成指南](docs/zh/集成方场景接入指南.md) - 集成方 Spring Boot 集成完整指南 ## Modules @@ -166,22 +167,9 @@ graph TD [Apache License 2.0](LICENSE) -### Inspect negotiation in the local Demo +## Verification -Running the local SpringSpnDemo without VM options now negotiates missing City1 input; City2 diagnoses directly. This is -a local sample scenario, not an engine default. To use complete inputs in both cities, add this IDEA **VM option**: - -```text --Da2at.samples.negotiation=false -``` - -By default only City1 loses its Task-T task object. Add `-Da2at.samples.negotiation.city=city2` or `both` -to exercise City2 or both cities. The host retains city-scoped authoritative input and the complaint context is -preserved. Expect DEMO_NEGOTIATION → INPUT_REQUIRED/PROPOSE → onNegotiation → ACCEPT → both diagnoses → one aggregate. -External-OMC mode defaults to no injection and rejects an explicit true switch. The Demo passes its setting into the -current Spring application context without modifying JVM-wide properties. Protocol observations are in the console and -`logs/spn-demo.log` relative to the run directory. - -Run `SpringSpnDemoE2ETest` for direct transport. It tests the no-VM-option -single-city default, explicit disable/enable and both-city negotiation with current SDK resources and an offline LLM -provider. This is local protocol E2E evidence, not real model/platform/OMC validation. +Run `mvn -B clean verify` before release. The reactor verifies the engine, Spring Boot starter, callback examples, +protocol transport, negotiation, independent extension lifecycles, cancellation, error propagation, and credential +redaction. These automated tests use controlled fixtures; production endpoints, identity services, and live model +providers require a separate acceptance record. diff --git a/docs/diagrams/workflow-engine-surrounding-systems.mmd b/docs/diagrams/workflow-engine-surrounding-systems.mmd index c3906c96..33e5d8bc 100644 --- a/docs/diagrams/workflow-engine-surrounding-systems.mmd +++ b/docs/diagrams/workflow-engine-surrounding-systems.mmd @@ -14,50 +14,50 @@ flowchart TB classDef local fill: #F8FAFC, stroke: #64748B, color: #0F172A, stroke-dasharray: 5 3 classDef agent fill: #F5F3FF, stroke: #7C3AED, color: #2E1065, stroke-width: 1.5px - subgraph CONTROL["周边控制面与上游系统"] + subgraph CONTROL["External systems"] direction LR - WAIMO["WAIMO
上游任务发起方"]:::external - REG["注册中心 / Registry Center
AgentCard 发布与查询"]:::external - ORCH["编排中心 / Orchestration Center
工作流检索与 PSOP 加载"]:::external + CALLER["External A2A requester
Task request and final response"]:::external + REG["Registry center
AgentCard publication and discovery"]:::external + ORCH["Orchestration center
Workflow search and loading"]:::external end - subgraph HOST["宿主智能体:集成方 / Integrator Agent"] + subgraph HOST["Host agent"] direction TB - ENTRY["A2A 服务端入口
接收 Task-T,解析诊断意图"]:::host - ADAPTER["宿主集成层
准备 AgentCard、Workflow、配置和业务上下文"]:::host + ENTRY["A2A server entry
Receive and validate inbound tasks"]:::host + ADAPTER["Host integration layer
Prepare AgentCards, Workflow, configuration and context"]:::host - subgraph ENGINE["嵌入式 Workflow Execution Engine SDK"] + subgraph ENGINE["Embedded Workflow Execution Engine SDK"] direction TB - LOAD["发现辅助 API
RegistryClient / LoadPsop"]:::sdk - RUN["工作流协议调度
ExecutePsop → WorkflowExecutor
Task-T + Negotiation-T"]:::sdk - CALLBACK["宿主回调接口
ControlPoint / EventCallback / onFinish"]:::sdk - EXT["流程外协议操作
ExtensionSender
Authorization-T / Notification-T"]:::sdk + LOAD["Discovery helpers
RegistryClient / LoadPsop"]:::sdk + RUN["Workflow protocol scheduling
ExecutePsop → WorkflowExecutor
Task-T + Negotiation-T"]:::sdk + CALLBACK["Host callbacks
ControlPoint / EventCallback / onFinish"]:::sdk + EXT["Independent protocol operations
ExtensionSender
Authorization-T / Notification-T"]:::sdk end - BIZ["集成方业务实现
业务接管、路由、汇总、持久化、通知处理"]:::host + BIZ["Host business implementation
Content, routing, aggregation, persistence and notification handling"]:::host end - subgraph SAMPLE["Demo 本地替代路径(非生产数据源)"] + subgraph SAMPLE["Optional local fixtures (non-production)"] direction LR - LOCALCARD["本地 AgentCard JSON
WorkbenchAgentCatalog"]:::local - LOCALPSOP["本地 PSOP fallback
仅用于离线演示"]:::local + LOCALCARD["Local AgentCard JSON"]:::local + LOCALPSOP["Local Workflow fixture"]:::local end - subgraph DOWNSTREAM["下游业务智能体"] - OMCS["地市 OMC 智能体群
地市 1 OMC | 地市 2 OMC"]:::agent + subgraph DOWNSTREAM["Dispatched agents"] + AGENTS["One or more dispatched agents"]:::agent end - WAIMO <-->|" Task-T 诊断任务 / 汇总 artifact "| ENTRY - ENTRY -->|" 运行意图 "| ADAPTER - REG -->|" 生产:查询 AgentCard "| LOAD - ORCH -->|" search / load PSOP "| LOAD - LOCALCARD -.->|" Demo 替代注册中心 "| ADAPTER - LOCALPSOP -.->|" Demo 检索失败时 "| ADAPTER - LOAD -->|" 发现结果交给宿主 "| ADAPTER + CALLER <-->|"A2A request / final result"| ENTRY + ENTRY -->|"Validated intent and input"| ADAPTER + REG -->|"Discover AgentCards"| LOAD + ORCH -->|"Search / load Workflow"| LOAD + LOCALCARD -.->|"Development fixture"| ADAPTER + LOCALPSOP -.->|"Development fixture"| ADAPTER + LOAD -->|"Discovery results"| ADAPTER ADAPTER -->|" Workflow + AgentCards "| RUN - RUN <-->|" 业务决策与执行结果 "| CALLBACK - CALLBACK <-->|" 回调 "| BIZ - ADAPTER -->|" 独立业务时机 "| EXT - RUN <-->|" 向两地市并行 Task-T
必要时 Negotiation-T / 返回诊断结果 "| OMCS - EXT <-->|" Authorization-T 一次性授权
Notification-T 长连接订阅与结果 SSE "| OMCS - BIZ -->|" 汇总结果 "| ENTRY + RUN <-->|"Business decisions and results"| CALLBACK + CALLBACK <-->|"Callbacks"| BIZ + ADAPTER -->|"Independent business timing"| EXT + RUN <-->|"Task-T; Negotiation-T when required"| AGENTS + EXT <-->|"Authorization-T / Notification-T"| AGENTS + BIZ -->|"Final business result"| ENTRY diff --git a/docs/en/API_REFERENCE.md b/docs/en/API_REFERENCE.md index aca2b997..f20dd20c 100644 --- a/docs/en/API_REFERENCE.md +++ b/docs/en/API_REFERENCE.md @@ -10,6 +10,7 @@ | `dev.openan.workflow.engine.model` | Data models (Workflow, Task, results) | | `dev.openan.workflow.engine.registry` | PSOP loading and AgentCard registry | | `dev.openan.workflow.engine.runner` | Entry point for workflow execution | +| `dev.openan.workflow.engine.spring` | Spring Boot server auto-configuration (starter) | --- @@ -24,7 +25,7 @@ Entry point for executing a PSOP workflow. Uses the Builder pattern. | Method | Type | Default | Description | |------------------------------------------|----------|-------------|----------------------------------------------| | `psop(Workflow)` | required | - | PSOP workflow definition | -| `agentCards(List)` | required | `List.of()` | Agent cards for all agents in the workflow | +| `agentCards(List)` | optional | `List.of()` | Cards for dispatched agents; required for remote steps unless a configured `engineClient` supplies transport | | `controlPoint(ControlPoint)` | required | - | User decision implementation | | `engineClient(WorkflowEngineClient)` | optional | null | Pre-configured client (null = auto-create) | | `runtimeIntent(String)` | optional | `""` | Natural-language intent for context assembly | @@ -42,7 +43,7 @@ ExecutionResult result = ExecutePsop.builder() .psop(workflow) .agentCards(cards) .controlPoint(cp) - .runtimeIntent("diagnose fault") + .runtimeIntent("analyze request") .sslVerify(false) .execute() .get(10, TimeUnit.MINUTES); @@ -84,7 +85,7 @@ Unchanged waiting state is observed with getTask. `maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout, exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never -diagnosis success, even if the remote acknowledges it with COMPLETED. +task success, even if the dispatched agent acknowledges it with COMPLETED. ### ExtensionSender @@ -96,8 +97,8 @@ NotificationSubscription openNotification(String agentName, MessageContent conte Authorization and notification accept host-generated final content. Use separate transport/runtime/context instances; their outcomes do not gate the workflow. openNotification registers a handle before I/O, and passes it plus -ReceivedMessage directly to the listener. acknowledgement () is the real ACK; timeout fails. close () requests closure; -completion () observes actual stream termination. +ReceivedMessage directly to the listener. acknowledgement() is the real ACK; timeout fails. close() requests closure; +completion() observes actual stream termination. ### WorkflowEngineClientConfig @@ -153,9 +154,9 @@ the same header name throw `SecurityException` instead of silently overwriting e ### A2atMessages -Submit custom extensions via MessageContent (parts, metadata, extensions); no engine content-handler registration. -A2atMessages.from (MetadataContent, List>) preserves SDK metadata and activates the corresponding URI; contextOf -(ReceivedMessage) or contextOf (Map) reads/checks canonical negotiation context. This adapter uses +Submit custom extensions via MessageContent(parts, metadata, extensions); no engine content-handler registration. +A2atMessages.from(MetadataContent, List>) preserves SDK metadata and activates the corresponding URI; contextOf +(ReceivedMessage) or contextOf(Map) reads/checks canonical negotiation context. This adapter uses a2a-t-core only, never content generation or semantic validation. ### A2AJavaClientRuntime @@ -231,6 +232,26 @@ custom normalization: Map normalized = AgentCardNormalizer.normalize(rawMap); ``` +### Supporting public client types + +The following public types support advanced integrations. Prefer the higher-level interfaces above unless a custom +runtime, diagnostic adapter, or explicit lifecycle requires them. + +| Type | Purpose | +|-------------------------------|---------| +| `A2ATExtension` | Canonical extension names and URIs | +| `A2ATransport` | Low-level transport, authentication, response assembly, and subscription lifecycle | +| `DefaultWorkflowEngineClient` | Default `WorkflowEngineClient` implementation | +| `DefaultExtensionSender` | Default `ExtensionSender` implementation backed by an `A2ATransport` | +| `DefaultA2AJavaClientRuntime` | Default A2A Java SDK runtime for HTTP/JSON-RPC/gRPC | +| `CredentialCrypto` | AES-GCM credential encryption utility and command-line entry point | +| `EnvFileLoader` | Explicit `.env` parser for host-owned configuration | +| `SslContextFactory` | TLS context construction used by transport and discovery helpers | +| `ProtocolResponses` | A2A event/result assembly helpers | +| `ClientEventMapper` | Stable event projection for callbacks and diagnostics | +| `WireLog` | Correlation context and protocol-observation facade | +| `RemoteProblemException` | Structured remote problem response (`status`, `title`, `detail`, `type`, `timestamp`) | + --- ## dev.openan.workflow.engine.control @@ -249,6 +270,10 @@ interface ControlPoint { onTask returns final parts/metadata/extensions; the engine sends them without generating or rewriting content. onSelfTask returns local TaskResult, onRoute selects an allowed candidate, and onNegotiation returns Send or Stop. Unimplemented callbacks fail explicitly. No echo-success, first-branch choice or automatic consent. + +`DefaultControlPoint` preserves those fail-fast defaults and can delegate `onNegotiation` to an injected +`NegotiationStrategy`. Implement `NegotiationStrategy.resolve(NegotiationRequest)` when negotiation policy is the only +custom decision; implement or build a full `ControlPoint` when task, self-task, or routing behavior also changes. See [Business callback contract](BUSINESS_CALLBACKS.md) for fields and working examples. ### EventCallback @@ -373,12 +398,6 @@ WorkflowExecutor executor = new WorkflowExecutor( ExecutionResult result = executor.run().join(); ``` -### ContextBuilder - -Package-private helper that selects upstream step results and creates a `WorkflowInput` according to `contextFrom` -(omitted = direct predecessors, `[]` = no upstream input, `"*"` = all ancestors, explicit names = selective -inheritance). It does not render prompts and is not part of the public API surface. - --- ## dev.openan.workflow.engine.model @@ -411,7 +430,7 @@ Static factory: `Workflow.fromMap(Map)` parses from orchestratio |---------------|------------------------------------------------------------------------------------------------------------------------| | `ALL_SUCCESS` | All subtasks must succeed | | `ANY_SUCCESS` | Any subtask success is sufficient | -| `SELF_LOOP` | The workflow agent handles the task locally via `onSelfTask`; no A2A-T message is sent. Success follows `ALL_SUCCESS`. | +| `SELF_LOOP` | The host agent handles the task locally via `onSelfTask`; no A2A-T message is sent. Success follows `ALL_SUCCESS`. | ### TaskStatus @@ -441,7 +460,7 @@ Task lifecycle status, used in `TASK_STATUS_CHANGED` events for cross-SDK consis ### TaskRequest / BusinessInput -TaskRequest uses getXxx () accessors: +TaskRequest uses getXxx() accessors: | Field | Meaning | |------------------------------|-----------------------------------------------------------------------------------| @@ -451,8 +470,10 @@ TaskRequest uses getXxx () accessors: | input | BusinessInput: exactly one of text or arbitrary JSON-serializable data; no schema | | workflowInput | WorkflowInput(runtimeIntent, upstreamResults), separate from current input | -BusinessInput.text (value) / BusinessInput.data (value) create input snapshots. WorkflowInput, UpstreamStepResult, -ReceivedMessage and NegotiationRequest use record field () accessors. +BusinessInput.text(value) / BusinessInput.data(value) create input snapshots. WorkflowInput, UpstreamStepResult, +ReceivedMessage and NegotiationRequest use record field() accessors. +`BusinessValues.snapshot`, `map`, and `list` expose the same defensive JSON snapshot behavior for host-owned values; +nested arrays remain individual values and returned collections are unmodifiable. | contextFrom | Upstream selection | |-------------------------|----------------------------------------------| @@ -477,7 +498,7 @@ record MessageContent(List> parts, Map metadata, Set taskMetadata, List artifacts) {} ``` -MessageContent.text (text), MessageContent.parts (parts), or the canonical constructor create snapshots. TextPart, +MessageContent.text(text), MessageContent.parts(parts), or the canonical constructor create snapshots. TextPart, DataPart and FilePart preserve order and part metadata. File references are not fetched. MessageContent has no role, target, messageId, taskId, contextId or auth headers. A business metadata key named contextId does not override the A2A envelope. @@ -490,7 +511,7 @@ adjacent-part concatenation or nested-array flattening. Failed task status messa partial artifacts are retained. Streaming append/replace is assembled per artifact; terminal snapshots do not duplicate earlier chunks. -TaskResult.success (List) represents local output, including an empty list. TaskResult.failure (code, message) +TaskResult.success(List) represents local output, including an empty list. TaskResult.failure(code, message) separates failure from output; its builder can retain valid partial outputs. TaskResult includes receivedMessages for remote evidence; remote convenience outputs derive from that view. A remote task succeeds only in COMPLETED, or a standalone A2A Message can complete the interaction. Progress and negotiation are never a successful workflow task @@ -502,7 +523,7 @@ See [BUSINESS_CALLBACKS](BUSINESS_CALLBACKS.md). ### SendMessageResult -getReceivedMessages () preserves response levels; getOutputs () is a convenience projection. getTask ()/getTaskState () +getReceivedMessages() preserves response levels; getOutputs() is a convenience projection. getTask()/getTaskState() retain actual remote state; failureCode/failureMessage describe independent local interaction failure. text and flattened metadata are transport diagnostics, not substitutes for the complete business response. @@ -548,8 +569,9 @@ flattened metadata are transport diagnostics, not substitutes for the complete b | Authorization-T | `https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1` | | Notification-T | `https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1` | -Canonical negotiation metadata is `templateUri` plus -`negotiationContext={id,round,maxRounds,performative}`. Legacy state-machine keys are not accepted. +Canonical negotiation metadata uses `templateUri` and `negotiationContext`. The latter carries +`{id, round, maxRounds, performative}` and is required when the engine parses a negotiation message. The engine does +not add private negotiation-state keys to the wire message. --- @@ -565,12 +587,11 @@ Canonical negotiation metadata is `templateUri` plus `findIn(Throwable)` finds it through wrappers. Workflow failures use `remote.problem.`, with details in history / TASK_RESPONSE rather than business outputs. Executor node events include executionId; task response events and history include the logical taskId. - See [integration guidance](INTEGRATION_GUIDE.md#remote-problem-responses) for propagation and logging. + See [integration guidance](INTEGRATION_GUIDE.md#14-remote-problem-responses) for propagation and logging. - Missing/null/exceptional/timed-out callbacks fail explicitly. Host SDK errors may be mapped to BusinessFailure with safe code/details. -- Exhausted maxNegotiationExchanges or local Stop does not generate Abort. Business Send (Abort) is never diagnosis - success. +- Exhausted maxNegotiationExchanges or local Stop does not generate Abort. Business Send(Abort) is never task success. - Remote state/errors stay separate from outputs; failed status text is evidence, while partial artifacts remain available. - Missing required credentials reject the request; the engine never silently sends unauthenticated. @@ -645,11 +666,10 @@ The partner only needs to provide an `AgentExecutor` implementation: @Component public class MyAgentExecutor implements AgentExecutor { @Override - public ExecuteResult execute(ExecuteRequest request) { - // business logic - return ExecuteResult.builder() - .addTextPart("result text") - .build(); + public void execute(RequestContext context, AgentEmitter emitter) throws A2AError { + // business logic, then emit progress and the final result: + emitter.updateStatus(TaskState.WORKING, statusMessage); + emitter.addArtifact(List.of(new TextPart("result text"))); } } ``` diff --git a/docs/en/BUSINESS_CALLBACKS.md b/docs/en/BUSINESS_CALLBACKS.md index eb68a07f..35550b6f 100644 --- a/docs/en/BUSINESS_CALLBACKS.md +++ b/docs/en/BUSINESS_CALLBACKS.md @@ -40,7 +40,7 @@ real business behavior. onTask must not send a second network request itself. Im ## 2. TaskRequest and dependency input -TaskRequest uses getXxx () accessors: +TaskRequest uses getXxx() accessors: | Field | Meaning | |------------------------------|-----------------------------------------------------------------------------------| @@ -50,8 +50,8 @@ TaskRequest uses getXxx () accessors: | input | BusinessInput: exactly one of text or arbitrary JSON-serializable data; no schema | | workflowInput | WorkflowInput(runtimeIntent, upstreamResults), separate from current input | -BusinessInput.text (value) / BusinessInput.data (value) create input snapshots. WorkflowInput, UpstreamStepResult, -ReceivedMessage and NegotiationRequest use record field () accessors. +BusinessInput.text(value) / BusinessInput.data(value) create input snapshots. WorkflowInput, UpstreamStepResult, +ReceivedMessage and NegotiationRequest use record field() accessors. | contextFrom | Upstream selection | |-------------------------|----------------------------------------------| @@ -76,7 +76,7 @@ record MessageContent(List> parts, Map metadata, Set taskMetadata, List artifacts) {} ``` -MessageContent.text (text), MessageContent.parts (parts), or the canonical constructor create snapshots. TextPart, +MessageContent.text(text), MessageContent.parts(parts), or the canonical constructor create snapshots. TextPart, DataPart and FilePart preserve order and part metadata. File references are not fetched. MessageContent has no role, target, messageId, taskId, contextId or auth headers. A business metadata key named contextId does not override the A2A envelope. @@ -89,7 +89,7 @@ adjacent-part concatenation or nested-array flattening. Failed task status messa partial artifacts are retained. Streaming append/replace is assembled per artifact; terminal snapshots do not duplicate earlier chunks. -TaskResult.success (List) represents local output, including an empty list. TaskResult.failure (code, message) +TaskResult.success(List) represents local output, including an empty list. TaskResult.failure(code, message) separates failure from output; its builder can retain valid partial outputs. TaskResult includes receivedMessages for remote evidence; remote convenience outputs derive from that view. A remote task succeeds only in COMPLETED, or a standalone A2A Message can complete the interaction. Progress and negotiation are never a successful workflow task @@ -101,24 +101,23 @@ Pure engine consumers only need a2a-t-core; hosts generating content explicitly A2ATClient in the host, not in WorkflowEngineClientConfig or ExecutePsop. ```java -// sdk, data, schema and template selection are owned by host code. +// sdk, data, schema and template selection are owned by host-agent code. MetadataContent generated = sdk.generateTaskPromptFromDataWithSchema( data, schema, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri()); -MessageContent outgoing = A2atMessages.from(generated, List.of(new TextPart("Diagnose this line"))); +MessageContent outgoing = A2atMessages.from(generated, List.of(new TextPart("Process this task"))); ``` -Natural-language equivalent: `sdk.generateTaskPromptFromText(text, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri())`, -then use the same A2atMessages.from conversion. +Natural-language equivalent: `sdk.generateTaskPromptFromText(text, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri())`, then use the same +A2atMessages.from conversion. Natural-language generation/validation and all other A2A-T content APIs are called directly on the host's SDK. A2atMessages.from copies generated metadata and activates exactly its extension; supplied parts stay unchanged. It does -not generate, validate or parse business content. Use StandardTemplates, not invented template URIs. -WorkbenchControlPoint and examples.negotiation.NegotiationStrategy provide runnable SPN implementations. -A2ATInitialization is a sample-host workaround for the pinned SDK's cached JarFile lifecycle, not an engine dependency. +not generate, validate or parse business content. Select a URI published by the active A2A-T SDK rather than inventing +one. Sample initialization helpers are host-side examples, not engine dependencies. ## 5. Negotiation -NegotiationRequest (task, originalSubmission, received, previousExchanges, remainingWait): +NegotiationRequest(task, originalSubmission, received, previousExchanges, remainingWait): task is the original TaskRequest; originalSubmission is the exact initial content; received is the complete current response; previousExchanges contains only this session's completed Exchange (received, reply); remainingWait is the remaining local interaction deadline. There are no engine-defined business proposal schemas. @@ -135,20 +134,20 @@ Unchanged waiting state is observed with getTask. `maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout, exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never -diagnosis success, even if the remote acknowledges it with COMPLETED. +task success, even if the dispatched agent acknowledges it with COMPLETED. Host policy may call validateProposePromptAndDataFilling and typed fromData or natural-language SDK generation. It must -not invent missing customer facts. The sample supplies only requested fields from the current city's input. Map SDK -business exceptions to BusinessFailure (code, safeMessage, safeDetails) in host code. The engine does not recognize +not invent missing business facts. Supply only requested fields from the current task's authoritative input. Map SDK +business exceptions to BusinessFailure(code, safeMessage, safeDetails) in host code. The engine does not recognize SDK-specific content exception classes. ## 6. Routing, concurrency and failures -RouteRequest (executionId, stepName, workflowInput, currentResults, candidates). Candidates are RouteOption (nextStep, -condition); return RouteDecision.builder ().nextStep (allowed).build (). Unconditional edges fan out without calling +RouteRequest(executionId, stepName, workflowInput, currentResults, candidates). Candidates are RouteOption(nextStep, +condition); return RouteDecision.builder().nextStep(allowed).build(). Unconditional edges fan out without calling onRoute; conditional selection must be an allowed destination. -Callbacks may run concurrently; do not hold a shared mutable current task/city. Each workflow task activation +Callbacks may run concurrently; do not hold shared mutable current-task state. Each workflow task activation (preparation and dispatch combined) is bounded by the client timeout (default sendTimeoutSeconds=600). Routing has a callback timeout; dispatch/negotiation additionally has its own total wait deadline. Cancellation/timeout prevents late sends; it does not automatically stop external business/LLM work. Hosts own cancellation of their resources. Synchronous @@ -157,111 +156,53 @@ Missing/null/exceptional callbacks fail; uncertain sends are not blindly retried ## 7. Independent extensions -ExtensionSender.sendAuthorization (agentName, finalContent) returns CompletableFuture. -ExtensionSender.openNotification (agentName, finalContent, (handle, received) -> ...) returns the handle immediately. +ExtensionSender.sendAuthorization(agentName, finalContent) returns CompletableFuture. +ExtensionSender.openNotification(agentName, finalContent, (handle, received) -> ...) returns the handle immediately. Use host-generated Authorization-T or Notification-T content with the matching activated extension. The handle is -registered before I/O; early callbacks can call handle.close () without capturing an unassigned variable. -acknowledgement () reports actual ACK or failure; timeout is not a synthesized success. close () requests shutdown; -completion () completes after the stream actually exits. Retain it until recovery, explicit cancellation or host -shutdown, not just workflow completion. +registered before I/O; early callbacks can call handle.close() without capturing an unassigned variable. +acknowledgement() reports actual ACK or failure; timeout is not a synthesized success. close() requests shutdown; +completion() completes after the stream actually exits. Retain it until the host-defined terminal event, explicit +cancellation, or host-agent shutdown, not just workflow completion. Task, authorization and notification use separate transports/runtimes/contexts. Neither extension success nor failure is -a prerequisite for workflow execution. Whitelist success only controls the OMC's optional automatic recovery behavior. +a prerequisite for workflow execution. Authorization controls only the host-defined action associated with that policy. ## 8. Verification and logs -### Inspect negotiation in the local Demo - -Running the local SpringSpnDemo without VM options now negotiates missing City1 input; City2 diagnoses directly. This is -a local sample scenario, not an engine default. To use complete inputs in both cities, add this IDEA **VM option**: - -```text --Da2at.samples.negotiation=false -``` - -By default only City1 loses its Task-T task object. Add `-Da2at.samples.negotiation.city=city2` or `both` -to exercise City2 or both cities. The host retains city-scoped authoritative input and the complaint context is -preserved. Expect DEMO_NEGOTIATION → INPUT_REQUIRED/PROPOSE → onNegotiation → ACCEPT → both diagnoses → one aggregate. -External-OMC mode defaults to no injection and rejects an explicit true switch. The Demo passes its setting into the -current Spring application context without modifying JVM-wide properties. Protocol observations are in the console and -`logs/spn-demo.log` relative to the run directory. - -Run `SpringSpnDemoE2ETest` for direct transport. It tests the no-VM-option -single-city default, explicit disable/enable and both-city negotiation with current SDK resources and an offline LLM -provider. This is local protocol E2E evidence, not real model/platform/OMC validation. - -Actual startup tests use public simulator credentials and do not shadow SDK schemas/templates. Run main/dev tests -sequentially; fixed local E2E ports must be free. After a local workflow, the demo may observe the first recovery -notification for up to ten seconds; absence of a recovery event does not change workflow success. -See [Integration guide](INTEGRATION_GUIDE.md) for logging and authentication configuration. +Run `mvn -B clean verify`. The reactor covers callback contracts, complete response assembly, negotiation association, +independent extension lifecycles, failure propagation, cancellation, and protocol redaction. Tests use controlled +fixtures and do not shadow SDK schemas/templates. They are release regression evidence, not certification of production +endpoints or live model semantics. See [Integration guide](INTEGRATION_GUIDE.md) for logging and authentication. ## 9. Business-side A2A-T 1.1.0 reference -Content generation, validation, filling and domain decisions belong to the sample host/OMC code, not the engine. Each -role calls the APIs it needs; do not invoke every generation alternative for the same message. - -| Business path | SDK generation and receiving validation | Consumed result | -|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| WAIMO → integrator | generateTaskPromptFromDataWithSchema → validateTaskPromptAndDataFilling | WorkbenchTaskInputParser retains filled.data for workflow selection | -| onTask → either OMC | generateTaskPromptFromDataWithSchema → validateTaskPromptAndDataFilling | SpnTaskInput built from filled.data, not raw protocol text | -| Either OMC → onNegotiation | generateNegotiationProposePromptFromData → validateProposePromptAndDataFilling | Requested items answered only from the current city's authoritative input | -| Host Accept → OMC | generateNegotiationAcceptPromptFromData → validateAcceptPromptAndDataFilling | Merge requested filled fields; validate domain requirements before diagnosis | -| Host Reject / Abort → OMC | generateNegotiationRejectPromptFromData / generateNegotiationAbortPromptFromData → corresponding validateRejectPromptAndDataFilling / validateAbortPromptAndDataFilling | Validate reason, end the task without diagnosis | -| Independent authorization | generateAuthPromptFromDataWithSchema → validateAuthPromptAndDataFilling | Apply AuthorizationPolicy from filled.data | -| Independent subscription | generateNotificationPromptFromDataWithSchema → validateNotificationPromptAndDataFilling | Create NotificationPolicy and keep a separate stream open | - -For natural-language inputs choose generateTaskPromptFromText (text, templateUri), generateAuthPromptFromText (text, -templateUri), or generateNotificationPromptFromText (text, templateUri). Negotiation also offers phase-specific FromText +Content generation, validation, filling and domain decisions belong to host-agent and dispatched-agent business code, +not the engine. Each role calls only the APIs needed for the message it owns. + +| Business path | SDK generation and receiving validation | Consumed result | +|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| Host-agent inbound task | validateTaskPromptAndDataFilling | Use validated `filled.data` for workflow selection and business input | +| onTask → dispatched agent | generateTaskPromptFromDataWithSchema or generateTaskPromptFromText → validateTaskPromptAndDataFilling | Build dispatched-agent input from validated data, not raw extension text | +| Dispatched agent → host negotiation | generateNegotiationProposePromptFromData or FromText → validateProposePromptAndDataFilling | Answer only requested items from authoritative task data | +| Host Accept → dispatched agent | generateNegotiationAcceptPromptFromData or FromText → validateAcceptPromptAndDataFilling | Merge validated requested fields and re-check domain requirements | +| Host Reject / Abort → dispatched agent | Phase-specific generation → corresponding validation | Validate the reason and terminate the current task path | +| Independent authorization | generateAuthPromptFromDataWithSchema → validateAuthPromptAndDataFilling | Apply AuthorizationPolicy from `filled.data` | +| Independent subscription | generateNotificationPromptFromDataWithSchema → validateNotificationPromptAndDataFilling | Create NotificationPolicy and keep a separate stream open | + +For natural-language inputs choose generateTaskPromptFromText(text, templateUri), generateAuthPromptFromText(text, +templateUri), or generateNotificationPromptFromText(text, templateUri). Negotiation also offers phase-specific FromText and typed FromData alternatives. Structured generation may still call the SDK's configured LLM. Wrap the returned -MetadataContent with A2atMessages.from (generated, List.of (new TextPart ("business summary"))). Preserve full metadata, +MetadataContent with A2atMessages.from(generated, List.of (new TextPart("business summary"))). Preserve full metadata, including templateUri and negotiationContext; provide nonempty parts. The parts summary is not the formal extension validation input. -The complaint example negotiates the canonical business fields 任务对象 and 任务上下文. The former identifies this -city's access port; the latter retains classification, OSS incident ID, time and details. Only unresolved fields are -requested when trusted partial data exists. If the SDK rejects the whole input, none of its partial fields are trusted. -The current proposal uses AND: all requested fields are required. Propose extraction uses items (nonempty field-name -array) and nullable relationship; Accept extraction dynamically requires the requested nonblank fields. These SPN domain -checks are sample rules, not generic engine constraints. - Replies must match A2A task/context IDs and negotiation id/round/maxRounds. Formal text comes from Negotiation-T -metadata. Validated replies are consumed once; malformed, incomplete, foreign-city or cross-negotiation replies never -run diagnosis. NegotiationStrategy.mayProvideField (task, fieldName) is the host disclosure-policy hook: false produces -SDK Reject. Missing authoritative information produces SDK Abort, not invented values. This policy is separate from -Authorization-T recovery permissions. - -Do not reuse subscription-request generation/validation for recovery events: diagnosis results, authorization receipts -and recovery events are OMC business outputs. RecoveryNotification checks the SPN result fields in formal Notification-T -artifact metadata before closing the subscription: terminal state, task/incident IDs, port, plan and execution outcome. -Both successful and failed finished recoveries are terminal, without treating failure as success. A name, summary, ACK -or not-started plan alone never closes the stream. Invalid events remain observable by the callback. City1 uses the -actual validated port and OSS incident ID. A healthy City2 need not emit a recovery result. Authorization/subscription -outcomes remain independent of workflow execution. - -EmbeddedA2AServerTest covers normal/Accept filled data, invalid formal text, missing fields, cross-negotiation replies, -Reject and Abort. NegotiationStrategyTest covers city isolation and host Abort/Reject policy. RecoveryNotificationTest -covers complete success/failure and malformed/incomplete events. SpringSpnDemoE2ETest covers full input, City1 missing input and both cities missing input; SpnCrossCityE2ETest -checks the recovery lifecycle. Tests use actual SDK pipelines with a strict fixture-only offline LLM provider, not a -live-model semantic evaluation or real-OMC certification. - -### Authorization format and negotiation keys (SDK 1.1.0) - -ADD uses consecutive numbered lines, labeled values and full-width commas; multiple rules use newlines, not semicolons. -Example: -`1. 业务场景是业务投诉诊断,处置类型是业务抢通,操作名称是隧道调优,有效期是2026-06-01~2030-06-18`. Validity is an -inclusive ISO-date range or `永久生效`. Parse every SDK-validated rule before replacing the whitelist; malformed rules -leave it unchanged. Authorization uses exact scenario/disposal/action matches, never fuzzy matching. - -The sample stores one in-memory whitelist with a fixed demonstration ID. DELETE accepts only -`1. 策略标识是`. QUERY requires an omitted or empty policy list and returns all active rules. -MODIFY, filtered queries and batch/conditional deletion require an integrating policy store; unsupported conditions are -rejected, never silently ignored. These are sample business limits, not limitations of the A2A-T SDK. - -After SDK Propose semantic validation, the sample preserves literal numbered item names from the current Chinese typed -information template. LLM paraphrases of descriptions must not rename an item or broaden disclosure. Replies use only -the current city's authoritative input; unknown/missing data causes Abort and a host disclosure denial causes Reject. -This handling belongs to sample business code, not the workflow engine. - -Notification requests use the complete failure-reason field name `业务抢通方案执行失败原因`, required when execution -fails. SDK rejection logs include its code, slot errors and extracted parameter keys; validation is not bypassed. -Authorization and subscription failures still do not block the workflow. +metadata. Validated replies are consumed once; malformed, incomplete, mismatched-task, or cross-negotiation replies +must not execute business work. Missing authoritative information produces SDK Abort rather than invented values; a +host disclosure denial produces SDK Reject. Authorization policy is a separate concern. + +Authorization and Notification generation APIs create requests, not arbitrary business results. Authorization receipts +and notification events are dispatched-agent business outputs and must follow the selected template and application +contract. A subscription closes only on a host-defined terminal event, explicit cancellation, or shutdown; an ACK alone +is not a terminal business event. SDK rejection should preserve its safe code, slot errors, and extracted parameter keys. +Authorization and subscription failures still do not block workflow execution. diff --git a/docs/en/DESIGN.md b/docs/en/DESIGN.md index 9c5dd2b7..01e4178d 100644 --- a/docs/en/DESIGN.md +++ b/docs/en/DESIGN.md @@ -41,74 +41,71 @@ flowchart TB classDef local fill: #F8FAFC, stroke: #64748B, color: #0F172A, stroke-dasharray: 5 3 classDef agent fill: #F5F3FF, stroke: #7C3AED, color: #2E1065, stroke-width: 1.5px - subgraph CONTROL["周边控制面与上游系统"] + subgraph CONTROL["External systems"] direction LR - WAIMO["WAIMO
上游任务发起方"]:::external - REG["注册中心 / Registry Center
AgentCard 发布与查询"]:::external - ORCH["编排中心 / Orchestration Center
工作流检索与 PSOP 加载"]:::external + CALLER["External A2A requester
Task request and final response"]:::external + REG["Registry center
AgentCard publication and discovery"]:::external + ORCH["Orchestration center
Workflow search and loading"]:::external end - subgraph HOST["宿主智能体:集成方 / Integrator Agent"] + subgraph HOST["Host agent"] direction TB - ENTRY["A2A 服务端入口
接收 Task-T,解析诊断意图"]:::host - ADAPTER["宿主集成层
准备 AgentCard、Workflow、配置和业务上下文"]:::host + ENTRY["A2A server entry
Receive and validate inbound tasks"]:::host + ADAPTER["Host integration layer
Prepare AgentCards, Workflow, configuration and context"]:::host - subgraph ENGINE["嵌入式 Workflow Execution Engine SDK"] + subgraph ENGINE["Embedded Workflow Execution Engine SDK"] direction TB - LOAD["发现辅助 API
RegistryClient / LoadPsop"]:::sdk - RUN["工作流协议调度
ExecutePsop → WorkflowExecutor
Task-T + Negotiation-T"]:::sdk - CALLBACK["宿主回调接口
ControlPoint / EventCallback / onFinish"]:::sdk - EXT["流程外协议操作
ExtensionSender
Authorization-T / Notification-T"]:::sdk + LOAD["Discovery helpers
RegistryClient / LoadPsop"]:::sdk + RUN["Workflow protocol scheduling
ExecutePsop → WorkflowExecutor
Task-T + Negotiation-T"]:::sdk + CALLBACK["Host callbacks
ControlPoint / EventCallback / onFinish"]:::sdk + EXT["Independent protocol operations
ExtensionSender
Authorization-T / Notification-T"]:::sdk end - BIZ["集成方业务实现
业务接管、路由、汇总、持久化、通知处理"]:::host + BIZ["Host business implementation
Content, routing, aggregation, persistence and notification handling"]:::host end - subgraph SAMPLE["Demo 本地替代路径(非生产数据源)"] + subgraph SAMPLE["Optional local fixtures (non-production)"] direction LR - LOCALCARD["本地 AgentCard JSON
WorkbenchAgentCatalog"]:::local - LOCALPSOP["本地 PSOP fallback
仅用于离线演示"]:::local + LOCALCARD["Local AgentCard JSON"]:::local + LOCALPSOP["Local Workflow fixture"]:::local end - subgraph DOWNSTREAM["下游业务智能体"] - OMCS["地市 OMC 智能体群
地市 1 OMC | 地市 2 OMC"]:::agent + subgraph DOWNSTREAM["Dispatched agents"] + AGENTS["One or more dispatched agents"]:::agent end - WAIMO <-->|" Task-T 诊断任务 / 汇总 artifact "| ENTRY - ENTRY -->|" 运行意图 "| ADAPTER - REG -->|" 生产:查询 AgentCard "| LOAD - ORCH -->|" search / load PSOP "| LOAD - LOCALCARD -.->|" Demo 替代注册中心 "| ADAPTER - LOCALPSOP -.->|" Demo 检索失败时 "| ADAPTER - LOAD -->|" 发现结果交给宿主 "| ADAPTER + CALLER <-->|"A2A request / final result"| ENTRY + ENTRY -->|"Validated intent and input"| ADAPTER + REG -->|"Discover AgentCards"| LOAD + ORCH -->|"Search / load Workflow"| LOAD + LOCALCARD -.->|"Development fixture"| ADAPTER + LOCALPSOP -.->|"Development fixture"| ADAPTER + LOAD -->|"Discovery results"| ADAPTER ADAPTER -->|" Workflow + AgentCards "| RUN - RUN <-->|" 业务决策与执行结果 "| CALLBACK - CALLBACK <-->|" 回调 "| BIZ - ADAPTER -->|" 独立业务时机 "| EXT - RUN <-->|" 向两地市并行 Task-T
必要时 Negotiation-T / 返回诊断结果 "| OMCS - EXT <-->|" Authorization-T 一次性授权
Notification-T 长连接订阅与结果 SSE "| OMCS - BIZ -->|" 汇总结果 "| ENTRY + RUN <-->|"Business decisions and results"| CALLBACK + CALLBACK <-->|"Callbacks"| BIZ + ADAPTER -->|"Independent business timing"| EXT + RUN <-->|"Task-T; Negotiation-T when required"| AGENTS + EXT <-->|"Authorization-T / Notification-T"| AGENTS + BIZ -->|"Final business result"| ENTRY ``` The SDK in this diagram is a library embedded in the host-agent process, not a separately deployed orchestration -service. In `SpringSpnDemo`, the transport integrator is the host agent: +service: -1. WAIMO invokes the integrator's A2A server endpoint with a Task-T diagnosis request. -2. The integrator prepares execution inputs. In production it obtains downstream AgentCards from the registry center and - searches/loads a PSOP from the orchestration center. The SDK offers optional - `RegistryClient` and `LoadPsop` helpers, while discovery timing, caching, and failure policy remain host +1. The host agent receives and validates an inbound A2A task, then prepares the execution intent and business input. +2. The host agent obtains dispatched-agent cards from a registry and loads a `Workflow` from an orchestration center. + `RegistryClient` and `LoadPsop` are optional helpers; discovery timing, caching, and failure policy remain host-agent responsibilities. -3. The integrator passes the `Workflow`, AgentCards, runtime intent, and business callbacks to - `ExecutePsop`. The engine walks the DAG, dispatches both city tasks in parallel, and handles Negotiation-T when - needed. `ControlPoint` returns control to the integrator for local aggregation, routing, and clarification decisions. -4. The integrator returns the aggregate as a Task-T artifact and terminal status to WAIMO. -5. Authorization-T and Notification-T are invoked by the integrator at independent business times through - `ExtensionSender`. They are outside the PSOP DAG and do not share a transport, runtime, or context with workflow - tasks. - -For offline execution, the demo loads AgentCards from the classpath through `WorkbenchAgentCatalog` -and uses a local PSOP fallback only if orchestration-center search or load fails. These are sample substitutes, not -production discovery or disaster-recovery rules. The editable diagram source is +3. The host agent passes the `Workflow`, AgentCards, runtime intent, and callbacks to `ExecutePsop`. The engine walks the + DAG, dispatches ready tasks concurrently, and handles Negotiation-T when required. `ControlPoint` returns business + content, local results, routing decisions, and negotiation replies. +4. The host agent consumes `ExecutionResult` and returns or persists the final business result. +5. Authorization-T and Notification-T are invoked by the host agent at independent business times through + `ExtensionSender`. They are outside the DAG and do not share a transport, runtime, or context with workflow tasks. + +Local AgentCards and workflows are development fixtures, not production discovery or disaster-recovery rules. The +editable diagram source is [`docs/diagrams/workflow-engine-surrounding-systems.mmd`](../diagrams/workflow-engine-surrounding-systems.mmd). --- @@ -199,11 +196,11 @@ Unchanged waiting state is observed with getTask. `maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout, exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never -diagnosis success, even if the remote acknowledges it with COMPLETED. +task success, even if the dispatched agent acknowledges it with COMPLETED. Authorization-T and Notification-T are independent of the DAG. The host generates content and uses dedicated senders; -failure does not affect the workflow. Whitelists only control optional OMC recovery. Subscriptions stay open until the -host closes them. +failure does not affect the workflow. Authorization policy controls only its own business action. Subscriptions stay +open until the host closes them. ## 6. Condition Routing @@ -246,7 +243,7 @@ through the `ExtensionSender` result, and subscription events through the sequenceDiagram participant H as Host callbacks + A2A-T client participant E as Workflow engine - participant A as Remote agent + participant A as Dispatched agent E->>H: onTask(TaskRequest + upstream window) H->>H: Generate/validate final content H-->>E: MessageContent @@ -267,7 +264,7 @@ sequenceDiagram sequenceDiagram participant H as Host + A2A-T client participant ES as Independent ExtensionSender - participant A as OMC + participant A as Dispatched agent H->>H: Generate final authorization content H->>ES: sendAuthorization(agent, content) ES->>A: One-shot authorization @@ -278,7 +275,7 @@ sequenceDiagram ES->>A: Independent long-lived stream A-->>H: ACK via acknowledgement() A-->>H: listener(handle, ReceivedMessage) - H->>ES: handle.close() on recovery/cancel/shutdown + H->>ES: handle.close() on terminal event/cancel/shutdown ES-->>H: completion() after stream exits ``` @@ -286,8 +283,9 @@ sequenceDiagram workflow-engine uses A2A Java `1.2.0.Final` (REST/JSON-RPC/gRPC), a matching gRPC runtime, `net.openan.a2a-t.sdk:a2a-t-core:1.1.0`, Jackson and SLF4J. Pure engine consumers do not transitively receive A2A-T -client/server, LLM, prompt or resources. samples/hosts explicitly depend on a2a-t-client, and OMC receivers additionally -use a2a-t-server. Registry/orchestration discovery is host-owned; RegistryClient/LoadPsop are optional helpers. +client/server, LLM, prompt or resources. Host agents explicitly depend on a2a-t-client; dispatched-agent services that +validate received content additionally use a2a-t-server. Registry/orchestration discovery is host-owned; +RegistryClient/LoadPsop are optional helpers. Templates and slot schemas come from the pinned SDK jar, not sample resource overrides. ## 10. Design Decisions Summary diff --git a/docs/en/DEVELOPER_GUIDE.md b/docs/en/DEVELOPER_GUIDE.md index 5d5d1be8..65f0ce32 100644 --- a/docs/en/DEVELOPER_GUIDE.md +++ b/docs/en/DEVELOPER_GUIDE.md @@ -15,8 +15,8 @@ Add to your `pom.xml`: ``` -The engine pulls in A2A protocol transports and a2a-t-core only. Hosts generating A2A-T content explicitly add -a2a-t-client; receiving hosts may also add a2a-t-server. +The engine pulls in A2A protocol transports and a2a-t-core only. Host agents generating A2A-T content explicitly add +a2a-t-client; dispatched-agent services that validate received content also add a2a-t-server. ## 2. Core Concepts @@ -63,7 +63,7 @@ ExecutionResult result = ExecutePsop.builder() .psop(workflow) .agentCards(agentCards) .controlPoint(new MyControlPoint()) - .runtimeIntent("Diagnose SPN fault") + .runtimeIntent("Analyze a service anomaly") .lang("zh") .sslVerify(false) .credentialsConfigPath("agent_credentials.json") @@ -119,7 +119,7 @@ WorkflowExecutor executor = new WorkflowExecutor( new MyControlPoint(), client, new EventCallback(), - "Diagnose fault", + "Analyze request", "zh" ); ExecutionResult result = executor.run().join(); @@ -140,13 +140,13 @@ Unchanged waiting state is observed with getTask. `maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout, exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never -diagnosis success, even if the remote acknowledges it with COMPLETED. +task success, even if the dispatched agent acknowledges it with COMPLETED. ### 6.2 Workflow Model Fields | Field | Where | Meaning | |-----------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `steps[].stepType` | `WorkflowStep` | `AllSuccess` (default): every subtask must succeed; `AnySuccess`: any subtask success suffices; `SelfLoop`: the workflow agent handles the step locally via `onSelfTask` (no A2A-T message to the named agent). | +| `steps[].stepType` | `WorkflowStep` | `AllSuccess` (default): every subtask must succeed; `AnySuccess`: any subtask success suffices; `SelfLoop`: the host agent handles the step locally via `onSelfTask` (no A2A-T message to the named agent). | | `steps[].subtasks[]` | `Task` | Each has `agent`, `skill`, `description`. One `onTask` (or `onSelfTask` for SelfLoop) call per subtask. | | `steps[].next[]` | `List` | Branch targets. `step` = next step name; `condition` = rule text. | | `steps[].layer` | `WorkflowStep` | Orchestration-level hint; actual readiness is derived from DAG predecessors. | @@ -175,7 +175,7 @@ seconds, and attaches the auth header to outbound requests. ```json { - "SPN Domain Agent": { + "Dispatched Agent": { "bearerAuth": { "login_url": "https://127.0.0.1:8080/auth/login", "method": "POST", @@ -233,10 +233,10 @@ Set `sslVerify=false` only for dev with self-signed certs. ## 9. A2A-T Environment (.env) -The engine does not read A2A-T .env files or create LLM clients. If host callbacks use A2A-T, initialize -A2ATClient/A2ATServer with a host-owned environment file containing provider/model/key/base URL and A2AT_LANGUAGE. The -sample's a2atEnvPath setting is only a host/demo setting, not an engine builder option. Do not put OMC credential -decryption ownership in LLM configuration: pass the secret explicitly through WorkflowEngineClientConfig.builder () +The engine does not read A2A-T .env files or create LLM clients. If host-agent callbacks use A2A-T, initialize +A2ATClient/A2ATServer with a host-owned environment file containing provider/model/key/base URL and A2AT_LANGUAGE. A +sample-specific a2atEnvPath is not an engine builder option. Keep dispatched-agent credential decryption separate from +LLM configuration: pass the secret explicitly through WorkflowEngineClientConfig.builder() .credentialEncryptionKey (key) when using encrypted built-in credentials, then pass that configured engineClient to ExecutePsop. Custom AuthProvider owns its own token/configuration. Tests use the current SDK SPI with an offline provider, not template overrides or production fallbacks. @@ -273,8 +273,8 @@ public Flux execute(@PathVariable String psopId) { ### Cancellation -ExecutePsop.builder ().execute () returns a CompletableFuture. Cancellation prevents late callback results from being -sent and requests conversation cleanup. Hosts must separately cancel their own LLM/business work. A remote task already +ExecutePsop.builder().execute() returns a CompletableFuture. Cancellation prevents late callback results from being + sent and requests conversation cleanup. Host agents must separately cancel their own LLM/business work. A dispatched task already submitted may require an explicit cancelTask operation; local cancellation is not protocol Abort. ## 11. Checklist @@ -291,13 +291,12 @@ submitted may require an explicit cancelTask operation; local cancellation is no ### Pretty protocol log display Protocol logs default to pretty display: one header value per line and indented JSON bodies. SSE keeps event controls -(id/event/comments); JSON appears between `=== SSE data (JSON display; not wire text) ===` +(id/event/comments); JSON appears between `=== SSE data(JSON display; not wire text) ===` and `=== End SSE data ===`, without repeating `data:` on every JSON line. Event boundaries remain separate. Set the environment variable or JVM property `WORKFLOW_ENGINE_PROTOCOL_PRETTY=false` to retain redacted raw body text. Formatting changes presentation only (JSON whitespace and SSE display labels), never transmitted bytes, metadata, number -tokens or extension headers. The raw observer content remains unchanged. SSE pretty output is a display, not a -replayable packet capture. Escaped newlines inside JSON strings stay escaped; invalid/incomplete or non-JSON content -stays raw. Redaction and capacity limits still apply. The demo logs `NEGOTIATION_DEMO enabled=..., city=...` at startup. -Locally City1 negotiates by default while City2 diagnoses directly; -`-Da2at.samples.negotiation=false` disables the missing-input scenario. Look for `A2A-Extensions: .../Negotiation-T/v1`, -the matching metadata key and `negotiationContext`; there is no dedicated Negotiation-T HTTP header. + tokens or extension headers. The raw observer content remains unchanged. SSE pretty output is a display, not a + replayable packet capture. Escaped newlines inside JSON strings stay escaped; invalid/incomplete or non-JSON content + stays raw. Redaction and capacity limits still apply. To verify negotiation traffic, look for + `A2A-Extensions: .../Negotiation-T/v1`, the matching metadata key and `negotiationContext`; there is no dedicated + Negotiation-T HTTP header. diff --git a/docs/en/INTEGRATION_GUIDE.md b/docs/en/INTEGRATION_GUIDE.md index 2e4c1f46..f73bc23d 100644 --- a/docs/en/INTEGRATION_GUIDE.md +++ b/docs/en/INTEGRATION_GUIDE.md @@ -40,15 +40,15 @@ Nonempty business conditions require an onRoute policy; this minimal workflow us ```java Workflow workflow = Workflow.builder() - .name("Fault Diagnosis") + .name("Service Analysis") .steps(List.of( WorkflowStep.builder() - .name("diagnose") + .name("analyze") .subtasks(List.of( Task.builder() - .agent("SPN Domain Agent") - .skill("diagnosis") - .description("Diagnose fault") + .agent("Dispatched Agent") + .skill("analysis") + .description("Analyze the request") .build())) .next(List.of( JumpCondition.builder() @@ -59,10 +59,10 @@ Workflow workflow = Workflow.builder() .build(), WorkflowStep.builder() .name("merge") - .stepType(StepType.SELF_LOOP) // self-loop: workbench merges locally, no A2A-T to self + .stepType(StepType.SELF_LOOP) // host-agent local step; no A2A-T send .subtasks(List.of( Task.builder() - .agent("Transport Workbench Agent") + .agent("Host Agent") .skill("aggregate") .description("Merge results") .build())) @@ -137,7 +137,7 @@ CompletableFuture execution = ExecutePsop.builder() .psop(workflow) .agentCards(cards) .controlPoint(callbacks) - .runtimeIntent("SPN cross-city fault diagnosis") + .runtimeIntent("Analyze the service request") .lang("zh") .credentialsConfigPath("credentials.json") .sslVerify(true) @@ -159,10 +159,10 @@ Required: `psop`, `controlPoint`. All other config items have defaults. ### 5.1 .env File -The engine does not read A2A-T .env files or create LLM clients. If host callbacks use A2A-T, initialize -A2ATClient/A2ATServer with a host-owned environment file containing provider/model/key/base URL and A2AT_LANGUAGE. The -sample's a2atEnvPath setting is only a host/demo setting, not an engine builder option. Do not put OMC credential -decryption ownership in LLM configuration: pass the secret explicitly through WorkflowEngineClientConfig.builder () +The engine does not read A2A-T .env files or create LLM clients. If host-agent callbacks use A2A-T, initialize +A2ATClient/A2ATServer with a host-owned environment file containing provider/model/key/base URL and A2AT_LANGUAGE. A +sample-specific a2atEnvPath is not an engine builder option. Keep dispatched-agent credential decryption separate from +LLM configuration: pass the secret explicitly through WorkflowEngineClientConfig.builder() .credentialEncryptionKey (key) when using encrypted built-in credentials, then pass that configured engineClient to ExecutePsop. Custom AuthProvider owns its own token/configuration. Tests use the current SDK SPI with an offline provider, not template overrides or production fallbacks. @@ -173,15 +173,13 @@ For agents requiring authentication, provide a JSON credentials file: ```json { - "SPN Domain Agent": { + "Dispatched Agent": { "bearerAuth": { - "login_url": "https://127.0.0.1:26335/rest/plat/smapp/v1/oauth/token", - "method": "PUT", + "login_url": "https://agent.example.com/oauth/token", + "method": "POST", "request_fields": { - "grantType": "password", - "userName": "admin", - "value": "enc::", - "ipaddr": "*" + "username": "service-account", + "password": "enc::" }, "token_field": "accessSession", "token_ttl": 3600 @@ -322,7 +320,7 @@ WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder() - `AuthProvider` can be the sole authentication source even when `securityRequirements` is non-empty - If both credentials and `AuthProvider` are configured, their headers are generated independently and merged; different values for the same header fail fast -- On auth failure (e.g. token retrieval throws), the exception propagates to `send()` and the request is blocked +- On auth failure(e.g. token retrieval throws), the exception propagates to `send()` and the request is blocked ## 6. AgentCard Definition @@ -330,7 +328,7 @@ AgentCards declare extensions via `capabilities.extensions`: ```json { - "name": "SPN Domain Agent", + "name": "Dispatched Agent", "capabilities": { "streaming": true, "extensions": [ @@ -346,12 +344,12 @@ AgentCards declare extensions via `capabilities.extensions`: }, { "uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1", - "description": "Authorization whitelist", + "description": "Authorization operation", "required": false }, { "uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1", - "description": "Result notification subscription", + "description": "Notification subscription", "required": false } ] @@ -399,7 +397,7 @@ Unchanged waiting state is observed with getTask. `maxNegotiationExchanges` (default 3) bounds local interactions, independently of the SDK context's maxRounds. Timeout, exhausted budget or a missing handler fails locally; no implicit Accept or synthesized Abort. Accept/Reject ACKs in SUBMITTED/WORKING remain pending and are observed without resending the command. A business-sent Abort is never -diagnosis success, even if the remote acknowledges it with COMPLETED. +task success, even if the dispatched agent acknowledges it with COMPLETED. ```java CompletableFuture sendAuthorization(String agentName, MessageContent content); @@ -407,10 +405,10 @@ NotificationSubscription openNotification(String agentName, MessageContent conte BiConsumer listener); ``` -The host generates final Authorization-T/Notification-T content and calls these methods on separate -transport/runtime/context instances. The listener receives handle and complete ReceivedMessage and closes on the -business recovery event. acknowledgement () and completion () separately represent ACK and actual stream exit, never -workflow prerequisites. +The host agent generates final Authorization-T/Notification-T content and calls these methods on separate +transport/runtime/context instances. The listener receives the handle and complete ReceivedMessage, and closes on the +host-defined terminal event. acknowledgement() and completion() separately represent ACK and actual stream exit; +neither is a workflow prerequisite. ## 8. HTTPS Configuration @@ -462,25 +460,12 @@ chunks. Observers cannot fail delivery. File references are recorded as referenc requestId correlates each call; workflow calls additionally carry executionId/logicalTaskId/attempt, agent/contextId/channel and remoteTaskId when known. These are local log fields, not wire metadata. -### Inspect negotiation in the local Demo +### Verify protocol and negotiation logs -Running the local SpringSpnDemo without VM options now negotiates missing City1 input; City2 diagnoses directly. This is -a local sample scenario, not an engine default. To use complete inputs in both cities, add this IDEA **VM option**: - -```text --Da2at.samples.negotiation=false -``` - -By default only City1 loses its Task-T task object. Add `-Da2at.samples.negotiation.city=city2` or `both` -to exercise City2 or both cities. The host retains city-scoped authoritative input and the complaint context is -preserved. Expect DEMO_NEGOTIATION → INPUT_REQUIRED/PROPOSE → onNegotiation → ACCEPT → both diagnoses → one aggregate. -External-OMC mode defaults to no injection and rejects an explicit true switch. The Demo passes its setting into the -current Spring application context without modifying JVM-wide properties. Protocol observations are in the console and -`logs/spn-demo.log` relative to the run directory. - -Run `SpringSpnDemoE2ETest` for direct transport. It tests the no-VM-option -single-city default, explicit disable/enable and both-city negotiation with current SDK resources and an offline LLM -provider. This is local protocol E2E evidence, not real model/platform/OMC validation. +Use a controlled test in which one dispatched agent returns `INPUT_REQUIRED` with a valid Negotiation-T Propose. The +expected sequence is request → Propose → `onNegotiation` → final Send/Stop → terminal task result. The `PROTOCOL` logger +must show the observed request/response boundaries and correlation fields without exposing credentials. A declared +Negotiation-T capability alone does not force negotiation. ## 10. Event Callback @@ -492,7 +477,7 @@ EventCallback callback = new EventCallback() { public void onEvent(String eventType, Map data) { switch (eventType) { case EventType.STEP_START -> System.out.println("Step started: " + data.get("step")); - case EventType.AGENT_STATUS_UPDATE -> System.out.println( + case EventType.TASK_STATUS_CHANGED -> System.out.println( data.get("agent") + " state: " + data.get("state")); case EventType.NEGOTIATION_REQUEST -> System.out.println( "Negotiation from " + data.get("agent")); @@ -506,59 +491,51 @@ ExecutePsop.builder() // ... ``` -Common event types: `STEP_START`, `STEP_COMPLETE`, `AGENT_REQUEST`, -`AGENT_RESPONSE`, `NEGOTIATION_REQUEST`, `NEGOTIATION_RESOLVED`, -`COMPLETE`, `ERROR`. +Common event types: `START`, `STEP_START`, `TASK_REQUEST`, +`TASK_RESPONSE`, `TASK_STATUS_CHANGED`, `STEP_COMPLETE`, +`NEGOTIATION_REQUEST`, `NEGOTIATION_RESOLVED`, `NEGOTIATION_FAILED`, +`ROUTE_DECISION`, `WORKFLOW_COMPLETE`, `COMPLETE`, `ERROR`, `CLOSE`. ## 11. Load Workflows from Orchestration Center ```java // Search by intent List results = LoadPsop.search( - "https://127.0.0.1:5001", "SPN cross-city fault diagnosis", 5, null, false); + "https://orchestration.example.com", "analyze service request", 5, null, true); // Load full workflow by ID Workflow workflow = LoadPsop.load( - "https://127.0.0.1:5001", results.get(0).getWorkflowId(), null, false); + "https://orchestration.example.com", results.get(0).getWorkflowId(), null, true); ``` ## 12. Custom Extensions -Construct final MessageContent (parts, metadata, extensions), with host-owned content generation/validation. No engine +Construct final MessageContent(parts, metadata, extensions), with host-owned content generation/validation. No engine handler or SDK instance registration. A2atMessages.from copies A2A-T metadata; other extensions can directly supply metadata and activation URIs. AgentCard declarations never cause implicit generation. -## External OMC startup checks +## 13. External endpoints and startup ownership -### Orchestration-center HTTPS development +### 13.1 Orchestration-center HTTPS development -The demo passes `A2A_SSL_VERIFY` (Spring property `a2a.ssl-verify`) to LoadPsop.search/load. -For controlled development, set `A2A_SSL_VERIFY=false` or `--a2a.ssl-verify=false`. -No local orchestration CA file is required: both calls skip chain and hostname checks, including -self-signed certificates, missing SANs and mismatched SANs, and emit a `[Registry] INSECURE_TLS` warning. +`LoadPsop.search/load` receives an explicit `sslVerify` argument. Keep it `true` in production. In a controlled +development environment, `false` disables certificate-chain and hostname checks for that connection only and emits a +`[Registry] INSECURE_TLS` warning. It does not modify JVM-wide TLS defaults. The HTTPS server must still present a certificate, and client-certificate requirements (mTLS) still apply. This mode cannot authenticate the server and is vulnerable to interception; do not use it in production. Keep verification enabled in production, configure matching server SANs and trust its CA in the running JVM. -The southbound `caCertsPath` setting is not forwarded to LoadPsop. +The dispatched-agent `caCertsPath` setting is not forwarded to LoadPsop. LoadPsop never changes JVM-wide SSLContext, SocketFactory or HostnameVerifier defaults. -Southbound HTTP/JSON-RPC retains hostname checks when chain verification is disabled; vendor forwarding TLS -remains managed by the vendor SDK/platform. - -### Downstream OMC startup +Dispatched-agent HTTP/JSON-RPC retains hostname checks when chain verification is disabled. -Set `A2A_AGENT_CARD_LOCATIONS` in the environment or local .env to comma-separated external -AgentCard file locations. For SpringSpnDemo, set `A2A_EMBEDDED_OMC_ENABLED=false` or pass -`--a2a.embedded-omc-enabled=false` when using externally managed OMCs. -Use SpringWorkbenchApplication for a persistent host; the demo exits after its task. +### 13.2 Dispatched-agent endpoints -Embedded startup validates both effective city cards before starting either OMC, then uses those -cards to bind. Missing cards, invalid URLs and non-local addresses fail early. Loopback and local -network-interface addresses are accepted; non-loopback does not automatically mean remote. -This does not probe OMCs or detect existing services. Disable embedded startup even when an externally -managed OMC runs on this same machine. Keep real cards and credentials outside tracked sample resources. +The engine consumes AgentCards and never starts dispatched-agent servers. The host agent owns AgentCard discovery, +endpoint reachability checks, and any development fixtures. When using externally managed dispatched agents, do not +start local fixtures on the same endpoints. Keep production AgentCards and credentials outside tracked sample resources. -## Remote problem responses +## 14. Remote problem responses A successful HTTP envelope does not guarantee a successful task: SSE data can contain a top-level problem object such as `{"status":429,"detail":"Active task limit reached","type":""}`. @@ -570,7 +547,7 @@ first as an HTTP/authentication error by the SDK or adapter, without exposing it such failures retain the generic failure path rather than fabricating errorDetails. The error fails the call without fabricating a successful task, invoking onNegotiation, or automatically -resubmitting it. For 400, inspect business inputs; for 429, inspect OMC capacity and existing tasks or +resubmitting it. For 400, inspect business inputs; for 429, inspect dispatched-agent capacity and active tasks or subscriptions before deciding whether to retry. Negotiation still requires a valid Negotiation-T Propose. Use `RemoteProblemException.findIn(error)` to inspect the cause chain (null means no problem found); workflow execution follows its existing failure path. @@ -581,18 +558,16 @@ unrecognized SDK exceptions still expose only the exception type. Independent authorization/subscription failures do not determine the workflow result. Pretty logging does not modify the received problem payload. -In the two-city sample, one failed diagnosis does not interrupt the other already dispatched call: -its result or timeout is collected before the workflow returns `success=false`. -The merge_analysis onSelfTask/LLM is not invoked; successful peer results remain in history, -not in a fabricated overall diagnosis. There is no automatic retry, queuing or partial-success merge. -Generic AnySuccess nodes retain their existing first-success semantics. +For an `ALL_SUCCESS` step, one failed task does not retroactively cancel a peer call that was already dispatched: the +peer result or timeout is collected before the workflow returns `success=false`. Downstream steps are not invoked; +successful peer results remain in history rather than being turned into a fabricated aggregate. There is no automatic +retry, queuing, or partial-success merge. `ANY_SUCCESS` nodes retain their declared first-success semantics. Observe failures through TASK_RESPONSE or inspect result.history in `onFinish(result, events)`; onTask prepares outbound content and is not an error-retry callback. Executor node events include executionId; TASK_RESPONSE and history also include the logical taskId. -The sample's northbound A2A task returns FAILED with per-agent errorCode, error and errorDetails -in its status message, without a success-summary artifact. Demo shutdown closes subscriptions; -a persistent host owns them independently of workflow results. +The host agent decides how to expose a failed execution to its caller. It must not convert a failed execution into a +success artifact. A persistent host owns notification subscriptions independently of workflow results. Logging responsibilities: PROTOCOL captures observed traffic, correlated by requestId rather than adjacent log entries; REMOTE_PROBLEM reports transport rejection; TASK_FAILED identifies the execution, @@ -601,7 +576,7 @@ Sample TASK_RESPONSE logs bridge contextId and executionId. Recognized remote pr while unexpected exceptions retain ERROR stack traces. Known credentials and Bearer/Basic values are redacted. Logging configuration, pretty display and observer callbacks do not determine task outcomes. -## 13. Interface Reference +## 15. Interface Reference | Interface/Class | Purpose | |--------------------------------------------------------|-------------------------------------------------------------------------| diff --git a/docs/zh/A2AT-SDK-DEPENDENCY.md b/docs/zh/A2AT-SDK-DEPENDENCY.md deleted file mode 100644 index 35f20b14..00000000 --- a/docs/zh/A2AT-SDK-DEPENDENCY.md +++ /dev/null @@ -1,130 +0,0 @@ -# A2A-T SDK 正式版本依赖说明 - -执行引擎首发版本只支持下列 A2A-T SDK 正式版本基线,不提供旧 SDK 兼容层: - -- 仓库:`project-openan/a2a-t-sdk-java` -- 发布标签:`v1.1.0`,commit:`e42c83acce3e5ac2c245d36546ced0fa017b2b58` -- Maven Central 版本:`1.1.0`,groupId `net.openan.a2a-t.sdk` -- 引擎使用 `a2a-t-core`;samples/宿主显式使用 `a2a-t-client`,OMC 接收端使用 `a2a-t-server` - 。引擎不传递引入内容生成、LLM、prompt/resources。 - -## 已知上游生命周期缺口 - -该正式版本中的 `A2ATClient`、`A2ATServer` 和 `LLMClient` 尚未实现 -`AutoCloseable`,因此调用过 OpenAI 兼容 LLM 后,底层 OpenAI Java 客户端的 -`DefaultSleeper` 定时线程无法由工作流引擎通过公共 API 关闭。`SpringSpnDemo` 的 离线协议测试不受此限制;真实模型调用后的生命周期仍需额外观察, -一次性关闭内嵌 Tomcat 时可能提示上游线程生命周期告警。 - -上游 SDK 应在新提交中补充幂等 `close()`,由 `A2ATClient/A2ATServer` 级联关闭其拥有的 LLM provider;执行引擎届时应升级到该新提交对应的新 -Maven 版本。禁止在不改变版本号的 情况下覆盖 `1.1.0`,否则 IDEA 与 CI 将得到不可复现的同坐标异内容制品。 - -该 SDK 已发布到 [Maven Central](https://repo.maven.apache.org/maven2/net/openan/a2a-t/sdk/a2a-t-client/1.1.0/)。 本机和 -CI 直接解析正式制品,不再检出 SDK 源码或执行 SDK install,也不降级到其他版本。 - -## 当前模板发现的资源隔离 - -当前 SDK 的 PromptTemplateCatalog 会关闭 JarURLConnection 返回的 JarFile。 默认缓存可能让并行门面共用该句柄,导致 zip file -closed。 samples 宿主 A2ATInitialization 在初始化线程临时包装资源 URL,仅该连接 useCaches=false, 完成后恢复类加载器;不更改全局 -URL 缓存或 SDK jar。 每次 SDK 升级需重新验证该隔离是否仍必要。 - -SDK 当前内容接口接受字符串模板 URI,宿主通过 StandardTemplates.*.uri () 指定。 业务调用生成/校验 API,再经 -A2atMessages.from 转为最终 MessageContent。 引擎只检查协商上下文关联,不解释业务内容。 -完整契约见 [业务回调集成契约](BUSINESS_CALLBACKS.md)。 - -## Maven / IDEA 接入 - -根 POM 的 `a2a.t.sdk.version` 是唯一依赖版本来源,当前为 `1.1.0`。 在执行引擎目录直接执行: - -```powershell -mvn -B -U clean verify -``` - -IDEA 使用该工程的 Maven 配置并执行 Reload All Maven Projects 即可;无需手工安装 A2A-T jar。 -如果企业或公共镜像尚未同步正式版本,应等待同步或按组织要求调整 Maven 镜像, 不要同坐标 install 本地构建物替代正式发布制品。本次通过本机镜像下载并核对 -SDK jar 的 SHA-1 与 Central 发布校验值一致。 - -宿主若调用 A2A-T 内容生成,需显式声明: - -```xml - - net.openan.a2a-t.sdk - a2a-t-client - 1.1.0 - -``` - -需要接收端校验时同样显式添加 `a2a-t-server:1.1.0`;samples 已声明二者。 - -## 升级 A2A-T SDK - -升级时更新根 `pom.xml` 的 `a2a.t.sdk.version`、本文发布标签和相关接入文档, 核对正式制品与源码/资源差异,并运行完整回归。CI -直接使用根 POM,不单独维护版本或编译 SDK。 - -禁止只覆盖本地同版本 jar。新版本至少要验证 Task-T 生成/校验、无状态 Negotiation-T metadata、Authorization-T、Notification-T -长连接,以及现行传输路径: 验证直连 OMC 的 SpringSpnDemo 端到端流程。 - -“只支持最新 A2A-T SDK”仅表示不兼容旧 SDK 版本和旧协议格式。 - -## A2A Java SDK 基线 - -执行引擎使用 Maven Central 已正式发布的 `org.a2aproject.sdk` `1.2.0.Final`,不采用尚未 发布制品的源码标签或 SNAPSHOT。该版本将 -`DefaultRequestHandler` 构造迁移到 Builder、 将任务校验收进请求处理器,并把 `Utils` 移到 `org.a2aproject.sdk.spec.util` -。同时必须让 -`protobuf-java-util` 与其传递引入的 `protobuf-java:4.35.0` 对齐,避免 REST JSON 转换时 出现 `NoSuchMethodError`。Spring -starter 通过仅暴露 `RequestHandler` 接口的委托隔离 -`DefaultRequestHandler` 中 CDI 专用注入点;SSE 端点使用 `SseEmitter` 输出规范事件,不能把 手工拼接的 `id:/data:` -文本再次作为普通响应体发送。升级 A2A Java SDK 后必须重新运行直连 E2E。 - -## 单一资源基线 - -模板、slot schema、slot extraction 和 content validation 提示词全部来自锁定的 SDK jar。 samples 不再用同名 classpath 资源覆盖 -SDK 内容,也不保留旧 renderer/validator 的兼容提示词。 测试会核对实际加载资源来自 `a2a-t-resources`,避免升级后仍静默运行旧规则。 - -Authorization-T 只接受当前 SDK 定义的规范数据形态:每条策略以从 1 连续递增的编号 `1.` 开头, 字段为“字段名是值”,字段间使用全角逗号 -`,`,多条策略用换行分隔。 例如: -`1. 业务场景是业务投诉诊断,处置类型是业务抢通,操作名称是隧道调优,有效期是2026-06-01~2030-06-18`。 -日期范围与“永久生效”均支持;裸值列表、分号分隔和旧 `/` 格式不接受。 此前本节关于全角分号的描述不正确,已按 1.1.0 正式 jar 的 -slot.json 修正。 - -## 示例业务接口调用复核(2026-08-31) - -此前发现的正文来源、填充结果丢弃、City2 缺参路径、Reject/Abort 分发及离线宽松校验问题, 现已在 samples -业务层整改;通用引擎未重新引入内容生成或语义判断职责。 - -| 路径 | 发送端生成 | 接收端校验/实际使用 | -|----------------------------|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| -| WAIMO → 集成方 | generateTaskPromptFromDataWithSchema | WorkbenchTaskInputParser 使用 validateTaskPromptAndDataFilling 的 filled.data | -| 集成方 → 两地市任务 | WorkbenchControlPoint.onTask:generateTaskPromptFromDataWithSchema | 两 OMC 校验 Task-T,使用 filled.data 构造 SpnTaskInput | -| 两地市 OMC → 集成方协商 | generateNegotiationProposePromptFromData,按实际缺失字段生成 | validateProposePromptAndDataFilling,按 items 从当前城市输入提取答案 | -| 集成方 → OMC Accept | generateNegotiationAcceptPromptFromData | 从正式 metadata 取正文,validateAcceptPromptAndDataFilling 后合并请求字段并复核业务必填信息 | -| 集成方 → OMC Reject/Abort | 对应 generateNegotiationRejectPromptFromData/generateNegotiationAbortPromptFromData | 对应 validateRejectPromptAndDataFilling/validateAbortPromptAndDataFilling,结束任务且不执行诊断 | -| 独立授权 | generateAuthPromptFromDataWithSchema | validateAuthPromptAndDataFilling 后构造并应用 AuthorizationPolicy | -| 独立订阅 | generateNotificationPromptFromDataWithSchema | validateNotificationPromptAndDataFilling 后构造 NotificationPolicy | - -自然语言 FromText 和结构化 FromDataWithSchema 是替代入口,不是同一消息必须串行全部执行的步骤。 本 Demo 主路径选择结构化数据;Task-T -自然语言另有测试,不宣称覆盖 SDK 全部公开接口。 端口资源归属、投诉分类和 OSS 流水号是 SPN 业务层规则;正式集成应替换为宿主/OMC -的实际业务判断。 - -协商校验不再读取 parts 摘要;会核对 A2A taskId/contextId 和协商 id/round/maxRounds。 SDK 拒绝原始任务时,不使用未经验证的局部数据,重新协商完整必要字段。 -接收 Accept 后只合并本轮请求字段;错误正文、缺参和跨协商回复均不能进入诊断。 - -1.1.0 的授权/通知生成入口面向授权操作及订阅请求,并不是任意业务结果生成接口。 诊断结果、授权回执和抢通事件由 OMC -业务产生。RecoveryNotification 从正式 Notification-T artifact metadata 检查 SPN 最终抢通结果,避免仅凭 artifact -名称/摘要就关闭订阅。 这不是对任意外部通知的通用语义验证,也不在工作流引擎内实施。 - -完整角色映射、字段规则、FromText/FromData 选择、宿主拒绝策略和可重复验证范围见 -[业务回调指南第 9 节](BUSINESS_CALLBACKS.md#9-示例业务侧的-a2a-t-110-调用参考)。 严格离线测试只处理明确样例输入,不从模板示例臆造参数;不能代替真实 -LLM 和现网 OMC 联调。 - -## 发布验证入口 - -执行 `mvn -B clean verify`,必须包含 core、starter 与 samples;测试统计以当前提交的 Surefire XML 为准。 -发布前必须通过直连 SpringSpnDemo。 -默认本地演示为 City1 缺参协商、City2 完整输入;外部 OMC 不注入演示缺参。 - -SSE pretty 展示保留事件控制字段,JSON 数据区明确标注为展示格式,不逐行重复 data:。 -设置 `WORKFLOW_ENGINE_PROTOCOL_PRETTY=false` 可查看脱敏后的原文,容量限制仍生效。 -Header 凭据始终脱敏,编排中心 query token 仅显示匿名标记,不存在敏感头放开开关。 - -离线 LLM provider 和本地 OMC 结果不能代替真实模型/OMC 验收。 -真实环境验收须单独记录配置来源、版本、操作授权和脱敏报文,私有联调记录不得提交到公共仓库。 diff --git a/docs/zh/API_REFERENCE.md b/docs/zh/API_REFERENCE.md index b50ff8d3..c95569cb 100644 --- a/docs/zh/API_REFERENCE.md +++ b/docs/zh/API_REFERENCE.md @@ -10,6 +10,7 @@ | `dev.openan.workflow.engine.model` | 数据模型(Workflow、Task、Result 等) | | `dev.openan.workflow.engine.registry` | PSOP 加载和 AgentCard 注册 | | `dev.openan.workflow.engine.runner` | 工作流执行入口 | +| `dev.openan.workflow.engine.spring` | Spring Boot 服务端自动配置(starter) | --- @@ -24,7 +25,7 @@ | 方法 | 类型 | 默认值 | 说明 | |------------------------------------------|------|-------------|---------------------------------| | `psop(Workflow)` | 必填 | - | PSOP 工作流定义 | -| `agentCards(List)` | 必填 | `List.of()` | 工作流中所有智能体的 AgentCard | +| `agentCards(List)` | 可选 | `List.of()` | 被调度智能体的 AgentCard;存在远程步骤且未传入已配置 `engineClient` 时必须提供 | | `engineClient(WorkflowEngineClient)` | 可选 | null | 预配置客户端(null=自动创建) | | `controlPoint(ControlPoint)` | 必填 | - | 用户决策实现 | | `runtimeIntent(String)` | 可选 | `""` | 自然语言意图,用于上下文组装 | @@ -42,7 +43,7 @@ ExecutionResult result = ExecutePsop.builder() .psop(workflow) .agentCards(cards) .controlPoint(cp) - .runtimeIntent("诊断故障") + .runtimeIntent("分析请求") .sslVerify(false) .execute() .get(10, TimeUnit.MINUTES); @@ -80,7 +81,7 @@ nextRound 或返回新 Propose。 Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 `maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 +COMPLETED 确认,也不能判为任务成功。 ### ExtensionSender @@ -91,8 +92,8 @@ NotificationSubscription openNotification(String agentName, MessageContent conte ``` 授权和订阅接收宿主生成的最终内容。三类操作使用独立 transport/runtime/context,成功与否不阻断工作流。openNotification 先注册 -handle 再开始 I/O,监听器直接收到 handle 和完整 ReceivedMessage。acknowledgement () 是实际 ACK;超时失败。close () -请求关闭,completion () 在流真正退出后完成。 +handle 再开始 I/O,监听器直接收到 handle 和完整 ReceivedMessage。acknowledgement() 是实际 ACK;超时失败。close() +请求关闭,completion() 在流真正退出后完成。 ### WorkflowEngineClientConfig @@ -147,8 +148,8 @@ Header 生成不同值,引擎会抛出 `SecurityException`,不会静默覆 ### A2atMessages -通过 MessageContent (parts, metadata, extensions) 提交自定义扩展内容,不注册引擎内容处理器。A2atMessages.from -(MetadataContent, List>) 保留 SDK metadata 原位置并激活对应 URI;contextOf (ReceivedMessage) 或 contextOf (Map< +通过 MessageContent(parts, metadata, extensions) 提交自定义扩展内容,不注册引擎内容处理器。A2atMessages.from +(MetadataContent, List>) 保留 SDK metadata 原位置并激活对应 URI;contextOf(ReceivedMessage) 或 contextOf(Map< String,Object>) 读取并检查规范协商上下文。只有 a2a-t-core 依赖,没有内容生成或语义校验。 ### A2AJavaClientRuntime @@ -221,6 +222,25 @@ AgentCard card = mapper.readValue( Map normalized = AgentCardNormalizer.normalize(rawMap); ``` +### 其他公开 client 类型 + +以下公开类型面向高级集成。除非需要自定义 runtime、观测适配或显式生命周期管理,优先使用上述高层接口。 + +| 类型 | 用途 | +|-------------------------------|------| +| `A2ATExtension` | 规范扩展名称与 URI | +| `A2ATransport` | 底层传输、认证、响应组装与订阅生命周期 | +| `DefaultWorkflowEngineClient` | `WorkflowEngineClient` 默认实现 | +| `DefaultExtensionSender` | 基于 `A2ATransport` 的 `ExtensionSender` 默认实现 | +| `DefaultA2AJavaClientRuntime` | HTTP/JSON-RPC/gRPC 的默认 A2A Java SDK runtime | +| `CredentialCrypto` | AES-GCM 凭据加密工具与命令行入口 | +| `EnvFileLoader` | 显式加载宿主自有 `.env` 配置 | +| `SslContextFactory` | 传输与发现辅助 API 的 TLS 上下文构造 | +| `ProtocolResponses` | A2A 事件与结果组装辅助方法 | +| `ClientEventMapper` | 面向回调和诊断的稳定事件投影 | +| `WireLog` | 关联上下文与协议观测门面 | +| `RemoteProblemException` | 结构化远程问题响应(`status`、`title`、`detail`、`type`、`timestamp`) | + --- ## dev.openan.workflow.engine.control @@ -238,6 +258,9 @@ interface ControlPoint { onTask 返回最终 parts/metadata/extensions,引擎封装发送,不再生成或改写内容。 onSelfTask 返回本地 TaskResult;onRoute 选择允许的候选;onNegotiation 返回 Send 或 Stop。 未实现的回调明确失败,不回显成功、不选首分支、不自动同意。 + +`DefaultControlPoint` 保留上述快速失败默认值,并可把 `onNegotiation` 委托给注入的 +`NegotiationStrategy`。仅需定制协商策略时实现 `NegotiationStrategy.resolve(NegotiationRequest)`;同时需要定制任务、宿主本地任务或路由时,实现或构建完整 `ControlPoint`。 字段与完整示例见 [业务回调集成契约](BUSINESS_CALLBACKS.md)。 ### EventCallback @@ -358,11 +381,6 @@ WorkflowExecutor executor = new WorkflowExecutor( ExecutionResult result = executor.run().join(); ``` -### ContextBuilder - -包级私有辅助类,按 `contextFrom` 规则选择上游步骤结果并构造 `WorkflowInput`(省略 = 直接前驱,`[]` = 不聚合上游,`"*"` = -所有祖先,指定名称 = 选择性聚合)。它不生成提示词,也不属于公共 API 接口。 - --- ## dev.openan.workflow.engine.model @@ -395,7 +413,7 @@ ExecutionResult result = executor.run().join(); |---------------|-----------------------------------------------------------------------------------------| | `ALL_SUCCESS` | 所有子任务必须成功 | | `ANY_SUCCESS` | 任一子任务成功即可 | -| `SELF_LOOP` | 工作流执行智能体通过 `onSelfTask` 本地处理,不发 A2A-T 消息。成功语义同 `ALL_SUCCESS`。 | +| `SELF_LOOP` | 宿主智能体通过 `onSelfTask` 本地处理,不发 A2A-T 消息。成功语义同 `ALL_SUCCESS`。 | ### TaskStatus @@ -425,7 +443,7 @@ ExecutionResult result = executor.run().join(); ### TaskRequest / BusinessInput -TaskRequest 使用 getXxx () 访问器: +TaskRequest 使用 getXxx() 访问器: | 字段 | 含义 | |------------------------------|------------------------------------------------------------------| @@ -435,8 +453,9 @@ TaskRequest 使用 getXxx () 访问器: | input | BusinessInput:文本或任意 JSON 可序列化数据,二选一,不含 schema | | workflowInput | WorkflowInput(runtimeIntent, upstreamResults),与当前输入分离 | -BusinessInput.text (value) / BusinessInput.data (value) 创建输入快照。 -WorkflowInput、UpstreamStepResult、ReceivedMessage、NegotiationRequest 等 record 使用 field () 访问器。 +BusinessInput.text(value) / BusinessInput.data(value) 创建输入快照。 +WorkflowInput、UpstreamStepResult、ReceivedMessage、NegotiationRequest 等 record 使用 field() 访问器。 +`BusinessValues.snapshot`、`map` 和 `list` 为宿主自有数据提供相同的防御性 JSON 快照;嵌套数组仍是独立值,返回集合不可修改。 | contextFrom | 上游选择 | |---------------|----------------------------------| @@ -450,7 +469,7 @@ contextFrom 只选择证据,不建立执行依赖;依赖由 next 定义。 窗口结构:stepName → taskResults[] → outputs[] / receivedMessages[]。 TaskExecutionResult 还保留 agentName、skill、逻辑 taskId、taskDescription、status、 error、errorCode、errorDetails。多子任务不混合,嵌套数组仍作为一个输出项。 输出不要求来自 -LLM,也不要求符合投诉模板。 +LLM,也不强制符合特定领域模板。 ### MessageContent / ReceivedMessage @@ -459,7 +478,7 @@ record MessageContent(List> parts, Map metadata, Set taskMetadata, List artifacts) {} ``` -通过 MessageContent.text (text)、MessageContent.parts (parts) 或构造器创建快照。 TextPart、DataPart、FilePart 保留顺序及各自 +通过 MessageContent.text(text)、MessageContent.parts(parts) 或构造器创建快照。 TextPart、DataPart、FilePart 保留顺序及各自 metadata,文件引用不会自动下载。 MessageContent 不提供 role、目标、messageId、taskId、contextId 或认证头。 业务 metadata 中即使存在 contextId 字段,也不能覆盖真实 A2A 信封。 @@ -468,7 +487,7 @@ metadata 的业务结果仍可从完整视图读取。 便利 outputs 按 artifa artifact 时,可提取成功终态或独立 Message 的正文。 FilePart 仅在完整视图提供。不解析文本、不拼接相邻文本、不拍平嵌套业务数组。 失败状态消息只保留作证据,不进入 outputs;已返回的有效部分 artifact 仍保留。 SSE append/replace 按 artifact 组装,最终快照不重复累加。 -本地 onSelfTask 返回 TaskResult.success (List),允许空列表; TaskResult.failure (code, message) 将错误与输出分开,builder +本地 onSelfTask 返回 TaskResult.success(List),允许空列表; TaskResult.failure(code, message) 将错误与输出分开,builder 可保留有效部分输出。 远端 TaskResult 的 receivedMessages 是完整证据,便利输出由它派生。 远端 Task 只有 COMPLETED 才成功,独立 A2A Message 也可完成交互。 进度/协商提示不会因为含文本就变成工作流成功。 @@ -478,7 +497,7 @@ A2A Message 也可完成交互。 进度/协商提示不会因为含文本就 ### SendMessageResult -getReceivedMessages () 是保留层级的响应来源,getOutputs () 为便利投影;getTask ()/getTaskState () +getReceivedMessages() 是保留层级的响应来源,getOutputs() 为便利投影;getTask()/getTaskState() 保留实际远端状态,failureCode/failureMessage 为独立本地交互失败。text 和扁平 metadata 仅用于传输诊断,不用它们代替完整业务响应。 ### ExecutionResult @@ -523,13 +542,8 @@ getReceivedMessages () 是保留层级的响应来源,getOutputs () 为便利 | Authorization-T | `https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1` | | Notification-T | `https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1` | -> **协商相关 metadata key 约定:** -> -> - `negotiationContext`(SDK 契约):SDK 生成协商消息时写入,携带 `{id, round, maxRounds, performative}`。 -> validate API 依赖它区分协商消息与普通消息。 -> - `templateUri`(SDK 契约):SDK 生成消息时写入,标识渲染所用模板。 -> - `negotiation_message` / `negotiation_params`(引擎内部):接收校验后供自动循环和 -> 业务控制点读取,绝不作为线上的协议兼容键。 +规范协商 metadata 使用 `templateUri` 和 `negotiationContext`。后者携带 +`{id, round, maxRounds, performative}`,引擎解析协商消息时必须存在。引擎不会向线上报文添加私有协商状态键。 --- @@ -544,10 +558,10 @@ getReceivedMessages () 是保留层级的响应来源,getOutputs () 为便利 - 顶层远端 problem 错误通过 `RemoteProblemException` 保留;可用 `findIn(Throwable)` 查找包装异常中的原因。 工作流任务失败信息为 `remote.problem.`,详情进入 history / TASK_RESPONSE,不作为业务 outputs。 引擎节点事件携带 executionId,任务结果事件和 history 携带逻辑 taskId。 - 失败传播、双地市汇总规则和日志职责见 [集成指南](INTEGRATION_GUIDE.md#远端错误响应)。 + 失败传播、并行步骤失败规则和日志职责见 [集成指南](INTEGRATION_GUIDE.md#14-远端错误响应)。 - 回调异常/空值/超时明确失败;SDK 内容错误由宿主转换为 BusinessFailure,必要时保留安全的 code/details。 -- maxNegotiationExchanges 耗尽或本地 Stop 只结束本地交互,不自动发送 Abort。业务 Send (Abort) 不算诊断成功。 +- maxNegotiationExchanges 耗尽或本地 Stop 只结束本地交互,不自动发送 Abort。业务 Send(Abort) 不算任务成功。 - 远端状态和错误与业务输出分开,失败状态消息不成为 outputs,已有部分 artifact 保留。 - 缺失必需认证信息时拒绝发送,不静默匿名访问。 - SDK 流退出/传输错误日志是传输观察,不等同于工作流最终状态;以 ExecutionResult 和远端任务状态判断结果。 @@ -619,11 +633,10 @@ a2at: @Component public class MyAgentExecutor implements AgentExecutor { @Override - public ExecuteResult execute(ExecuteRequest request) { - // 业务逻辑 - return ExecuteResult.builder() - .addTextPart("result text") - .build(); + public void execute(RequestContext context, AgentEmitter emitter) throws A2AError { + // business logic, then emit progress and the final result: + emitter.updateStatus(TaskState.WORKING, statusMessage); + emitter.addArtifact(List.of(new TextPart("result text"))); } } ``` diff --git a/docs/zh/BUSINESS_CALLBACKS.md b/docs/zh/BUSINESS_CALLBACKS.md index aaf31ee7..87a00761 100644 --- a/docs/zh/BUSINESS_CALLBACKS.md +++ b/docs/zh/BUSINESS_CALLBACKS.md @@ -37,7 +37,7 @@ ControlPoint callbacks = ControlPoint.builder() ## 2. TaskRequest 与上游窗口 -TaskRequest 使用 getXxx () 访问器: +TaskRequest 使用 getXxx() 访问器: | 字段 | 含义 | |------------------------------|------------------------------------------------------------------| @@ -47,8 +47,8 @@ TaskRequest 使用 getXxx () 访问器: | input | BusinessInput:文本或任意 JSON 可序列化数据,二选一,不含 schema | | workflowInput | WorkflowInput(runtimeIntent, upstreamResults),与当前输入分离 | -BusinessInput.text (value) / BusinessInput.data (value) 创建输入快照。 -WorkflowInput、UpstreamStepResult、ReceivedMessage、NegotiationRequest 等 record 使用 field () 访问器。 +BusinessInput.text(value) / BusinessInput.data(value) 创建输入快照。 +WorkflowInput、UpstreamStepResult、ReceivedMessage、NegotiationRequest 等 record 使用 field() 访问器。 | contextFrom | 上游选择 | |---------------|----------------------------------| @@ -61,8 +61,8 @@ contextFrom 只选择证据,不建立执行依赖;依赖由 next 定义。 引擎不把窗口附加到 instruction/parts,也不调用 LLM 映射上游;由宿主决定怎么消费或映射下游输入。 窗口结构:stepName → taskResults[] → outputs[] / receivedMessages[]。 TaskExecutionResult 还保留 agentName、skill、逻辑 -taskId、taskDescription、status、 error、errorCode、errorDetails。多子任务不混合,嵌套数组仍作为一个输出项。 输出不要求来自 -LLM,也不要求符合投诉模板。 +taskId、taskDescription、status、error、errorCode、errorDetails。多子任务不混合,嵌套数组仍作为一个输出项。输出不要求来自 +LLM,也不要求符合特定领域模板。 ## 3. 最终内容与完整响应 @@ -71,7 +71,7 @@ record MessageContent(List> parts, Map metadata, Set taskMetadata, List artifacts) {} ``` -通过 MessageContent.text (text)、MessageContent.parts (parts) 或构造器创建快照。 TextPart、DataPart、FilePart 保留顺序及各自 +通过 MessageContent.text(text)、MessageContent.parts(parts) 或构造器创建快照。 TextPart、DataPart、FilePart 保留顺序及各自 metadata,文件引用不会自动下载。 MessageContent 不提供 role、目标、messageId、taskId、contextId 或认证头。 业务 metadata 中即使存在 contextId 字段,也不能覆盖真实 A2A 信封。 @@ -80,7 +80,7 @@ metadata 的业务结果仍可从完整视图读取。 便利 outputs 按 artifa artifact 时,可提取成功终态或独立 Message 的正文。 FilePart 仅在完整视图提供。不解析文本、不拼接相邻文本、不拍平嵌套业务数组。 失败状态消息只保留作证据,不进入 outputs;已返回的有效部分 artifact 仍保留。 SSE append/replace 按 artifact 组装,最终快照不重复累加。 -本地 onSelfTask 返回 TaskResult.success (List),允许空列表; TaskResult.failure (code, message) 将错误与输出分开,builder +本地 onSelfTask 返回 TaskResult.success(List),允许空列表; TaskResult.failure(code, message) 将错误与输出分开,builder 可保留有效部分输出。 远端 TaskResult 的 receivedMessages 是完整证据,便利输出由它派生。 远端 Task 只有 COMPLETED 才成功,独立 A2A Message 也可完成交互。 进度/协商提示不会因为含文本就变成工作流成功。 @@ -90,22 +90,19 @@ A2A Message 也可完成交互。 进度/协商提示不会因为含文本就 WorkflowEngineClientConfig 或 ExecutePsop 承载 LLM 配置。 ```java -// sdk、data、schema、模板选择均属于宿主。 +// sdk、data、schema、模板选择均属于宿主智能体。 MetadataContent generated = sdk.generateTaskPromptFromDataWithSchema( data, schema, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri()); -MessageContent outgoing = A2atMessages.from(generated, List.of(new TextPart("诊断当前专线"))); +MessageContent outgoing = A2atMessages.from(generated, List.of(new TextPart("处理当前任务"))); ``` -自然语言入口为 `sdk.generateTaskPromptFromText(text, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri())`,随后同样调用 -A2atMessages.from。 +自然语言入口为 `sdk.generateTaskPromptFromText(text, StandardTemplates.PRIVATE_LINE_COMPLAINT.uri())`,随后同样调用 A2atMessages.from。 -自然语言、结构化生成和校验等能力直接调用宿主 SDK。 A2atMessages.from 只复制生成的 metadata 并激活对应扩展,parts 保持业务提供的内容; -不生成、不校验业务语义。模板使用 StandardTemplates,不自行猜 URI。 可运行 SPN 示例见 WorkbenchControlPoint 和 -examples.negotiation.NegotiationStrategy。 A2ATInitialization 是 samples 宿主对锁定 SDK 的 JarFile 初始化隔离,不属于引擎。 +自然语言、结构化生成和校验等能力直接调用宿主 SDK。A2atMessages.from 只复制生成的 metadata 并激活对应扩展,parts 保持业务提供的内容;不生成、不校验业务语义。应选择当前 A2A-T SDK 发布的模板 URI,不自行构造。样例初始化工具属于宿主示例,不是引擎依赖。 ## 5. 协商 -NegotiationRequest (task, originalSubmission, received, previousExchanges, remainingWait): task 是原始 +NegotiationRequest(task, originalSubmission, received, previousExchanges, remainingWait): task 是原始 TaskRequest,originalSubmission 是首次最终提交, received 是当前完整响应,previousExchanges 只含该会话的 Exchange (received, reply), remainingWait 是本次交互剩余时间。引擎不规定业务 proposal 分类或 schema。 @@ -118,158 +115,59 @@ nextRound 或返回新 Propose。 Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 `maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 +COMPLETED 确认,也不能判为任务成功。 业务可调用 validateProposePromptAndDataFilling,再选择 SDK 强类型 fromData 或自然语言生成接口。 -不得虚构缺失客户事实;样例只从本地当前地市任务输入提取实际请求的字段。 SDK 内容异常由宿主映射成 BusinessFailure (code, +不得虚构缺失业务事实;只从当前任务的权威输入中提取实际请求的字段。SDK 内容异常由宿主映射成 BusinessFailure(code, safeMessage, safeDetails); 引擎不识别 SDK 专属内容异常类,也不自动转存可能含敏感数据的原异常。 ## 6. 路由、并发与失败 -RouteRequest (executionId, stepName, workflowInput, currentResults, candidates)。 候选为 RouteOption (nextStep, -condition),返回 RouteDecision.builder ().nextStep (允许目标).build ()。 无条件边自动并行推进,条件分支只接受候选中的目标。 +RouteRequest(executionId, stepName, workflowInput, currentResults, candidates)。 候选为 RouteOption(nextStep, +condition),返回 RouteDecision.builder().nextStep(允许目标).build()。 无条件边自动并行推进,条件分支只接受候选中的目标。 -回调可能并发,不要共享可变“当前任务/城市”字段。 每个工作流任务从内容准备到传输完成受客户端 timeout 总体限制,默认 +回调可能并发,不要共享可变的“当前任务”状态。每个工作流任务从内容准备到传输完成受客户端 timeout 总体限制,默认 sendTimeoutSeconds=600; 路由单独限制回调等待时间,dispatch/协商另有总等待截止时间。取消/超时后晚到结果不发送, 但不等于自动取消宿主正在运行的 LLM 或业务操作,宿主负责清理其资源。 同步回调入口应迅速返回,阻塞任务应交给异步执行器。 回调缺失、返回 null 或异常均明确失败;不确定发送失败不自动重发。 ## 7. 独立授权与订阅 -ExtensionSender.sendAuthorization (agentName, finalContent) 返回 CompletableFuture。 -ExtensionSender.openNotification (agentName, finalContent, (handle, received) -> ...) 立即返回 handle。 -传入宿主生成的最终授权/订阅内容并激活对应扩展。 I/O 前注册 handle,早到事件可直接 handle.close (),不用捕获尚未赋值的外部变量。 -acknowledgement () 代表真实 ACK 或失败;超时不伪造成功。 close () 请求关闭,completion () 在流实际退出后完成。 -订阅保持到抢通结果、显式取消或宿主退出,不随单次工作流结束自动关闭。 +ExtensionSender.sendAuthorization(agentName, finalContent) 返回 CompletableFuture。 +ExtensionSender.openNotification(agentName, finalContent, (handle, received) -> ...) 立即返回 handle。 +传入宿主生成的最终授权/订阅内容并激活对应扩展。 I/O 前注册 handle,早到事件可直接 handle.close(),不用捕获尚未赋值的外部变量。 +acknowledgement() 代表真实 ACK 或失败;超时不伪造成功。 close() 请求关闭,completion() 在流实际退出后完成。 +订阅保持到宿主定义的终态事件、显式取消或宿主退出,不随单次工作流结束自动关闭。 -任务、授权、通知使用独立 transport/runtime/context。 授权和订阅成功与否均不作为工作流执行前提; 有效白名单只决定 OMC -是否允许额外的自动抢通操作。 +任务、授权、通知使用独立 transport/runtime/context。授权和订阅成功与否均不作为工作流执行前提;授权策略只控制与该策略关联的宿主自定义业务操作。 ## 8. 验证和日志 -### 查看真实协商与协议日志 +执行 `mvn -B clean verify`。Reactor 覆盖回调契约、完整响应组装、协商关联、独立扩展生命周期、失败传播、取消和协议日志脱敏。测试使用受控测试数据,不覆盖 SDK schema 或模板;这些是发布回归证据,不等于生产端点或真实模型语义认证。日志与认证配置见 [集成指南](INTEGRATION_GUIDE.md)。 -直接运行本地 SpringSpnDemo,默认 City1 缺参并协商、City2 参数完整直接诊断,无需添加 VM 参数。 Negotiation-T 扩展激活本身不强制协商;这是本地 -Demo 专门设置的场景,不是引擎默认行为。 要关闭本地缺参演示、让两城市都直接诊断,在 IDEA **VM options** 增加: +## 9. 业务侧 A2A-T 1.1.0 调用参考 -```text --Da2at.samples.negotiation=false -``` - -默认仅移除 City1 的 Task-T 任务对象;启用状态下增加 `-Da2at.samples.negotiation.city=city2` 或 `both` -可演示 City2 或两城市同时缺参。宿主仍保留各城市的正确输入,原投诉上下文不变。预期链路是 `DEMO_NEGOTIATION` → -`INPUT_REQUIRED / PROPOSE` -→ 集成方 onNegotiation → `ACCEPT` → 两城市完成 → 一次汇总。 非内嵌 OMC 模式默认不注入缺参,显式设置 -`-Da2at.samples.negotiation=true` 也会被拒绝。 Demo 将开关传入当前 Spring 应用实例,不设置或修改 JVM 全局开关,不影响其他宿主。 -协议日志在控制台及以运行目录为基准的 `logs/spn-demo.log`。 本分支使用直连模式。 - -离线重复验证: - -```powershell -mvn -q -pl samples -am "-Dtest=SpringSpnDemoE2ETest" "-Dsurefire.failIfNoSpecifiedTests=false" test -``` - -每个测试类覆盖无 VM 参数的默认单城市协商、显式关闭、显式开启及两城市同时缺参。测试使用离线 LLM provider,但实际运行当前 SDK -的 模板、校验和 HTTP/SSE;不是现网 OMC、平台或真实模型语义的验证。 - -启动测试使用公开模拟凭据,不覆盖 SDK 模板或 schema。 运行时固定测试端口需空闲;本地工作流完成后最多观察首次抢通通知十秒, -没有抢通通知不改变工作流结果。认证和日志配置详见 [集成指南](INTEGRATION_GUIDE.md)。 - -## 9. 示例业务侧的 A2A-T 1.1.0 调用参考 +引擎只调度最终 A2A 消息;生成、校验、填充和业务判断属于宿主智能体与被调度智能体的业务代码。每个角色只调用自己所有消息需要的 API。 -引擎只调度最终 A2A 消息;下列生成、校验、填充和业务判断都在 samples 中完成。 不是每个智能体都调用全部 API,也不是同一消息依次调用 -FromText 和 FromData。 +| 业务位置 | 生成/接收校验入口 | 对结果的业务使用 | +|-----------------------------------|-------------------------------------------------------------------------------------------------------------|----------------------| +| 宿主智能体入站任务 | validateTaskPromptAndDataFilling | 使用已校验 `filled.data` 选择工作流并构建业务输入 | +| onTask → 被调度智能体 | generateTaskPromptFromDataWithSchema 或 generateTaskPromptFromText → validateTaskPromptAndDataFilling | 从已校验数据构建输入,不直接使用扩展原文 | +| 被调度智能体 → 宿主协商 | generateNegotiationProposePromptFromData 或 FromText → validateProposePromptAndDataFilling | 仅从当前任务权威数据回答实际请求项 | +| 宿主 Accept → 被调度智能体 | generateNegotiationAcceptPromptFromData 或 FromText → validateAcceptPromptAndDataFilling | 合并已校验的请求字段,再执行领域校验 | +| 宿主 Reject / Abort → 被调度智能体 | 阶段对应生成入口 → 阶段对应校验入口 | 校验原因并终止当前任务路径 | +| 独立授权 | generateAuthPromptFromDataWithSchema → validateAuthPromptAndDataFilling | 从 `filled.data` 构建并应用 AuthorizationPolicy | +| 独立订阅 | generateNotificationPromptFromDataWithSchema → validateNotificationPromptAndDataFilling | 构建 NotificationPolicy 并保持独立连接 | -| 业务位置 | 生成/校验入口 | 对结果的业务使用 | -|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| -| WAIMO → 集成方 | SpringSpnDemo:generateTaskPromptFromDataWithSchema;WorkbenchTaskInputParser:validateTaskPromptAndDataFilling | 将 filled.data 作为投诉参数,按任务检索工作流 | -| 集成方 onTask → 两地市 OMC | WorkbenchControlPoint:generateTaskPromptFromDataWithSchema;NegotiationBaseAgentExecutor:validateTaskPromptAndDataFilling | 用 filled.data 构造 SpnTaskInput,交给 executeBusiness;不把原始协议提示词传给诊断 | -| OMC 缺参 → 集成方 onNegotiation | 两地市共用 generateNegotiationProposePromptFromData;NegotiationStrategy:validateProposePromptAndDataFilling | 从 items 读取实际请求字段,仅从当前城市原始业务输入补充,不使用另一城市的数据 | -| 集成方补参 → OMC | generateNegotiationAcceptPromptFromData;validateAcceptPromptAndDataFilling | 合并所请求字段的 filled.data,复核端口、投诉分类及 OSS 流水号后执行诊断 | -| 集成方拒绝/终止 → OMC | generateNegotiationRejectPromptFromData/generateNegotiationAbortPromptFromData;对应 validateRejectPromptAndDataFilling/validateAbortPromptAndDataFilling | 校验原因后结束当前诊断任务,不进入诊断业务 | -| 独立白名单授权 | ExtensionPrePositioner:generateAuthPromptFromDataWithSchema;PrePositionedExtensionHandler:validateAuthPromptAndDataFilling | 用 filled.data 构建并应用 AuthorizationPolicy | -| 独立订阅 | ExtensionPrePositioner:generateNotificationPromptFromDataWithSchema;PrePositionedExtensionHandler:validateNotificationPromptAndDataFilling | 用 filled.data 构建 NotificationPolicy,保持独立通知连接 | - -### 输入选择与消息封装 - -有结构化业务参数时使用上述 FromDataWithSchema;只有自然语言时,业务可改用 generateTaskPromptFromText (text, templateUri) -、generateAuthPromptFromText (text, templateUri)、 generateNotificationPromptFromText (text, templateUri)。协商同时提供各阶段 +有结构化业务参数时使用上述 FromDataWithSchema;只有自然语言时,业务可改用 generateTaskPromptFromText(text, templateUri) +、generateAuthPromptFromText(text, templateUri)、 generateNotificationPromptFromText(text, templateUri)。协商同时提供各阶段 FromText 和强类型 FromData。 这些是输入方式的选择,不是每条消息必须全部执行的流水线。FromDataWithSchema 也可能调用 SDK 配置的 LLM, 不能等同于完全不调用模型。 -无论选择哪一种生成入口,都保留返回 MetadataContent 的完整 metadata,再通过 A2atMessages.from (generated, List.of (new -TextPart ("业务摘要"))) 转成引擎 MessageContent。 parts 必须有合法内容;摘要不是正式扩展正文,不能拿摘要替代校验输入。不要只复制 +无论选择哪一种生成入口,都保留返回 MetadataContent 的完整 metadata,再通过 A2atMessages.from(generated, List.of (new +TextPart("业务摘要"))) 转成引擎 MessageContent。 parts 必须有合法内容;摘要不是正式扩展正文,不能拿摘要替代校验输入。不要只复制 promptText 而丢失 templateUri、negotiationContext 或扩展 URI。 - -### 信息协商的数据与失败边界 - -本场景按 SDK 投诉模板的业务字段协商:任务对象中包含本地市接入端口,任务上下文中包含投诉分类、 -OSS侧事件流水号及已有的时间和详情。已验证任务只有部分字段不足时仅请求这些字段; SDK 拒绝整个原始输入时不信任其中的局部内容,重新请求完整业务字段。当前请求项采用 -AND,所有请求字段必需。 - -接收 Propose 的参数 schema 中,items 是非空字段名数组,relationship 是可空逻辑关系字符串; 其 description 说明“实际请求字段”和 -AND/OR 含义。Accept 校验 schema 则按本轮请求动态生成, 字段为非空字符串并列入 required。SpnTaskInput 的端口、流水号等规则仅是 -SPN 示例业务规则, 不是通用执行引擎的输出格式约束;正式 OMC 应接入自身资源清单与领域校验。 - 回复必须匹配 A2A taskId/contextId 及协商 id/round/maxRounds,正式正文从 Negotiation-T metadata 读取。 -一次有效回复只消费一次。错误正文、漏填、外地市端口或跨协商回复不能进入诊断。 Reject 表示业务拒绝提供;Abort -表示无法核实信息或终止协商,均不是诊断成功。 - -默认 NegotiationStrategy 允许提供已掌握的当前任务字段;无法核实时生成 Abort,不编造字段。 宿主可通过 mayProvideField (task, -fieldName) 注入披露策略,拒绝时由示例生成 Reject: - -```java -var strategy = new dev.openan.workflow.engine.examples.negotiation.NegotiationStrategy(envPath) { - @Override - protected boolean mayProvideField( - dev.openan.workflow.engine.model.TaskRequest task, String fieldName) { - return allowedFields.contains(fieldName); // 宿主已计算的业务允许字段集合 - } -}; -var callbacks = new dev.openan.workflow.engine.examples.workbench.WorkbenchControlPoint(envPath, strategy); -``` - -这里的字段披露策略与 Authorization-T 的“自动抢通白名单”是两件事。 - -### 结果和通知不要错用请求生成接口 - -诊断结果、授权回执、业务抢通事件由 OMC 业务生成。1.1.0 的上述授权/通知入口面向授权操作和订阅请求, 不能把抢通事件再次交给订阅请求生成/校验接口。示例 -RecoveryNotification 按 SPN 业务事件格式检查正式 Notification-T artifact metadata:包含任务流水号、OSS流水号、端口、方案及完成结果等必要字段且状态为已结束, -才确认收到最终结果并关闭该订阅;成功和失败都是“执行已结束”,不把失败结果冒充成功。 单独 artifact 名称、parts 摘要、订阅 -ACK、未启动方案均不能触发关闭。不完整事件仍交给通知回调观察。 - -City1 上报的端口和 OSS 流水号来自本次已校验 SpnTaskInput,不再固定写成样例常量。 City2 -没有故障可以不产生抢通结果;授权/订阅失败和是否收到抢通通知,均不决定工作流诊断是否启动或成功。 - -### 可重复验证范围 - -EmbeddedA2AServerTest 覆盖正常填充数据、Accept 填充数据保留、错误正文、漏填、跨协商回复、 Reject 和 -Abort;NegotiationStrategyTest 覆盖城市输入隔离、不可核实的 Abort 和业务拒绝的 Reject; RecoveryNotificationTest -覆盖完整成功/失败结果、仅摘要、错误命名和不完整事件。 SpringSpnDemoE2ETest 覆盖完整输入、City1 缺参、两城市同时缺参。 完整业务抢通链路由 SpnCrossCityE2ETest 验证。 - -这些测试用真实 SDK 1.1.0 的生成和校验管线、真实 A2A HTTP/SSE。 离线 LLM provider -仅解析明确的样例输入,拒绝错误摘要/缺参,不代表真实模型的语义质量或现网联调已通过。 - -### 授权策略格式与协商字段(SDK 1.1.0) - -新增授权使用 SDK 当前的编号、标签、换行格式,不能传四个无标签的裸值: - -```text -1. 业务场景是业务投诉诊断,处置类型是业务抢通,操作名称是隧道调优,有效期是2026-06-01~2030-06-18 -2. 业务场景是视频保障,处置类型是故障切换,操作名称是频谱重耕,有效期是永久生效 -``` - -OMC 示例仅从 SDK validate-and-fill 的数据构造白名单,逐字段提取标签后的值, 按业务场景、处置类型、操作名称精确匹配,有效期包含起止日。全部规则解析成功才替换原策略; -任意一条非法均保持原策略,不降级成默认授权。 - -示例只保存一组内存白名单(固定示例 ID),不代表生产策略库: 删除请求使用 `1. 策略标识是<当前策略UUID>`;仅支持单个精确 -ID,不支持批量或条件删除。 查询省略策略列表或传空字符串,返回全部当前规则;不支持带条件查询,不会静默忽略条件。 -修改、条件查询、批量/条件删除需由集成方的策略库实现。该业务限制不代表 A2A-T SDK 不支持这些操作。 - -信息协商仍先调用 SDK `validateProposePromptAndDataFilling`。当前中文信息协商模板由 typed API -生成,示例从已通过语义校验的“所需信息项”中保留编号后的原始名称(如“任务对象”)用于回复, -不把模型提取出的说明文字(如“本地市实际接入端口名称”)当成新字段,也不模糊映射到其他数据。 未知字段或本地数据缺失时 -Abort,业务披露策略拒绝时 Reject;不能用其他地市的信息补齐。 这些处理均在 samples 的业务层,不进入通用引擎。 +一次有效回复只消费一次。正文错误、字段不完整、任务不匹配或跨协商的回复不得执行业务。无法核实权威信息时生成 SDK Abort,不编造数据;宿主披露策略拒绝时生成 SDK Reject。授权策略是独立关注点。 -Notification-T 的失败原因字段使用完整名称“业务抢通方案执行失败原因”,执行失败时必填。 SDK 校验拒绝时记录错误码、slot -错误和已提取参数的键名,方便定位;不绕过校验。 授权、订阅的成功与否仍不阻断独立工作流。 +Authorization-T 和 Notification-T 的生成接口用于生成请求,不用于包装任意业务结果。授权回执与通知事件是被调度智能体的业务输出,必须遵循选定模板和应用契约。订阅只在宿主定义的终态事件、显式取消或关闭时结束;ACK 本身不是终态业务事件。SDK 拒绝应保留安全的错误码、slot 错误与已提取参数键。授权或订阅失败仍不阻断工作流执行。 diff --git a/docs/zh/DESIGN.md b/docs/zh/DESIGN.md index 11377939..a4aae8f8 100644 --- a/docs/zh/DESIGN.md +++ b/docs/zh/DESIGN.md @@ -37,17 +37,17 @@ flowchart TB classDef local fill: #F8FAFC, stroke: #64748B, color: #0F172A, stroke-dasharray: 5 3 classDef agent fill: #F5F3FF, stroke: #7C3AED, color: #2E1065, stroke-width: 1.5px - subgraph CONTROL["周边控制面与上游系统"] + subgraph CONTROL["外部系统"] direction LR - WAIMO["WAIMO
上游任务发起方"]:::external - REG["注册中心 / Registry Center
AgentCard 发布与查询"]:::external - ORCH["编排中心 / Orchestration Center
工作流检索与 PSOP 加载"]:::external + CALLER["外部 A2A 请求方
任务请求与最终响应"]:::external + REG["注册中心
AgentCard 发布与发现"]:::external + ORCH["编排中心
工作流检索与加载"]:::external end - subgraph HOST["宿主智能体:集成方 / Integrator Agent"] + subgraph HOST["宿主智能体"] direction TB - ENTRY["A2A 服务端入口
接收 Task-T,解析诊断意图"]:::host - ADAPTER["宿主集成层
准备 AgentCard、Workflow、配置和业务上下文"]:::host + ENTRY["A2A 服务端入口
接收并校验入站任务"]:::host + ADAPTER["宿主集成层
准备 AgentCard、Workflow、配置和上下文"]:::host subgraph ENGINE["嵌入式 Workflow Execution Engine SDK"] direction TB @@ -57,49 +57,45 @@ flowchart TB EXT["流程外协议操作
ExtensionSender
Authorization-T / Notification-T"]:::sdk end - BIZ["集成方业务实现
业务接管、路由、汇总、持久化、通知处理"]:::host + BIZ["宿主业务实现
内容、路由、汇总、持久化与通知处理"]:::host end - subgraph SAMPLE["Demo 本地替代路径(非生产数据源)"] + subgraph SAMPLE["可选本地测试资源(非生产数据源)"] direction LR - LOCALCARD["本地 AgentCard JSON
WorkbenchAgentCatalog"]:::local - LOCALPSOP["本地 PSOP fallback
仅用于离线演示"]:::local + LOCALCARD["本地 AgentCard JSON"]:::local + LOCALPSOP["本地 Workflow 测试数据"]:::local end - subgraph DOWNSTREAM["下游业务智能体"] - OMCS["地市 OMC 智能体群
地市 1 OMC | 地市 2 OMC"]:::agent + subgraph DOWNSTREAM["被调度智能体"] + AGENTS["一个或多个被调度智能体"]:::agent end - WAIMO <-->|" Task-T 诊断任务 / 汇总 artifact "| ENTRY - ENTRY -->|" 运行意图 "| ADAPTER - REG -->|" 生产:查询 AgentCard "| LOAD - ORCH -->|" search / load PSOP "| LOAD - LOCALCARD -.->|" Demo 替代注册中心 "| ADAPTER - LOCALPSOP -.->|" Demo 检索失败时 "| ADAPTER - LOAD -->|" 发现结果交给宿主 "| ADAPTER + CALLER <-->|"A2A 任务 / 最终结果"| ENTRY + ENTRY -->|"已校验的意图与输入"| ADAPTER + REG -->|"发现 AgentCard"| LOAD + ORCH -->|"检索 / 加载 Workflow"| LOAD + LOCALCARD -.->|"开发测试数据"| ADAPTER + LOCALPSOP -.->|"开发测试数据"| ADAPTER + LOAD -->|"发现结果"| ADAPTER ADAPTER -->|" Workflow + AgentCards "| RUN RUN <-->|" 业务决策与执行结果 "| CALLBACK CALLBACK <-->|" 回调 "| BIZ ADAPTER -->|" 独立业务时机 "| EXT - RUN <-->|" 向两地市并行 Task-T
必要时 Negotiation-T / 返回诊断结果 "| OMCS - EXT <-->|" Authorization-T 一次性授权
Notification-T 长连接订阅与结果 SSE "| OMCS - BIZ -->|" 汇总结果 "| ENTRY + RUN <-->|"Task-T;必要时 Negotiation-T"| AGENTS + EXT <-->|"Authorization-T / Notification-T"| AGENTS + BIZ -->|"最终业务结果"| ENTRY ``` -上图中的 SDK 是 **嵌入宿主智能体进程的库**,不是独立部署的编排服务。当前 -`SpringSpnDemo` 的宿主是集成方智能体,实际责任链如下: +上图中的 SDK 是 **嵌入宿主智能体进程的库**,不是独立部署的编排服务: -1. WAIMO 通过 A2A Task-T 调用集成方的服务端入口。 -2. 集成方准备执行输入:生产环境从注册中心获取下游 AgentCard,并根据任务意图从编排中心 搜索、加载 PSOP;SDK 提供可选的 - `RegistryClient` 和 `LoadPsop` 辅助 API,但何时发现、如何缓存、 失败策略均由宿主决定。 -3. 集成方把 `Workflow`、AgentCard、运行意图和业务回调交给 `ExecutePsop`。执行引擎遍历 DAG, 并行向两个地市 OMC 下发任务,并在必要时处理 - Negotiation-T;集成方通过 `ControlPoint` 接管 本地汇总、路由和澄清等业务操作。 -4. 集成方把汇总结果作为 Task-T artifact 和完成状态返回 WAIMO。 -5. Authorization-T 与 Notification-T 由集成方在各自业务时机通过 `ExtensionSender` 单独触发, 不属于 PSOP DAG,也不与工作流任务复用 - transport/runtime/context。 +1. 宿主智能体接收并校验入站 A2A 任务,准备执行意图和业务输入。 +2. 宿主智能体从注册中心获取被调度智能体的 AgentCard,并从编排中心加载 `Workflow`。 + `RegistryClient` 和 `LoadPsop` 是可选辅助 API;发现时机、缓存与失败策略仍由宿主智能体负责。 +3. 宿主智能体把 `Workflow`、AgentCard、运行意图和回调交给 `ExecutePsop`。引擎遍历 DAG、并行下发就绪任务,并在需要时处理 Negotiation-T;`ControlPoint` 返回业务内容、本地结果、路由决策和协商回复。 +4. 宿主智能体消费 `ExecutionResult`,返回或持久化最终业务结果。 +5. Authorization-T 与 Notification-T 由宿主智能体在独立业务时机通过 `ExtensionSender` 触发,不属于 DAG,也不与工作流任务复用 transport/runtime/context。 -Demo 为保证离线可运行,`WorkbenchAgentCatalog` 从 classpath 加载 AgentCard;仅在编排中心搜索或 加载失败时使用本地 PSOP -fallback。这两项是样例替代路径,不代表生产环境的数据来源或容灾策略。 图的可编辑源文件见 +本地 AgentCard 和 Workflow 只是开发测试资源,不代表生产发现或容灾策略。图的可编辑源文件见 [`docs/diagrams/workflow-engine-surrounding-systems.mmd`](../diagrams/workflow-engine-surrounding-systems.mmd)。 --- @@ -177,10 +173,9 @@ nextRound 或返回新 Propose。 Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 `maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 +COMPLETED 确认,也不能判为任务成功。 -Authorization-T 和 Notification-T 是独立业务操作,不属于 DAG。宿主生成内容并使用独立发送器;失败不影响工作流。白名单仅影响 -OMC 自动抢通,订阅保持到业务主动关闭。 +Authorization-T 和 Notification-T 是独立业务操作,不属于 DAG。宿主生成内容并使用独立发送器;失败不影响工作流。授权策略只控制自身的业务操作,订阅保持到宿主主动关闭。 ## 6. 条件路由 @@ -219,7 +214,7 @@ OMC 自动抢通,订阅保持到业务主动关闭。 sequenceDiagram participant H as Host callbacks + A2A-T client participant E as Workflow engine - participant A as Remote agent + participant A as Dispatched agent E->>H: onTask(TaskRequest + upstream window) H->>H: Generate/validate final content H-->>E: MessageContent @@ -240,7 +235,7 @@ sequenceDiagram sequenceDiagram participant H as Host + A2A-T client participant ES as Independent ExtensionSender - participant A as OMC + participant A as Dispatched agent H->>H: Generate final authorization content H->>ES: sendAuthorization(agent, content) ES->>A: One-shot authorization @@ -251,7 +246,7 @@ sequenceDiagram ES->>A: Independent long-lived stream A-->>H: ACK via acknowledgement() A-->>H: listener(handle, ReceivedMessage) - H->>ES: handle.close() on recovery/cancel/shutdown + H->>ES: 终态事件 / 取消 / 停机时 handle.close() ES-->>H: completion() after stream exits ``` @@ -259,7 +254,7 @@ sequenceDiagram workflow-engine:A2A Java `1.2.0.Final`(REST/JSON-RPC/gRPC)、匹配的 gRPC runtime、 `net.openan.a2a-t.sdk:a2a-t-core:1.1.0`、Jackson、SLF4J。 纯引擎消费者不会传递引入 A2A-T client/server、LLM、prompt 或 -resources。 samples/宿主显式依赖 a2a-t-client,需要实现 OMC 接收端时另依赖 a2a-t-server。 注册中心和编排中心由宿主调用,可选择 +resources。宿主智能体显式依赖 a2a-t-client,校验接收内容的被调度智能体服务另依赖 a2a-t-server。注册中心和编排中心由宿主调用,可选择 RegistryClient/LoadPsop 辅助接口或自己的实现。 模板和 slot schema 来自锁定 SDK jar,样例不覆盖同名资源。 ## 10. 设计决策总结 diff --git a/docs/zh/DEVELOPER_GUIDE.md b/docs/zh/DEVELOPER_GUIDE.md index 202e0ee4..8e9bc938 100644 --- a/docs/zh/DEVELOPER_GUIDE.md +++ b/docs/zh/DEVELOPER_GUIDE.md @@ -14,8 +14,7 @@ ``` -引擎会传递性引入 A2A 协议 SDK(`a2a-java-sdk-client`,含 REST、JSON-RPC、gRPC 传输) 和最小 A2A-T 核心(`a2a-t-core` -)。生成内容的宿主显式依赖 a2a-t-client,OMC 接收端另引入 a2a-t-server。 +引擎会传递性引入 A2A 协议 SDK(`a2a-java-sdk-client`,含 REST、JSON-RPC、gRPC 传输) 和最小 A2A-T 核心(`a2a-t-core`)。生成 A2A-T 内容的宿主智能体显式依赖 a2a-t-client,校验接收内容的被调度智能体服务另引入 a2a-t-server。 ## 2. 核心概念 @@ -61,7 +60,7 @@ ExecutionResult result = ExecutePsop.builder() .psop(workflow) .agentCards(agentCards) .controlPoint(new MyControlPoint()) - .runtimeIntent("诊断SPN故障") + .runtimeIntent("分析服务异常") .lang("zh") .sslVerify(false) .credentialsConfigPath("agent_credentials.json") @@ -116,7 +115,7 @@ try (var client = new DefaultWorkflowEngineClient(agentCards, a2aRuntime, new MyControlPoint(), client, new EventCallback(), - "诊断故障", + "分析请求", "zh" ); ExecutionResult result = executor.run().join(); @@ -134,13 +133,13 @@ nextRound 或返回新 Propose。 Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 `maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 +COMPLETED 确认,也不能判为任务成功。 ### 6.2 工作流模型字段 | 字段 | 位置 | 含义 | |-----------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `steps[].stepType` | `WorkflowStep` | `AllSuccess`(默认):所有子任务必须成功;`AnySuccess`:任一成功即可;`SelfLoop`:工作流智能体通过 `onSelfTask` 本地处理(不向命名智能体发送 A2A-T 消息)。 | +| `steps[].stepType` | `WorkflowStep` | `AllSuccess`(默认):所有子任务必须成功;`AnySuccess`:任一成功即可;`SelfLoop`:宿主智能体通过 `onSelfTask` 本地处理(不向命名智能体发送 A2A-T 消息)。 | | `steps[].subtasks[]` | `Task` | 每个含 `agent`、`skill`、`description`。每个子任务触发一次 `onTask`(或 SelfLoop 的 `onSelfTask`)调用。 | | `steps[].next[]` | `List` | 分支目标。`step` = 下一步名称;`condition` = 规则文本。 | | `steps[].layer` | `WorkflowStep` | 编排层级提示;实际就绪条件由 DAG 前驱关系决定。 | @@ -167,7 +166,7 @@ AgentCard card = mapper.readValue(json, AgentCard.class); ```json { - "SPN Domain Agent": { + "Dispatched Agent": { "bearerAuth": { "login_url": "https://127.0.0.1:8080/auth/login", "method": "POST", @@ -225,9 +224,8 @@ WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder() ## 9. A2A-T 环境(.env) -引擎不读取 A2A-T .env,也不创建 LLM client。业务回调需要 A2A-T 时, 宿主用自己的环境文件初始化 A2ATClient/A2ATServer,配置 -provider/model/key/base URL 和 A2AT_LANGUAGE。 样例的 a2atEnvPath 是宿主/Demo 配置,不是引擎 builder 参数。 OMC 凭据解密不与 -LLM 配置耦合:内置凭据模式通过 WorkflowEngineClientConfig.builder ().credentialEncryptionKey (key) 显式提供密钥, 再将配置好的 +引擎不读取 A2A-T .env,也不创建 LLM client。宿主智能体的业务回调需要 A2A-T 时,由宿主使用自有环境文件初始化 A2ATClient/A2ATServer,配置 provider/model/key/base URL 和 A2AT_LANGUAGE。样例中的 a2atEnvPath 不是引擎 builder 参数。被调度智能体凭据解密不与 +LLM 配置耦合:内置凭据模式通过 WorkflowEngineClientConfig.builder().credentialEncryptionKey (key) 显式提供密钥, 再将配置好的 engineClient 传给 ExecutePsop。自定义 AuthProvider 自行管理 token 和配置。 测试使用当前 SDK SPI 的离线 provider,不覆盖模板,也不是生产失败兜底。 @@ -279,11 +277,10 @@ public Flux execute(@PathVariable String psopId) { ### 协议日志 pretty 展示 协议日志默认以 pretty 形式展示:逐行打印头字段,缩进 JSON 正文。SSE 保留 id/event 等事件信息, JSON 在 -`=== SSE data (JSON display; not wire text) ===` 与 `=== End SSE data ===` 之间单独展示, 不再为展开后的每行 JSON 添加 +`=== SSE data(JSON display; not wire text) ===` 与 `=== End SSE data ===` 之间单独展示, 不再为展开后的每行 JSON 添加 `data:` 前缀。多个事件各自保留边界。 `WORKFLOW_ENGINE_PROTOCOL_PRETTY=false`(环境变量或同名 JVM 属性)可保留脱敏后的原始正文展示。 pretty 只影响显示(JSON 缩进及 SSE 数据区标记),不改实际发送字节、metadata、数值或扩展头; 原始观测正文仍保留,SSE pretty 展示不是可直接重放的抓包。 JSON -字符串中的 `\\n` 保留转义,避免把业务字符串误写成非法 JSON;非 JSON/不完整正文保持原样。 日志仍执行脱敏和容量限制。Demo 启动记录 -`NEGOTIATION_DEMO enabled=..., city=...`; 本地默认 City1 协商、City2 直接诊断,`-Da2at.samples.negotiation=false` 关闭缺参演示。 -协商激活位于 `A2A-Extensions: .../Negotiation-T/v1`,不存在专用的 Negotiation-T HTTP 头; 正文检查对应 URI 的 metadata 和 +字符串中的 `\\n` 保留转义,避免把业务字符串误写成非法 JSON;非 JSON/不完整正文保持原样。日志仍执行脱敏和容量限制。验证协商流量时, +检查 `A2A-Extensions: .../Negotiation-T/v1`;不存在专用的 Negotiation-T HTTP 头。正文检查对应 URI 的 metadata 和 `negotiationContext`,不要只检索大写 `NEGOTIATION-T`。 diff --git a/docs/zh/INTEGRATION_GUIDE.md b/docs/zh/INTEGRATION_GUIDE.md index 9dd72fa2..87510315 100644 --- a/docs/zh/INTEGRATION_GUIDE.md +++ b/docs/zh/INTEGRATION_GUIDE.md @@ -38,15 +38,15 @@ A2A-T 工作流执行引擎是一个 Java SDK,用于基于 A2A 协议和 A2A-T ```java Workflow workflow = Workflow.builder() - .name("故障诊断") + .name("服务分析") .steps(List.of( WorkflowStep.builder() - .name("diagnose") + .name("analyze") .subtasks(List.of( Task.builder() - .agent("SPN Domain Agent") - .skill("diagnosis") - .description("诊断故障") + .agent("Dispatched Agent") + .skill("analysis") + .description("分析当前请求") .build())) .next(List.of( JumpCondition.builder() @@ -57,10 +57,10 @@ Workflow workflow = Workflow.builder() .build(), WorkflowStep.builder() .name("merge") - .stepType(StepType.SELF_LOOP) // 自环节点:工作台本地汇总,不发 A2A-T 给自己 + .stepType(StepType.SELF_LOOP) // 宿主智能体本地步骤,不发送 A2A-T .subtasks(List.of( Task.builder() - .agent("Transport Workbench Agent") + .agent("Host Agent") .skill("aggregate") .description("汇总结果") .build())) @@ -134,7 +134,7 @@ CompletableFuture execution = ExecutePsop.builder() .psop(workflow) .agentCards(cards) .controlPoint(callbacks) - .runtimeIntent("SPN跨城专线故障诊断与抢通") + .runtimeIntent("分析服务请求") .lang("zh") .credentialsConfigPath("credentials.json") .sslVerify(true) @@ -156,9 +156,8 @@ try { ### 5.1 .env 文件 -引擎不读取 A2A-T .env,也不创建 LLM client。业务回调需要 A2A-T 时, 宿主用自己的环境文件初始化 A2ATClient/A2ATServer,配置 -provider/model/key/base URL 和 A2AT_LANGUAGE。 样例的 a2atEnvPath 是宿主/Demo 配置,不是引擎 builder 参数。 OMC 凭据解密不与 -LLM 配置耦合:内置凭据模式通过 WorkflowEngineClientConfig.builder ().credentialEncryptionKey (key) 显式提供密钥, 再将配置好的 +引擎不读取 A2A-T .env,也不创建 LLM client。宿主智能体的业务回调需要 A2A-T 时,由宿主使用自有环境文件初始化 A2ATClient/A2ATServer,配置 provider/model/key/base URL 和 A2AT_LANGUAGE。样例中的 a2atEnvPath 不是引擎 builder 参数。被调度智能体凭据解密不与 +LLM 配置耦合:内置凭据模式通过 WorkflowEngineClientConfig.builder().credentialEncryptionKey (key) 显式提供密钥, 再将配置好的 engineClient 传给 ExecutePsop。自定义 AuthProvider 自行管理 token 和配置。 测试使用当前 SDK SPI 的离线 provider,不覆盖模板,也不是生产失败兜底。 @@ -168,15 +167,13 @@ provider,不覆盖模板,也不是生产失败兜底。 ```json { - "SPN Domain Agent": { + "Dispatched Agent": { "bearerAuth": { - "login_url": "https://127.0.0.1:26335/rest/plat/smapp/v1/oauth/token", - "method": "PUT", + "login_url": "https://agent.example.com/oauth/token", + "method": "POST", "request_fields": { - "grantType": "password", - "userName": "admin", - "value": "enc::", - "ipaddr": "*" + "username": "service-account", + "password": "enc::" }, "token_field": "accessSession", "token_ttl": 3600 @@ -318,7 +315,7 @@ AgentCard 通过 `capabilities.extensions` 声明扩展点: ```json { - "name": "SPN Domain Agent", + "name": "Dispatched Agent", "capabilities": { "streaming": true, "extensions": [ @@ -334,12 +331,12 @@ AgentCard 通过 `capabilities.extensions` 声明扩展点: }, { "uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1", - "description": "授权白名单", + "description": "授权操作", "required": false }, { "uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1", - "description": "结果通知订阅", + "description": "通知订阅", "required": false } ] @@ -383,7 +380,7 @@ nextRound 或返回新 Propose。 Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 `maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 +COMPLETED 确认,也不能判为任务成功。 ```java CompletableFuture sendAuthorization(String agentName, MessageContent content); @@ -391,9 +388,7 @@ NotificationSubscription openNotification(String agentName, MessageContent conte BiConsumer listener); ``` -宿主生成最终 Authorization-T/Notification-T 内容后调用上述接口;使用三类独立 transport/runtime/context。订阅监听器收到 -handle 与完整 ReceivedMessage,按业务收到抢通结果后关闭。handle.acknowledgement () 和 completion () 分别表示 ACK -和真实流退出,不用它们的成功作为工作流前提。 +宿主智能体生成最终 Authorization-T/Notification-T 内容后调用上述接口;使用三类独立 transport/runtime/context。订阅监听器收到 handle 与完整 ReceivedMessage,在宿主定义的终态事件上关闭。handle.acknowledgement() 和 completion() 分别表示 ACK 和真实流退出,两者都不是工作流前提。 ## 8. HTTPS 配置 @@ -439,30 +434,9 @@ dropped-capacity,禁用、截断、中断也有明确标记。 UTF-8 分片先 requestId 关联单次调用;工作流调用还带 executionId/logicalTaskId/attempt、 agent/contextId/channel,已知远端任务时附 remoteTaskId,均为本地日志字段,不污染协议 metadata。 -### 查看真实协商与协议日志 +### 验证协议与协商日志 -直接运行本地 SpringSpnDemo,默认 City1 缺参并协商、City2 参数完整直接诊断,无需添加 VM 参数。 Negotiation-T 扩展激活本身不强制协商;这是本地 -Demo 专门设置的场景,不是引擎默认行为。 要关闭本地缺参演示、让两城市都直接诊断,在 IDEA **VM options** 增加: - -```text --Da2at.samples.negotiation=false -``` - -默认仅移除 City1 的 Task-T 任务对象;启用状态下增加 `-Da2at.samples.negotiation.city=city2` 或 `both` -可演示 City2 或两城市同时缺参。宿主仍保留各城市的正确输入,原投诉上下文不变。预期链路是 `DEMO_NEGOTIATION` → -`INPUT_REQUIRED / PROPOSE` -→ 集成方 onNegotiation → `ACCEPT` → 两城市完成 → 一次汇总。 非内嵌 OMC 模式默认不注入缺参,显式设置 -`-Da2at.samples.negotiation=true` 也会被拒绝。 Demo 将开关传入当前 Spring 应用实例,不设置或修改 JVM 全局开关,不影响其他宿主。 -协议日志在控制台及以运行目录为基准的 `logs/spn-demo.log`。 本分支使用直连模式。 - -离线重复验证: - -```powershell -mvn -q -pl samples -am "-Dtest=SpringSpnDemoE2ETest" "-Dsurefire.failIfNoSpecifiedTests=false" test -``` - -每个测试类覆盖无 VM 参数的默认单城市协商、显式关闭、显式开启及两城市同时缺参。测试使用离线 LLM provider,但实际运行当前 SDK -的 模板、校验和 HTTP/SSE;不是现网 OMC、平台或真实模型语义的验证。 +使用一个被调度智能体返回“`INPUT_REQUIRED` + 有效 Negotiation-T Propose”的受控测试。预期序列为:请求 → Propose → `onNegotiation` → 最终 Send/Stop → 任务终态。`PROTOCOL` 日志应显示实际观测到的请求/响应边界与关联字段,且不泄露凭据。AgentCard 声明 Negotiation-T 能力本身不会强制发起协商。 ## 10. 事件回调 @@ -474,7 +448,7 @@ EventCallback callback = new EventCallback() { public void onEvent(String eventType, Map data) { switch (eventType) { case EventType.STEP_START -> System.out.println("步骤开始: " + data.get("step")); - case EventType.AGENT_STATUS_UPDATE -> System.out.println( + case EventType.TASK_STATUS_CHANGED -> System.out.println( data.get("agent") + " 状态: " + data.get("state")); case EventType.NEGOTIATION_REQUEST -> System.out.println( "协商请求来自 " + data.get("agent")); @@ -488,57 +462,47 @@ ExecutePsop.builder() // ... ``` -常用事件类型:`STEP_START`、`STEP_COMPLETE`、`AGENT_REQUEST`、`AGENT_RESPONSE`、`NEGOTIATION_REQUEST`、`NEGOTIATION_RESOLVED`、 -`COMPLETE`、`ERROR`。 +常用事件类型:`START`、`STEP_START`、`TASK_REQUEST`、`TASK_RESPONSE`、 +`TASK_STATUS_CHANGED`、`STEP_COMPLETE`、`NEGOTIATION_REQUEST`、 +`NEGOTIATION_RESOLVED`、`NEGOTIATION_FAILED`、`ROUTE_DECISION`、 +`WORKFLOW_COMPLETE`、`COMPLETE`、`ERROR`、`CLOSE`。 ## 11. 从编排中心加载工作流 ```java // 按意图搜索 List results = LoadPsop.search( - "https://127.0.0.1:5001", "SPN跨城专线故障诊断", 5, null, false); + "https://orchestration.example.com", "分析服务请求", 5, null, true); // 按 ID 加载完整工作流 Workflow workflow = LoadPsop.load( - "https://127.0.0.1:5001", results.get(0).getWorkflowId(), null, false); + "https://orchestration.example.com", results.get(0).getWorkflowId(), null, true); ``` ## 12. 自定义扩展 -直接构造最终 MessageContent (parts, metadata, extensions),由宿主管理扩展内容的生成/校验。无需注册引擎 handler 或 SDK +直接构造最终 MessageContent(parts, metadata, extensions),由宿主管理扩展内容的生成/校验。无需注册引擎 handler 或 SDK 实例。A2atMessages.from 是 A2A-T metadata 复制辅助;非 A2A-T 扩展也可直接提供 metadata 和激活 URI。引擎不会仅因 AgentCard 声明而自动生成内容。 -## 远端 OMC 联调与启动校验 +## 13. 外部端点与启动责任 -### 编排中心 HTTPS 联调 +### 13.1 编排中心 HTTPS 联调 -Demo 将 `A2A_SSL_VERIFY`(Spring 属性 `a2a.ssl-verify`)传给 LoadPsop.search/load。 -受控开发环境可设置 `A2A_SSL_VERIFY=false` 或启动参数 `--a2a.ssl-verify=false`: -执行引擎端不用下载或配置编排中心证书,搜索与加载连接均跳过证书链及主机名校验, -包括自签名、缺少 SAN 或 SAN 不匹配的开发证书,并打印 `[Registry] INSECURE_TLS` 警告。 +`LoadPsop.search/load` 显式接收 `sslVerify` 参数。生产环境保持 `true`。受控开发环境可传入 `false`,仅对当前连接跳过证书链和主机名校验,并打印 `[Registry] INSECURE_TLS` 警告;不修改 JVM 全局 TLS 默认值。 “免配本地证书”不表示 HTTPS 服务端不需要证书,也不能绕过要求客户端证书的 mTLS。 该非验证模式不能确认服务端身份,存在中间人风险,仅用于受控联调。 生产保持 `true`,给服务端配置匹配实际 URL 的 SAN,并让运行 JVM 信任其 CA; -引擎南向 `caCertsPath` 不会自动传给 LoadPsop。 +被调度智能体的 `caCertsPath` 不会自动传给 LoadPsop。 LoadPsop 不修改 JVM 全局 SSLContext、默认 SocketFactory 或 HostnameVerifier; -引擎南向 HTTP/JSON-RPC 的 `false` 仍仅跳过证书链校验,自定义转发适配器的 TLS 由其 SDK/平台管理。 - -### 下游 OMC 启动 +被调度智能体 HTTP/JSON-RPC 的 `false` 仍仅跳过证书链校验。 -在 IDEA 环境变量或本地 .env 中设置 `A2A_AGENT_CARD_LOCATIONS`,以逗号分隔外部 AgentCard 文件路径。 -运行 SpringSpnDemo 对接外部 OMC 时,显式设置 `A2A_EMBEDDED_OMC_ENABLED=false`, -或使用 Program arguments `--a2a.embedded-omc-enabled=false`;不会为这些下游节点创建本地 JDK Server。 -持续接收任务和保持订阅应运行 SpringWorkbenchApplication,而不是执行一次后退出的 Demo。 +### 13.2 被调度智能体端点 -启用内嵌 OMC 时,Demo 在启动任意 OMC 前校验两张实际加载的地市卡片,并使用其地址启动。 -非本机地址、缺失卡片或非法 URL 会提前失败;回环地址和本机网卡地址都可绑定, -不以“是否 127.0.0.1”判断远端。校验不探测 OMC,也不能判定本机某端口是否已有真实服务: -即使外部 OMC 恰好部署在本机,也必须显式关闭内嵌服务。 -真实地址与凭据应放在本地外部文件,不覆盖仓库示例,不提交。 +引擎只消费 AgentCard,不启动被调度智能体服务。宿主智能体负责 AgentCard 发现、端点可达性校验与开发测试资源。使用外部管理的被调度智能体时,不得在相同端点上启动本地测试服务。生产 AgentCard 和凭据应保存在仓库外部。 -## 远端错误响应 +## 14. 远端错误响应 响应头成功不代表任务成功。HTTP 200 的 SSE data 也可能返回顶层错误对象,例如: @@ -552,7 +516,7 @@ LoadPsop 不修改 JVM 全局 SSLContext、默认 SocketFactory 或 HostnameVeri 该识别以 SDK 实际交付的正文为限:真实 HTTP 非 2xx 可能先被 SDK/适配器作为 HTTP 或认证错误拒绝, 不保证可取得 problem 正文;没有该正文时仍走原有通用失败路径,不伪造 errorDetails。 错误不生成成功结果、不触发 onNegotiation,也不自动重新提交请求。400 应核对业务参数; -429 应检查 OMC 活跃任务配额及已有任务/订阅,再由业务决定是否重试。 +429 应检查被调度智能体的容量、活跃任务和订阅,再由业务决定是否重试。 只有合法 Negotiation-T Propose 才进入协商回调。 直接调用客户端时可用 `RemoteProblemException.findIn(error)` 检查 cause 链(没有则返回 null); @@ -563,17 +527,12 @@ LoadPsop 不修改 JVM 全局 SSLContext、默认 SocketFactory 或 HostnameVeri 预置授权、通知订阅的错误仍由其独立生命周期处理,不决定工作流执行结果。 协议 pretty 日志仅用于展示,不改写原始收到的错误对象。 -**当前双地市示例的失败流程**:两个诊断并行下发,一方失败后记录该方错误; -已经下发的另一方继续等待结果或超时。当前批次结束后返回 `success=false`, -不进入 `merge_analysis`,也不调用该节点的 onSelfTask/LLM。成功地市的结果留在 history 中, -不冒充整体诊断结论。此处没有自动重试、排队或“部分成功后汇总”策略; -通用引擎的 AnySuccess 节点仍按原有任一成功规则执行。 +对于 `ALL_SUCCESS` 步骤,一个任务失败不会追溯取消已下发的并行任务;引擎在另一任务返回或超时后报告 `success=false`。后续步骤不执行,成功的并行结果保留在 history,不伪造整体成功结果。引擎不自动重试、排队或部分成功汇总;`ANY_SUCCESS` 节点仍遵循声明的任一成功语义。 -集成方通过 TASK_RESPONSE 事件及时观察任务失败,或在 `onFinish(result, events)` +宿主智能体通过 TASK_RESPONSE 事件及时观察任务失败,或在 `onFinish(result, events)` 检查 `result.history`;onTask 是发送前准备消息的回调,不是失败重试回调。 引擎节点事件带 executionId;TASK_RESPONSE 和 history 还带逻辑 taskId。 -Demo 的北向 A2A 任务返回 FAILED,状态消息包含失败地市的 errorCode、error 和 errorDetails, -不产生成功汇总 artifact。整个演示退出时会关闭订阅;常驻宿主中的订阅由独立生命周期管理。 +宿主智能体决定如何向调用方暴露失败,但不得把失败执行转换为成功 artifact。常驻宿主以独立生命周期管理通知订阅。 **日志职责**:PROTOCOL 记录已观察到的请求/响应,按 requestId 关联并发报文; REMOTE_PROBLEM 记录通信层识别的远端拒绝,TASK_FAILED 记录执行 ID、节点、逻辑任务 ID、 @@ -582,14 +541,14 @@ contextId 与 executionId,供跨层定位。已识别的远端问题使用 WAR 未知异常保留 ERROR 堆栈;错误详情中的已知凭据及 Bearer/Basic 值脱敏。 日志开关、pretty 展示和观察回调不会改变任务结果。 -## 13. 你需要使用的接口一览 +## 15. 接口一览 | 接口/类 | 用途 | |--------------------------------------------------------|---------------------------------------------------------------| | `ExecutePsop.Builder` | 工作流执行入口 | | `ControlPoint` / `DefaultControlPoint` | 业务决策实现(onTask、onSelfTask、onRoute、onNegotiation 等) | | `WorkflowEngineClient` / `DefaultWorkflowEngineClient` | 工作流发送(sendMessage、认证、扩展) | -| `ExtensionSender` / `DefaultExtensionSender` | 前置授权请求与长连接通知订阅 | +| `ExtensionSender` / `DefaultExtensionSender` | 独立 Authorization-T 操作与 Notification-T 长连接订阅 | | `A2ATransport` | 共享通信层(A2A Java 客户端 runtime、认证、SSE 消费) | | `WorkflowEngineClientConfig` | 配置(SSL、认证、A2A-T、协商轮数、自定义 Handler) | | `AuthProvider` | 自定义认证 | diff --git "a/docs/zh/\344\270\232\345\212\241\346\265\201.md" "b/docs/zh/\344\270\232\345\212\241\346\265\201.md" deleted file mode 100644 index f11f9806..00000000 --- "a/docs/zh/\344\270\232\345\212\241\346\265\201.md" +++ /dev/null @@ -1,64 +0,0 @@ -# SPN 专线投诉诊断业务流 - -## 集成方业务边界(2026-08-27 校准) - -1. WAIMO 通过 Task-T 将一个投诉诊断任务下发给集成方。集成方使用 SDK 校验并提取 投诉参数,搜索/加载工作流。 -2. 工作流向两个不同地市 OMC 并行下发含地市诊断输入的 Task-T。两路结果都完成后, 集成方 join 节点仅执行一次汇总分析,把真实汇总结果返回 - WAIMO。 -3. 集成方到 OMC 通过直连通道下发 Task-T 诊断任务。 -4. Authorization-T 和 Notification-T 由集成方在各自业务时机独立触发,不是工作流 DAG 节点。Task、Authorization、Notification - 使用不同的 transport/runtime/context/通道。 -5. OMC 对诊断后的抢通方案做白名单精确匹配;命中后自动执行,并通过已建立的 Notification-T 长连接上报结果。集成方收到抢通结果后关闭对应订阅;ACK - 不是抢通结果。 - -### 表格一:专线业务投诉诊断接口(核心诊断) - -| 项目 | 详细说明 | -|:--------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **接口名称** | 专线业务投诉诊断接口 | -| **协议/模式** | A2A-T Task-T | -| **调用方向** | 集成方智能体 -> SPN Domain Agent(OMC) | -| **功能描述** | 集成方智能体下发诊断请求,OMC完成网络侧故障根因诊断并返回结果。 | -| **请求参数** | **必填:**
1. 任务对象:业务名称、业务标识或接入端口三者至少一个
2. 投诉分类(专线质差 / 专线中断)
3. OSS 侧事件流水号

**可选:**
4. 问题发生时间
5. 投诉详情 | -| **响应内容** | Task-T 只返回本地诊断结论:
1. 诊断结果
2. 故障根因/根因位置与描述
3. 修复建议。抢通方案和执行结果不拼入 Task-T 响应,统一走独立 Notification-T 订阅通道。 | - -### 表格二:专线业务投诉诊断协商接口(参数修正) - -| 项目 | 详细说明 | -|:---------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **接口名称** | 专线业务投诉诊断协商接口 | -| **协议/模式** | A2A-T Negotiation-T | -| **调用方向** | SPN Domain Agent(OMC) <--> 集成方智能体(双向) | -| **功能描述** | 当OMC发现任务参数有误或缺失时,反向发起协商,请求集成方智能体补全或修正信息。 | -| **协商请求(OMC→集成方)** | **触发条件:**
1. 必选参数缺失(如任务对象、投诉分类或 OSS 侧事件流水号)
2. 参数取值错误(如投诉分类不在枚举范围内)
3. 参数语义校验错误(如应为逻辑口却下发了物理口)

**请求内容:** 明确指出缺失/错误的参数及修正建议。 | -| **协商响应(集成方→OMC)** | 集成方智能体理解协商请求,返回强类型 Accept / Reject / Abort。Information Reject 必须对每个无法提供的请求信息项逐项说明原因,不能只返回一个汇总拒绝原因。 | -| **协商完成(OMC)** | OMC确认协商信息完成后,继续执行诊断任务,并最终返回诊断结果(同表格一响应内容)。 | - -### 表格三:业务抢通消息订阅与上报接口(结果通知) - -| 项目 | 详细说明 | -|:---------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **接口名称** | 业务抢通消息订阅接口 / 消息上报 | -| **协议/模式** | A2A-T Notification-T | -| **功能描述** | 用于集成方订阅抢通结果,以及OMC通过SSE流主动上报抢通结果消息。 | -| **订阅请求(集成方→OMC)** | **包含内容:**
1. 消息主题
2. 上报消息数据格式(需包括:投诉事件流水号、分段业务标识、抢通方案执行结果、抢通方案详情) | -| **订阅响应(OMC→集成方)** | **成功:** Notification-T metadata 返回“订阅结果:成功”,任务进入 `WORKING`。
**失败:** 返回 `FAILED` 及失败原因。 | -| **消息上报(OMC→集成方)** | 基于订阅时指定的数据格式,通过 **SSE** 先推送 `recovery-plan`(未启动),白名单命中并执行后再推送 `recovery-result`(已结束);结果、取消或宿主关闭才结束通道。 | - -### 表格四:专线业务抢通授权接口(自动执行授权) - -| 项目 | 详细说明 | -|:---------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **接口名称** | 专线业务抢通授权接口 | -| **协议/模式** | A2A-T Authorization-T | -| **功能描述** | 集成方向 OMC 新增、修改、删除或查询抢通授权,OMC 据有效白名单决定是否自动执行抢通。 | -| **授权请求(集成方→OMC)** | **包含内容:**
1. 操作类型:新增/修改/删除/查询授权策略
2. SDK 模板所定义的策略列表或查询条件 | -| **授权响应(OMC→集成方)** | artifact 的 Authorization-T metadata 返回“授权操作执行结果”;新增/查询成功时还返回策略标识和策略列表,删除必须精确匹配策略标识。失败返回 `FAILED` 且保留原策略。 | -| **后置操作(OMC内部)** | 授权成功后,当OMC在执行诊断后的业务抢通时,若匹配到已授权项,则**自动执行**业务抢通,并依据**表格三**的订阅通道上报抢通结果消息。 | - -## 引擎回调与依赖结果 - -上述场景使用同一套业务回调:onTask 返回最终 MessageContent,引擎封装信封并发送;onSelfTask 返回 TaskResult。 -多地市、多任务的输出按步骤分组保留于 WorkflowInput,contextFrom 选择是否聚合,当前 instruction 不拼历史。 宿主自行调用 A2A-T -SDK 生成协商回复,onNegotiation 返回 Send (content) 或 Stop;引擎只管理会话关联和等待。 -[业务回调集成契约](BUSINESS_CALLBACKS.md) 给出字段、最终内容与上下文选择的完整规则。 diff --git "a/docs/zh/\351\233\206\346\210\220\346\226\271\345\234\272\346\231\257\346\216\245\345\205\245\346\214\207\345\215\227.md" "b/docs/zh/\351\233\206\346\210\220\346\226\271\345\234\272\346\231\257\346\216\245\345\205\245\346\214\207\345\215\227.md" deleted file mode 100644 index 9e20cd08..00000000 --- "a/docs/zh/\351\233\206\346\210\220\346\226\271\345\234\272\346\231\257\346\216\245\345\205\245\346\214\207\345\215\227.md" +++ /dev/null @@ -1,798 +0,0 @@ -# 工作流执行引擎 SDK 集成方场景接入指南 - -> 交付基线:workflow engine `1.0.0`,A2A-T SDK -> `1.1.0`(commit `e42c83acce3e5ac2c245d36546ced0fa017b2b58`), -> A2A Java SDK `1.2.0.Final`。本执行引擎是首发版本, -> 只支持该当前 A2A-T API/资源基线,不提供旧 SDK 状态机接口或旧报文 fallback。 - -> 本文中的集成方是宿主智能体。代码示例保留 Workbench* 类名与 AgentCard 标识,便于对应真实源码;这些名称不构成 SDK 的业务绑定。 -> -> 面向集成方研发团队。本文档以 SPN 专线故障跨城协同诊断联创场景为例,从集成方视角说明接入 Workflow 执行引擎 SDK 的完整路径。 -> -> 业务基线:上游为 WAIMO;集成方接收 Task-T 后加载工作流,并行调度两地市 OMC, -> 两路完成后汇总一次并返回 WAIMO。Authorization-T 和 Notification-T 是集成方在 -> 独立时机触发的协议操作,不是工作流 DAG 节点。 - ---- - -## 1. 场景全景 - -### 1.1 业务拓扑 - -周边系统与 SDK 的可编辑架构图见 [架构设计](DESIGN.md)。 - -集成方是运行时编排宿主,执行引擎 SDK 嵌入集成方进程,不是独立部署的服务。编排中心负责 PSOP 的存储、检索和加载,注册中心负责 -AgentCard 的发布和查询;集成方负责在适当时机获取这些 输入并交给 SDK。它接收 WAIMO 通过 A2A-T 下发的原始诊断任务,并行编排下游多地市 -SPN Domain Agent(OMC)执行故障根因诊断,汇总结果后上报。 - -`SpringSpnDemo` 为便于离线运行,默认从 classpath 加载 AgentCard,并在编排中心不可用时使用本地 PSOP fallback。真实 OMC 联调可用 -`A2A_AGENT_CARD_LOCATIONS` 指定逗号或分号分隔的 -`classpath:`、`file:` URI 或绝对路径;显式文件缺失或内容无效时启动立即失败。这是样例特例, 生产集成应以注册中心和编排中心为数据源,并自行定义受控的失败策略。 - -### 1.2 四类 A2A-T 交互 - -SDK 在该场景中承载四类 A2A-T 交互,对应业务流文档的四个接口: - -| 序号 | A2A-T 扩展 | 业务含义 | 发起方 | 时机 | -|------|-----------------|-------------------|-------------------------------------------------|------------------------------| -| 1 | Task-T | 下发诊断任务 | 集成方 -> OMC | 工作流执行中 | -| 2 | Negotiation-T | 参数修正协商 | OMC <-> 集成方 | OMC 发现参数缺失时反向发起 | -| 3 | Authorization-T | 抢通授权 | 集成方 -> OMC | 工作流启动前,前置预定位 | -| 4 | Notification-T | 抢通结果订阅/上报 | 集成方 -> OMC (订阅) / OMC -> 集成方 (SSE 上报) | 工作流启动前订阅,执行中上报 | - -序号 1、2 在工作流执行过程中发生,由 SDK 的 `WorkflowEngineClient` 承载。序号 3、4 是流程外的独立操作,Demo 只是在工作流启动前尝试,由 -SDK 的 `ExtensionSender` 承载;这个时间顺序不构成因果依赖,授权或订阅失败均不得阻止后续 Task-T 工作流。各路径使用同一类底层传输组件 -`A2ATransport`,但 runtime、context 和生命周期互相隔离:工作流发送使用任务级 transport,Notification-T 订阅使用集成方级 -transport,避免工作流结束时关闭长连接。 - -### 1.3 SDK 自动处理 vs 集成方实现 - -| 引擎承担 | 集成方承担 | -|---------------------------------|---------------------------------------| -| DAG、并行、任务关联、信封与传输 | onTask 返回最终 MessageContent | -| 上游来源选择、完整响应组装 | 根据业务需要映射上下游输入 | -| 协商关联、去重、等待 | SDK 生成/校验回复,返回 Send 或 Stop | -| 认证机制与 Header 注入 | 凭据来源或自定义 AuthProvider | -| 独立授权发送与订阅生命周期 | SDK 生成授权/订阅内容、处理业务结果 | - -A2A-T client/server、模板、schema、LLM 初始化属于宿主; workflow-engine 仅依赖 a2a-t-core,不管理内容生成。 - -## 2. 集成全景图 - -集成方集成 SDK 共需完成六步。先总后分,每步后续均有详细说明和代码示例。 - -```mermaid -graph LR - S1["步骤 1
引入 Maven 依赖"] - S2["步骤 2
准备配置文件
AgentCard + 认证 + .env"] - S3["步骤 3
实现 AgentExecutor
A2A 服务端入口"] - S4["步骤 4
实现 ControlPoint
工作流决策回调"] - S5["步骤 5
组装运行管线
发现输入 -> 执行工作流 / 独立扩展"] - - S1 --> S2 --> S3 --> S4 --> S5 -``` - -职责分布: - -| 组件 | 集成方实现 | SDK 提供 | 说明 | -|-------------------------|---------------|-----------------|------------------------------------------------------------------| -| `AgentExecutor` | 是 | 否 | 集成方的 A2A 服务端入口,接收 上层系统 下发的任务 | -| `ControlPoint` | 是 (4 个方法) | 接口 + 默认实现 | 工作流决策: 任务下发、自处理、路由、协商 | -| `AuthProvider` | 是 (可选) | 接口 | 自定义认证头注入 (企业 SSO 等),与 credentials.json 二选一或组合 | -| `WorkbenchOrchestrator` | 是 | 否 | 组装编排管线的业务类 | -| `WorkflowEngineClient` | 否 | 是 | 工作流发送门面 (Task-T + 协商循环) | -| `ExtensionSender` | 否 | 是 | 前置预定位门面 (授权 + 订阅) | -| `A2ATransport` | 否 | 是 | 共享传输层 (HTTP + 认证 + SSE) | -| `ExecutePsop` | 否 | 是 | 高层运行器 (生命周期 + 事件 + 持久化) | - ---- - -## 3. 逐步集成 - -### 步骤 1: 引入 Maven 依赖 - -集成方项目 `pom.xml` 中添加: - -```xml - - dev.openan.workflow.sdk - workflow-engine - 1.0.0 - -``` - -如果集成方基于 Spring Boot 且需要 SDK 自动装配 A2A 服务端 (AgentCard 加载、REST 控制器、事件总线): - -```xml - - dev.openan.workflow.sdk - spring-boot-starter - 1.0.0 - -``` - -Spring Boot starter 会自动注册 `AgentCard`、`RequestHandler`、`RestHandler`、`A2AController` 等 Bean。集成方只需提供一个 -`AgentExecutor` 实现类标注 `@Component` 即可。 - -### 步骤 2: 准备配置文件 - -集成需要三类配置文件。 - -#### 2.1 AgentCard (集成方自身的) - -集成方作为 A2A 服务端,需要声明自己的 AgentCard。该文件告知 上层故障系统如何调用集成方: - -```json -{ - "name": "Transport Workbench Agent", - "description": "传输工作台智能体: 接收上层诊断任务,编排多地市 OMC 执行诊断并汇总", - "version": "1.0.0", - "capabilities": { - "streaming": true, - "extensions": [ - {"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Task-T/v1", "required": false}, - {"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/NEGOTIATION-T", "required": false}, - {"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Authorization-T/v1", "required": false}, - {"uri": "https://projects.tmforum.org/a2aproject/telecommunication/extensions/Notification-T/v1", "required": false} - ] - }, - "defaultInputModes": ["text/plain"], - "defaultOutputModes": ["text/plain"], - "skills": [ - { - "id": "cross-city-fault-diagnosis", - "name": "跨城故障协同诊断", - "description": "协同多地市 OMC 执行专线故障诊断并汇总结果", - "tags": ["SPN", "fault-diagnosis"] - } - ], - "supportedInterfaces": [ - {"protocolBinding": "HTTP+JSON", "url": "https://工作台地址/a2a/json"} - ] -} -``` - -下游 SPN Domain Agent 的 AgentCard 由厂商提供或从注册中心获取。集成方只需加载它们用于 SDK 通信。关键信息是 `name` (SDK -中用此名称寻址) 和 `supportedInterfaces.url` (通信地址)。 - -下游 AgentCard 的安全字段语义: - -- `securitySchemes` 表示智能体支持的认证方式,不等于强制要求 -- `securityRequirements` 表示当前对接必须满足的认证方式;缺失、`null` 或 `[]` 都表示不启用内置凭证认证 -- 如果集成方通过 `AuthProvider` 自行获取 OMC Token,推荐配置 `"securityRequirements": []`,`securitySchemes` 可以保留为能力声明 -- 如果希望内置 credentials 认证生效,`securityRequirements` 必须非空,并引用 `securitySchemes` 中声明的方案名 - -AuthProvider-only 场景的下游 AgentCard 安全字段示例: - -```json -{ - "securitySchemes": { - "bearerAuth": { - "httpAuthSecurityScheme": { - "scheme": "Bearer" - } - } - }, - "securityRequirements": [] -} -``` - -#### 2.2 认证配置 - -SDK 提供两层认证机制,可单独使用或组合使用: - -| 认证方式 | 适用场景 | 配置方式 | -|---------------------|---------------------------------------------------------------------------------|----------------------------| -| Credentials 认证 | Agent 提供登录接口并使用 Bearer 或自定义认证头 | 提供 credentials.json 文件 | -| 自定义 AuthProvider | 集成方自管 Token、企业 SSO、非标准认证头,或经自定义网关/协议转发获取 OMC Token | 实现 `AuthProvider` 接口 | - -**合并规则:** 两种认证来源分别生成 Header 后合并。`AuthProvider` 可以作为唯一认证来源;如果两者对同名 Header 生成不同值,引擎会抛出 -`SecurityException`。 - -##### 方式一: Credentials 认证 (credentials.json) - -SPN Domain Agent (OMC) 使用 Bearer 认证。SDK 的 `AgentAuthManager` -自动调用登录接口获取令牌并缓存刷新,集成方只需提供凭证文件: - -```json -{ - "SPN Domain Agent City1": { - "bearerAuth": { - "login_url": "https://omc-city1地址/rest/plat/smapp/v1/oauth/token", - "method": "PUT", - "request_fields": { - "grantType": "password", - "userName": "admin", - "value": "enc:加密后的密码", - "ipaddr": "*" - }, - "token_field": "accessSession", - "token_ttl": 3600 - } - }, - "SPN Domain Agent City2": { - "bearerAuth": { - "login_url": "https://omc-city2地址/rest/plat/smapp/v1/oauth/token", - "method": "PUT", - "request_fields": { - "grantType": "password", - "userName": "admin", - "value": "enc:加密后的密码", - "ipaddr": "*" - }, - "token_field": "accessSession", - "token_ttl": 3600 - } - } -} -``` - -键名必须与 AgentCard 的 `name` 字段一致。SDK 依据此键名匹配 AgentCard 与凭证。密码字段 `value` 使用 `enc:` 前缀标识加密内容,SDK -的 `CredentialCrypto` 自动解密 (密钥通过 `A2AT_CRED_KEY` 环境变量注入,32 字节 hex)。 - -**密码加密方法:** - -加密格式为 `enc::`,算法为 AES-256-GCM。SDK 提供两种加密方式: - -**方式一: 命令行工具 (推荐)** - -```bash -# 1. 生成 32 字节密钥 (仅首次,妥善保存) -openssl rand -hex 32 -# 输出示例: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 - -# 2. 设置密钥环境变量 -set A2AT_CRED_KEY=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 - -# 3. 加密密码 (输出 enc:... 格式,直接复制到 credentials.json) -java -cp workflow-engine.jar dev.openan.workflow.engine.client.CredentialCrypto "Admin@123" -# 输出: enc:xxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy - -# 也可以将密钥作为第二个参数传入 -java -cp workflow-engine.jar dev.openan.workflow.engine.client.CredentialCrypto "Admin@123" a1b2c3d4... -``` - -**方式二: 代码调用** - -```java -// 设置密钥后调用 -System.setProperty("A2AT_CRED_KEY", "a1b2c3d4e5f6..."); -String encrypted = CredentialCrypto.encrypt("Admin@123"); -// encrypted = "enc:xxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" -``` - -将输出的 `enc:...` 值直接填入 credentials.json 的 `value` 字段: - -```json -{ - "request_fields": { - "value": "enc:xxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" - } -} -``` - -运行时 SDK 从 `A2AT_CRED_KEY` 环境变量读取密钥自动解密。密钥来源优先级:显式 credentialEncryptionKey > 操作系统 -A2AT_CRED_KEY > 同名 JVM 属性。引擎不自动读取宿主 LLM .env。 - -凭证配置支持的完整字段: - -| 字段 | 类型 | 默认值 | 说明 | -|----------------------|--------|--------------------|-------------------------------------------------------------------| -| `login_url` | String | 必填 | 登录接口地址 | -| `method` | String | `POST` | HTTP 方法 | -| `content_type` | String | `application/json` | 请求 Content-Type | -| `request_fields` | Map | - | 请求体字段 (替代 legacy 的 username/password) | -| `token_field` | String | `accessSession` | 从响应 JSON 提取令牌的路径 (支持点号嵌套,如 `data.access_token`) | -| `token_ttl` | int | 3600 | 令牌缓存有效期 (秒),到期前 60 秒自动刷新 | -| `auth_header` | String | `Authorization` | 自定义认证头名称 (设置后引擎改用该 Header 注入凭证) | -| `auth_header_prefix` | String | `Bearer ` | 自定义认证头前缀 (与 `auth_header` 配合使用) | - -##### 方式二: 自定义 AuthProvider - -当认证机制不属于标准 Bearer/API Key,或令牌来自外部身份提供商 (如企业 SSO) 时,实现 `AuthProvider` 接口: - -```java -public interface AuthProvider { - /** - * 为发送消息注入认证头。 - * - * @param agentName 目标 Agent 名称 (匹配 AgentCard.name) - * @param agentCard Agent 卡片 (securitySchemes 可能为空) - * @param headers 可变的请求头 Map,直接写入认证信息 - */ - void applyAuth(String agentName, AgentCard agentCard, Map headers); -} -``` - -通过 `WorkflowEngineClientConfig` 注册: - -```java -WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder() - .authProvider((agentName, agentCard, headers) -> { - // 从企业 SSO 获取令牌 - String token = ssoClient.getToken(agentName); - headers.put("Authorization", "Bearer " + token); - }) - .sslVerify(sslVerify) - .build(); -``` - -`AuthProvider` 在 **每次消息发送时**被调用,无论 AgentCard 是否声明了 `securitySchemes`。典型用途: - -- AgentCard 无 `securitySchemes` 但服务端仍要求认证 -- 认证机制非 A2A 标准类型 (Bearer、API Key、OAuth2) -- 令牌来自企业统一身份认证平台 (如企业 SSO) -- 需要注入非标准认证头 (如 `X-Custom-Token`、`X-Request-Signature`) -- 消息经自定义 `A2AJavaClientRuntime` 协议转发时,认证 Header 已由 `AuthProvider` 注入 `callContext`,转发实现原样复制即可 - -如果 Authorization-T / Notification-T 前置消息和工作流 Task-T 消息都需要认证,请把同一个 `AuthProvider` 配置到 -`extensionTransport` 和 `taskTransport` 的 `WorkflowEngineClientConfig` 中。 - -##### 两种方式组合使用 - -如果部分 Agent 使用 credentials 认证、部分使用自定义认证,或需要在 credentials 基础上追加额外头: - -```java -WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder() - .credentialsConfigPath(credentialsPath) // credentials 认证 - .authProvider((agentName, agentCard, headers) -> { - // 追加企业级追踪头 - headers.put("X-Trace-Id", TraceContext.current().getTraceId()); - }) - .build(); -``` - -`AuthProvider` 与 credentials 认证分别生成 Header 后再合并。`AuthProvider` 可以作为唯一认证来源,即使 AgentCard 的 -`securityRequirements` 非空且未配置 credentials。不同名称的 Header 可同时使用;如果两者对同名 Header(例如 `Authorization` -)给出不同值,引擎会立即抛出 `SecurityException`,避免认证信息被顺序覆盖。建议每个认证 Header 只配置一个来源。 - -#### 2.3 A2A-T SDK 环境 (.env) - -A2A-T SDK 的 LLM 配置 (用于 Task-T 提示词生成、场景识别等)。最小配置: - -```ini -A2AT_LLM_PROVIDER=openai -A2AT_LLM_MODEL=deepseek-chat -A2AT_LLM_API_KEY=你的API密钥 -A2AT_LLM_BASE_URL=https://api.deepseek.com -A2AT_LANGUAGE=zh-CN -``` - -集成方业务代码通过此配置初始化 A2A-T SDK,生成和校验协议内容。执行引擎不读取 LLM 配置,也不生成提示词。示例在配置缺失或生成失败时明确报错,不把原始输入降级为 Task-T 正文。 - -### 步骤 3: 实现 AgentExecutor - -集成方实现 A2A Java AgentExecutor.execute (RequestContext, AgentEmitter) 接收 WAIMO, 并在自己的入口用 -A2ATServer.validateTaskPromptAndDataFilling 校验 Task-T。 metadata 中的 Task-T 正文与 templateUri 是内容入口,不用 parts -长短决定读取哪个正文。 然后搜索工作流、获取 AgentCards、调用执行引擎,并通过 A2A emitter 返回 artifact/终态。 - -可编译的完整实现见 samples 的 SpringWorkbenchExecutor、WorkbenchTaskInputParser、 WorkbenchOrchestrator;Spring starter -只提供协议入口与生命周期,不替宿主决定业务或模板。 入口取消应停止对应本地执行并清理宿主资源;远端已提交任务需按业务策略显式取消。 -不要把错误或空结果伪装成成功诊断。 - -### 步骤 4: 实现 ControlPoint - -```java -interface ControlPoint { - CompletableFuture onTask(TaskRequest request); - CompletableFuture onSelfTask(TaskRequest request); - CompletableFuture onRoute(RouteRequest request); - CompletableFuture onNegotiation(NegotiationRequest request); -} -``` - -onTask 返回最终 parts/metadata/extensions,引擎封装发送,不再生成或改写内容。 onSelfTask 返回本地 TaskResult;onRoute -选择允许的候选;onNegotiation 返回 Send 或 Stop。 未实现的回调明确失败,不回显成功、不选首分支、不自动同意。 -字段与完整示例见 [业务回调集成契约](BUSINESS_CALLBACKS.md)。 - -```java -ControlPoint callbacks = ControlPoint.builder() - .onTask(request -> CompletableFuture.completedFuture( - MessageContent.text(request.getInstruction()))) - .onSelfTask(request -> CompletableFuture.completedFuture( - TaskResult.success(List.of(Map.of( - "sourceResults", request.getWorkflowInput().upstreamResults()))))) - .onRoute(request -> CompletableFuture.failedFuture( - new IllegalStateException("Supply a routing policy for " + request.stepName()))) - .onNegotiation(request -> CompletableFuture.completedFuture( - new NegotiationReply.Stop("manual.required", "Manual confirmation required"))) - .build(); -``` - -生产 Task-T 生成及协商示例见 WorkbenchControlPoint / examples.negotiation.NegotiationStrategy;上述仅是普通 A2A 内容示例。 - -### 步骤 5: 组装编排管线 - -```java -WorkflowEngineClientConfig config = WorkflowEngineClientConfig.builder() - .sslVerify(true) - .credentialsConfigPath(credentialsPath) // 内置认证;自定义认证时改为 authProvider 并不配置它 - .credentialEncryptionKey(credentialKey) // 宿主密钥;明文配置可不提供 - .maxNegotiationExchanges(3) - .build(); -try (A2ATransport transport = new A2ATransport(cards, runtime, config)) { - WorkflowEngineClient client = new DefaultWorkflowEngineClient(transport); - ExecutionResult result = ExecutePsop.builder() - .psop(workflow).agentCards(cards).controlPoint(callbacks) - .engineClient(client).runtimeIntent(intent).execute().join(); -} -``` - -workflow、cards、callbacks、intent 由宿主取得或构造。 runtime 为 null 时使用默认直连实现;自定义 runtime 只替换传输,不替换业务回调。 -上面 transport 仅属于这次工作流;授权和通知使用另外的实例。 宿主自己的 A2ATClient 可在回调中复用,但要确保业务状态按 -execution/task 隔离。 - -#### 5.1 前置预定位详解 - -```java -// authorizationSender 与 notificationSender 各自使用独立 transport/runtime。 -MessageContent authContent = A2atMessages.from( - sdk.generateAuthPromptFromDataWithSchema(authData, authSchema, - StandardTemplates.AUTHORIZATION_POLICY_MANAGEMENT.uri()), - List.of(new TextPart("新增动网操作授权"))); -authorizationSender.sendAuthorization(agentName, authContent) - .whenComplete((result, error) -> recordIndependentAuthorization(result, error)); - -MessageContent notificationContent = A2atMessages.from( - sdk.generateNotificationPromptFromDataWithSchema(notifData, notifSchema, - StandardTemplates.SERVICE_RECOVERY.uri()), - List.of(new TextPart("订阅业务抢通事件"))); -NotificationSubscription subscription = notificationSender.openNotification( - agentName, notificationContent, (handle, received) -> { - if (received.artifacts().stream().anyMatch(a -> "recovery-result".equals(a.name()))) { - handle.close(); - } - }); -// 将 handle 加入宿主订阅集合;ACK/失败都仅影响这个独立操作。 -// 监听器使用传入的 handle,不依赖此时外部 subscription 变量是否赋值。 -subscription.acknowledgement().whenComplete((ack, error) -> recordIndependentSubscription(ack, error)); -``` - -sdk、业务数据、schema、recordIndependent* 均由宿主提供,不是引擎隐式服务。 内容生成失败也只记录本次独立操作失败,不能阻止任务 -DAG。 close () 幂等请求关闭;completion () 等待真实流退出。工作流完成不能关闭独立订阅。 实际生命周期与异常处理见 samples 的 -ExtensionPrePositioner / WorkbenchExtensionLifecycle。 - -#### 5.2 PSOP 工作流搜索与加载 - -工作流 (PSOP) 存储在编排中心。SDK 提供搜索和加载方法: - -```java -// 语义搜索: 根据原始意图匹配最相关的工作流 -List results = LoadPsop.search(orchUrl, messageText, 3, null, sslVerify); -String psopId = results.get(0).getWorkflowId(); - -// 加载完整工作流定义 -Workflow workflow = LoadPsop.load(orchUrl, psopId, null, sslVerify); -``` - -生产环境应明确编排中心不可用时的业务失败策略,不能默认执行可能过期的本地流程。 -`SpringSpnDemo` 为离线演示提供本地 PSOP fallback;接入方只有在完成版本一致性、审计和变更治理后, 才应自行设计受控的本地兜底。 - -#### 5.3 EventCallback -- 事件追踪 - -`ExecutePsop` 在执行过程中产出全量事件。集成方可实现 `EventCallback` 追踪执行进度、记录日志、向前端推送: - -```java -private EventCallback createLogCallback() { - return new EventCallback() { - @Override - public void onEvent(String type, Map data) { - switch (type) { - case EventType.START -> log.info("[开始] 工作流: {}", data.get("workflow")); - case EventType.STEP_START -> log.info("[步骤开始] {}", data.get("step")); - case EventType.AGENT_REQUEST -> log.info("[发送] -> {}", data.get("agent")); - case EventType.AGENT_RESPONSE -> log.info("[接收] <- {}", data.get("agent")); - case EventType.NEGOTIATION_REQUEST -> - log.info("[协商请求] agent={}, 第{}次交互", data.get("agent"), data.get("exchange")); - case EventType.NEGOTIATION_RESOLVED -> - log.info("[协商完成] agent={}", data.get("agent")); - // 以下事件类型为预留,当前 SDK 版本不会触发 - // (独立操作不进入工作流事件流) - case EventType.AUTHORIZATION_REQUEST -> - log.info("[授权请求] agent={}", data.get("agent")); - case EventType.NOTIFICATION -> - log.info("[通知] agent={}", data.get("agent")); - case EventType.ROUTE_DECISION -> - log.info("[路由] {} -> {}", data.get("step"), data.get("next")); - case EventType.STEP_COMPLETE -> log.info("[步骤完成] {}", data.get("step")); - case EventType.COMPLETE -> log.info("[完成]"); - case EventType.ERROR -> log.error("[错误] {}", data.get("error")); - case EventType.CLOSE -> log.info("[关闭]"); - } - } - }; -} -``` - -事件类型完整列表见 SDK 的 `EventType` 常量类。关键事件: `start` -> `step_start` -> `agent_request` / `agent_response` (含 -`negotiation_*`) -> `step_complete` -> `complete` / `error` -> `close`。 - -> **注意:** `authorization_request` 和 `notification` 事件类型在当前 SDK 版本中不会被触发 (独立操作不进入工作流事件流) -> 。这些事件类型为未来扩展预留。独立授权通过 SendMessageResult,订阅通过 handle 的 ACK 和监听器取得响应,不经过事件流。 - ---- - -## 4. 完整调用时序 - -```mermaid -sequenceDiagram - participant H as Host callbacks + A2A-T client - participant E as Workflow engine - participant A as Remote agent - E->>H: onTask(TaskRequest + upstream window) - H->>H: Generate/validate final content - H-->>E: MessageContent - E->>A: A2A envelope + unchanged content - opt INPUT_REQUIRED with valid Propose - A-->>E: Task status + Negotiation-T Propose - E->>H: onNegotiation(originalSubmission, received, history) - H->>H: Validate proposal and generate reply - H-->>E: Send(MessageContent) or local Stop - E->>A: Same task/context with final reply content - end - A-->>E: Task result / artifacts - E->>H: onSelfTask(selected complete upstream results) - H-->>E: TaskResult -``` - -```mermaid -sequenceDiagram - participant H as Host + A2A-T client - participant ES as Independent ExtensionSender - participant A as OMC - H->>H: Generate final authorization content - H->>ES: sendAuthorization(agent, content) - ES->>A: One-shot authorization - A-->>H: Independent result, never gates workflow - H->>H: Generate final subscription content - H->>ES: openNotification(agent, content, listener) - ES-->>H: Registered handle - ES->>A: Independent long-lived stream - A-->>H: ACK via acknowledgement() - A-->>H: listener(handle, ReceivedMessage) - H->>ES: handle.close() on recovery/cancel/shutdown - ES-->>H: completion() after stream exits -``` - -只有远端 `INPUT_REQUIRED` 携带有效 Negotiation-T Propose 才进入 `onNegotiation`。 终态不会重启协商,普通 INPUT_REQUIRED -明确报告不支持的交互。 宿主自行校验、理解 Propose,并用自己的 A2A-T client 生成最终 Accept/Reject/Abort。 通过 -`A2atMessages.contextOf(request.received())` 取得收到的上下文; 结束回复保持相同 id、round、maxRounds,最后允许的一轮仍可回答,不自行 -nextRound 或返回新 Propose。 - -返回 `new NegotiationReply.Send(content)` 发送最终内容; 返回 `new NegotiationReply.Stop(code, reason)` 只在本地停止,不生成 -Abort。 同一任务/会话/轮次的重复等待事件不会重复回调、重复提交;未变化状态通过 getTask 观察。 -`maxNegotiationExchanges` 默认 3,是独立于 SDK context.maxRounds 的本地交互资源预算。 超时、预算耗尽、回调缺失均明确失败,不默认 -Accept,也不自动生成 Abort。 Accept/Reject 的 SUBMITTED/WORKING ACK 仍需等待任务结果,不重发原命令。 业务发送 Abort 后,即使远端用 -COMPLETED 确认,也不能判为诊断成功。 - -### 查看真实协商与协议日志 - -直接运行本地 SpringSpnDemo,默认 City1 缺参并协商、City2 参数完整直接诊断,无需添加 VM 参数。 Negotiation-T 扩展激活本身不强制协商;这是本地 -Demo 专门设置的场景,不是引擎默认行为。 要关闭本地缺参演示、让两城市都直接诊断,在 IDEA **VM options** 增加: - -```text --Da2at.samples.negotiation=false -``` - -默认仅移除 City1 的 Task-T 任务对象;启用状态下增加 `-Da2at.samples.negotiation.city=city2` 或 `both` -可演示 City2 或两城市同时缺参。宿主仍保留各城市的正确输入,原投诉上下文不变。预期链路是 `DEMO_NEGOTIATION` → -`INPUT_REQUIRED / PROPOSE` -→ 集成方 onNegotiation → `ACCEPT` → 两城市完成 → 一次汇总。 非内嵌 OMC 模式默认不注入缺参,显式设置 -`-Da2at.samples.negotiation=true` 也会被拒绝。 Demo 将开关传入当前 Spring 应用实例,不设置或修改 JVM 全局开关,不影响其他宿主。 -协议日志在控制台及以运行目录为基准的 `logs/spn-demo.log`。 本分支使用直连模式。 - -离线重复验证: - -```powershell -mvn -q -pl samples -am "-Dtest=SpringSpnDemoE2ETest" "-Dsurefire.failIfNoSpecifiedTests=false" test -``` - -每个测试类覆盖无 VM 参数的默认单城市协商、显式关闭、显式开启及两城市同时缺参。测试使用离线 LLM provider,但实际运行当前 SDK -的 模板、校验和 HTTP/SSE;不是现网 OMC、平台或真实模型语义的验证。 - -### Demo 端到端 A2A-T SDK 调用清单 - -`SpringSpnDemo` 完整业务链路(两地市并行诊断、City1 缺参协商、City2 直接诊断)按业务时序对 A2A-T SDK 的全部调用如下。授权与订阅是独立于工作流 -DAG 的操作,使用各自的独立通道 (授权为一次性通道,订阅为长连接通道),与 Task-T 报文无关;Demo 的调用时机是在工作流任务 下发 -**之前**先行完成授权与订阅,再启动两地市诊断。样例覆盖全部 8 个 validate 接口和全部 7 个 fromData 生成接口,均为真实调用;fromText -生成系列未在样例中使用(见文末说明)。 - -| # | 业务时机 | 调用方(角色) | A2A-T SDK 接口 | 样例实现 | -|----|-------------------------------------------------------------------------------------------|------------------|------------------------------------------------------------------------------|---------------------------------| -| 1 | WAIMO 下发投诉任务,集成方入口校验 Task-T 并提取任务参数 | 集成方(服务端) | `A2ATServer.validateTaskPromptAndDataFilling` | `WorkbenchTaskInputParser` | -| 2 | 集成方生成抢通白名单授权请求,经独立一次性通道发送 OMC(任务下发前) | 集成方(客户端) | `A2ATClient.generateAuthPromptFromDataWithSchema` | `ExtensionPrePositioner` | -| 3 | OMC 收到授权报文,校验并按白名单执行,返回 ACK | OMC(服务端) | `A2ATServer.validateAuthPromptAndDataFilling` | `PrePositionedExtensionHandler` | -| 4 | 集成方生成业务抢通事件订阅请求,经独立长连接通道向 OMC 订阅(任务下发前) | 集成方(客户端) | `A2ATClient.generateNotificationPromptFromDataWithSchema` | `ExtensionPrePositioner` | -| 5 | OMC 校验订阅报文,建立 SSE 上报;后续抢通结果经该长连接推回集成方 | OMC(服务端) | `A2ATServer.validateNotificationPromptAndDataFilling` | `PrePositionedExtensionHandler` | -| 6 | 集成方按地市生成并行 Task-T;不依赖独立授权或订阅成功 | 集成方(客户端) | `A2ATClient.generateTaskPromptFromDataWithSchema` | `SpringSpnDemo` | -| 7 | OMC 校验 Task-T,提取任务对象/上下文;参数完整则直接诊断(City2) | OMC(服务端) | `A2ATServer.validateTaskPromptAndDataFilling` | `NegotiationBaseAgentExecutor` | -| 8 | OMC 发现必选参数缺失,生成协商 Propose,随 INPUT_REQUIRED 返回(City1) | OMC(服务端) | `A2ATServer.generateNegotiationProposePromptFromData` | `NegotiationBaseAgentExecutor` | -| 9 | 集成方 `onNegotiation` 回调:校验入站 Propose,提取所需补充字段 | 集成方(客户端) | `A2ATClient.validateProposePromptAndDataFilling` | `NegotiationStrategy` | -| 10 | 集成方从当前地市业务输入补参,生成协商结尾报文(Accept;拒绝/终止对应 Reject/Abort 变体) | 集成方(客户端) | `A2ATClient.generateNegotiationAcceptPromptFromData`(及 Reject/Abort 变体) | `NegotiationStrategy` | -| 11 | OMC 校验集成方回传的协商结尾报文,提取补参后重新执行诊断 | OMC(客户端) | `A2ATClient.validateAcceptPromptAndDataFilling`(及 Reject/Abort 变体) | `NegotiationBaseAgentExecutor` | - -两个组装辅助贯穿上述调用(属执行引擎的通用工具,非 SDK 接口): - -- `A2atMessages.from(MetadataContent, parts)`:把 SDK 生成的 `MetadataContent`(模板 URI、扩展 URI、 协商上下文 - metadata)封装为发送用的 `MessageContent`,不生成、不解释业务内容; -- `A2atMessages.contextOf(received)`:从收到的协商报文中取回 `NegotiationContext`,协商结尾回复 保持相同 - id/round/maxRounds(步骤 6)。 - -说明: - -- 全部生成都走 **fromData(结构化输入、确定性渲染)**;`generateTaskPromptFromText` 等 fromText 系列 (自然语言入口,含一步 - LLM 抽取)接口在样例中未使用,接口契约见 - [A2A-T SDK 依赖说明](A2AT-SDK-DEPENDENCY.md)。 -- 协商由 **OMC 侧触发**(缺参时返回 INPUT_REQUIRED + Propose),集成方无法选择哪个地市协商; 样例通过删除 City1 - 的任务对象字段构造该输入条件,两城市的补参互相隔离。 -- 输入内容(任务参数、补参值、授权策略、订阅条件)全部来自样例构造的业务数据(`SpnCasePrompts`), SDK 接口输出不做任何打桩;运行 - `SpringSpnDemo` 使用真实 LLM 配置。 - -## 5. 集成方需交付物清单 - -| 交付物 | 类型 | 说明 | -|--------------------------------------------|---------|--------------------------------------------------------------------------| -| `TransportWorkbenchExecutor` | Java 类 | 实现 `AgentExecutor`,A2A 服务端入口 | -| `WorkbenchControlPoint` | Java 类 | 继承 `DefaultControlPoint`,实现 onTask/onSelfTask/onRoute/onNegotiation | -| `WorkbenchOrchestrator` | Java 类 | 组装编排管线 (加载卡片 -> 检索工作流 -> 执行) | -| `WorkbenchAuthProvider` (可选) | Java 类 | 实现 `AuthProvider`,自定义认证逻辑 (如企业 SSO) | -| `agentcard/transport_workbench_agent.json` | JSON | 集成方自身的 AgentCard | -| `spn_agent_credentials.json` (可选) | JSON | 下游 OMC 的认证凭证 (使用 Credentials 认证时需要) | -| `.env` | INI | A2A-T SDK 的 LLM 配置 | -| `application.yml` (Spring Boot) | YAML | `a2at.server.agent-card` 等配置项 | - ---- - -## 6. 生产环境注意事项 - -### 6.1 TLS 与证书 - -生产环境 OMC 通常使用自签名证书。`WorkflowEngineClientConfig` 中: - -- `sslVerify(true)`: 校验证书 (需配置 CA 信任库 `caCertsPath`) -- `sslVerify(false)`: 仅跳过证书链校验,主机名校验仍启用;只用于受控的本地诊断 (HTTP/JSON-RPC 会继续加载 mTLS 客户端证书;默认 - gRPC 使用 plaintext,不能同时配置 mTLS/CRL) - -```java -WorkflowEngineClientConfig.builder() - .sslVerify(true) - .caCertsPath("/path/to/ca-certs.pem") - // OMC 要求双向 TLS 时配置: - .clientCertPath("/path/to/client-cert.pem") - .clientKeyPath("/path/to/client-key.pem") - .credentialsConfigPath(credentialsPath) - .build() -``` - -### 6.2 凭证加密与自定义认证 - -**Credentials 认证:** `spn_agent_credentials.json` 中的密码字段使用 `enc:` 前缀标识加密内容。SDK 的 `CredentialCrypto` 使用 -AES-GCM 对称加密自动解密。加密密钥通过环境变量 `A2AT_CRED_KEY` 注入 (32 字节 hex)。生产环境不应明文存储密码。 - -**自定义 AuthProvider:** 如果集成方已有统一的认证体系 (如企业 SSO、OAuth2 网关),可直接实现 `AuthProvider` 接口,无需维护 -credentials.json 文件。`AuthProvider` 在每次消息发送时调用,适合令牌生命周期由外部管理的场景: - -```java -public class SsoAuthProvider implements AuthProvider { - private final SsoClient ssoClient; - - @Override - public void applyAuth(String agentName, AgentCard agentCard, Map headers) { - String token = ssoClient.getAccessToken(agentName); - headers.put("Authorization", "Bearer " + token); - } -} -``` - -### 6.3 协商轮次限制 - -WorkflowEngineClientConfig.builder ().maxNegotiationExchanges (3) 配置本地交互预算, 与收到的 SDK context.maxRounds -分开。耗尽/超时只本地失败,不自动生成 Abort。 结束回复保留收到的 id/round/maxRounds,最后一轮仍可回答。 - -### 6.4 SSE 长连接管理 - -`openNotification` 建立的 SSE 订阅通道是长连接。SDK 的 `A2ATransport` 管理连接池和超时。集成方应在服务关闭时调用 -`extensionTransport.close()` 释放连接。`ExecutePsop` 仅自动关闭它自行创建的 client/transport;传入已有 engineClient 时由集成方负责关闭。不要把任务级 -transport 与 `ExtensionSender` 共用,否则 Notification-T 长连接会在工作流结束时被关闭。 - -### 6.5 并发与并行 - -SDK 的 `WorkflowExecutor` 按 DAG 已激活前驱的完成情况调度就绪步骤;无依赖关系的就绪步骤及步骤内子任务可并行执行。如果 -OMC 有并发限制,通过 DAG 依赖边或宿主限流控制;layer 不是执行顺序约束。 - -### 6.6 错误处理 - -引擎处理远端协议状态和结果解包;onTask 只返回 MessageContent。onSelfTask 返回 TaskResult,失败原因与 outputs 分离。异常和失败 -future 由引擎记录;不要把未发送成功包装成成功输出。详见 [业务回调契约](BUSINESS_CALLBACKS.md)。 - -## 7. 接口速查表 - -```java -interface ControlPoint { - CompletableFuture onTask(TaskRequest request); - CompletableFuture onSelfTask(TaskRequest request); - CompletableFuture onRoute(RouteRequest request); - CompletableFuture onNegotiation(NegotiationRequest request); -} -``` - -onTask 返回最终 parts/metadata/extensions,引擎封装发送,不再生成或改写内容。 onSelfTask 返回本地 TaskResult;onRoute -选择允许的候选;onNegotiation 返回 Send 或 Stop。 未实现的回调明确失败,不回显成功、不选首分支、不自动同意。 -字段与完整示例见 [业务回调集成契约](BUSINESS_CALLBACKS.md)。 - -```java -CompletableFuture sendAuthorization(String agentName, MessageContent content); -NotificationSubscription openNotification(String agentName, MessageContent content, - BiConsumer listener); -``` - -- ExecutePsop.builder ().execute ():执行完整工作流。 -- WorkflowEngineClient.sendMessage (String, MessageContent):DAG 外发送最终内容;DAG 内由执行器调用 dispatch。 -- LoadPsop.search/load、RegistryClient.fetchAgentCards:可选发现辅助。 -- AuthProvider.applyAuth (String, AgentCard, Map):宿主认证,不接收引擎强制规定的凭据文件结构。 -- A2atMessages.from / contextOf:metadata 转换与规范上下文辅助,不是内容 SDK 门面。 - -## 8. 与 Demo 的关键区别 - -本次集成是生产环境接入,与 Demo 示例代码的关键区别: - -| 维度 | Demo (samples) | 生产环境 | -|---------------|--------------------------------|-------------------------------------------------------| -| AgentExecutor | 模拟实现,返回固定文本 | 对接真实 上层故障系统,需完整 A2A 消息时序 | -| OMC Agent | 模拟 Agent,预置响应 | 真实 SPN Domain Agent,Bearer 认证、真实诊断逻辑 | -| AgentCard | 从 classpath 加载 JSON | 从注册中心获取或厂商提供 | -| 认证 | credentials.json 明文密码 | 加密存储 (enc: 前缀) 或自定义 AuthProvider (企业 SSO) | -| TLS | sslVerify=false | 生产证书校验 | -| PSOP | fallback 固定 ID | 编排中心语义搜索 + 动态加载 | -| 事件 | 日志打印 | SSE 推送前端 + 持久化数据库 | -| 协商 | 结构化规则示例(typed reply) | 宿主 SDK 生成最终回复,回调返回 Send 或 Stop | -| 抢通结果 | 通过独立订阅接收,收到结果关闭 | 通过 Notification-T SSE 通道接收并入库 | - ---- - -## 9. 集成自查清单 - -- Maven 是否解析到 `a2a-t-client:1.1.0`,且 jar 为 Central 发布制品而非本地同坐标重打包 -- AgentCard 是否包含 `name`、`description`、`version`、`capabilities`、`defaultInputModes`、`defaultOutputModes`、`skills`、 - `supportedInterfaces` -- 下游 AgentCard 的 `securitySchemes` / `securityRequirements` 是否按实际认证方式配置;AuthProvider-only 推荐 - `securityRequirements: []` -- `AuthProvider` 是否已同时配置到前置预定位 transport 和工作流任务 transport -- 前置预定位 transport 是否为集成方级生命周期,工作流 transport 是否为任务级生命周期 -- onTask 是否仅准备 MessageContent,由引擎判断远端终态;本地 onSelfTask 是否明确返回业务成功或失败 -- `openNotification` 是否传入回调,并在服务关闭时释放前置预定位 transport - -## 协议日志 - -将 `PROTOCOL` logger 设为 DEBUG,可查看实际传输边界的观测记录。 HTTP/JSON-RPC 记录 A2A SDK 处理后的实际序列化正文和应用头; -A2A-Version 只在真实请求有该头时出现,不为日志展示补造 Header。 gRPC 记录实际 metadata 和 protobuf 的 JSON 展示,不伪装成 -HTTP JSON 报文。 JDK 自动网络头、HTTP/2 帧、TLS 密文及服务端字节不在此观测范围。 - -`MODEL_PREVIEW` 只是高层预览,默认关闭,不是协议证据。 - -```properties -logger.protocol.name=PROTOCOL -logger.protocol.level=DEBUG -logger.protocol.additivity=true -WORKFLOW_ENGINE_PROTOCOL_INCLUDE_BODY=true -WORKFLOW_ENGINE_PROTOCOL_MAX_BODY_CHARS=100000 -``` - -DEBUG 开启时正文观测默认开启;敏感部署可显式禁用。JVM 同名属性优先于环境变量。 认证头、Cookie、Token -及识别出的口令字段强制脱敏,不提供关闭脱敏的开关。 这是字段级脱敏,不能自动识别所有个人信息和业务敏感内容,生产应另定日志策略。 -缓冲有界:原始收集器按该数值限制字节,输出文本按字符限制; 超限 SSE 帧整帧丢弃至下个分隔符并标记 -dropped-capacity,禁用、截断、中断也有明确标记。 UTF-8 分片先组装再解码,日志观察失败不改变报文投递;文件引用不会为打印日志而下载。 -requestId 关联单次调用;工作流调用还带 executionId/logicalTaskId/attempt、 agent/contextId/channel,已知远端任务时附 -remoteTaskId,均为本地日志字段,不污染协议 metadata。 - -### 协议日志 pretty 展示 - -协议日志默认以 pretty 形式展示:逐行打印头字段,缩进 JSON 正文。SSE 保留 id/event 等事件信息, JSON 在 -`=== SSE data (JSON display; not wire text) ===` 与 `=== End SSE data ===` 之间单独展示, 不再为展开后的每行 JSON 添加 -`data:` 前缀。多个事件各自保留边界。 -`WORKFLOW_ENGINE_PROTOCOL_PRETTY=false`(环境变量或同名 JVM 属性)可保留脱敏后的原始正文展示。 pretty 只影响显示(JSON 缩进及 -SSE 数据区标记),不改实际发送字节、metadata、数值或扩展头; 原始观测正文仍保留,SSE pretty 展示不是可直接重放的抓包。 JSON -字符串中的 `\\n` 保留转义,避免把业务字符串误写成非法 JSON;非 JSON/不完整正文保持原样。 日志仍执行脱敏和容量限制。Demo 启动记录 -`NEGOTIATION_DEMO enabled=..., city=...`; 本地默认 City1 协商、City2 直接诊断,`-Da2at.samples.negotiation=false` 关闭缺参演示。 -协商激活位于 `A2A-Extensions: .../Negotiation-T/v1`,不存在专用的 Negotiation-T HTTP 头; 正文检查对应 URI 的 metadata 和 -`negotiationContext`,不要只检索大写 `NEGOTIATION-T`。