feat(responses api): add responses api support#1454
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces OpenAI Responses API-compatible HTTP support to AgentScope Java by adding a new Responses web extension, a Spring Boot starter exposing /v1/responses and /v1/conversations, a runnable example app, and corresponding English/Chinese documentation.
Changes:
- Added
agentscope-extensions-responses-webfor converting Responses requests into AgentScope messages and producing Responses-style non-streaming/streaming outputs (including JSON Schema structured output). - Added
agentscope-responses-web-starterwith Spring Boot auto-configuration, controllers, streaming service, and an in-memory state backend for stored responses/background/conversations. - Added a
responses-webexample module plus docs + TOC + BOM/distribution wiring.
Reviewed changes
Copilot reviewed 55 out of 55 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/zh/task/responses-api.md | New Chinese Responses API usage guide and curl examples. |
| docs/zh/quickstart/installation.md | Adds the new Responses starter to the starter list (ZH). |
| docs/en/task/responses-api.md | New English Responses API usage guide and curl examples. |
| docs/en/quickstart/installation.md | Adds the new Responses starter to the starter list (EN). |
| docs/_toc.yml | Adds Responses API docs pages to EN/ZH TOC. |
| agentscope-extensions/pom.xml | Registers the new agentscope-extensions-responses-web module. |
| agentscope-extensions/agentscope-spring-boot-starters/pom.xml | Registers the new agentscope-responses-web-starter module. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/pom.xml | New starter module dependencies (Spring MVC + validation + tests). |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | Enables auto-configuration discovery for the starter. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/config/ResponsesWebAutoConfiguration.java | Auto-configures controllers, converters, streaming, and state beans. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/config/ResponsesProperties.java | Adds agentscope.responses.* configuration properties. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/web/ResponsesController.java | Implements /v1/responses endpoints incl. streaming/background/stateful operations. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/web/ConversationsController.java | Implements /v1/conversations CRUD and items endpoints. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/service/ResponsesStreamingService.java | Converts core Responses stream events to Spring ServerSentEvent. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/main/java/io/agentscope/spring/boot/responses/service/ResponsesStateService.java | In-memory state backend for responses, background tasks, and conversations. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/test/java/io/agentscope/spring/boot/responses/config/ResponsesWebAutoConfigurationTest.java | Verifies auto-configured beans and property binding. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/test/java/io/agentscope/spring/boot/responses/web/ResponsesControllerTest.java | Tests create/retrieve/store/background/streaming and error behavior. |
| agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter/src/test/java/io/agentscope/spring/boot/responses/web/ConversationsControllerTest.java | Tests conversation CRUD and item lifecycle. |
| agentscope-extensions/agentscope-extensions-responses-web/pom.xml | New core extension module for Responses protocol support. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/converter/ResponsesInputConverter.java | Converts Responses input shapes to AgentScope Msg + system fragments + schema metadata. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/converter/ResponsesToolConverter.java | Converts function tools + tool_choice into AgentScope tool schemas/choices. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/converter/ResponsesGenerationOptionsConverter.java | Maps Responses generation fields to AgentScope GenerateOptions. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/converter/ResponsesConversionResult.java | Record encapsulating conversion outputs. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/converter/ResponsesValidationException.java | Carries Responses-style validation error metadata. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/hook/ResponsesRequestHook.java | Applies system fragments + per-request generation options via hooks. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/builder/ResponsesResponseBuilder.java | Builds Responses non-streaming/structured/stream-terminal payloads and errors. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/streaming/ResponsesStreamingAdapter.java | Core choreography converting AgentScope stream events into Responses stream events. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesRequest.java | DTO for /v1/responses request payload. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesResponse.java | DTO for Responses non-streaming response object. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesStreamEvent.java | DTO for streaming event payloads. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesOutputItem.java | Models message/function_call output items. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesContentPart.java | Models content parts like output_text. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesTool.java | Models function tool declarations. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesTextConfig.java | Models text.format configuration (incl. schema). |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesReasoningConfig.java | Models reasoning.effort config. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesUsage.java | Models token usage in Responses shape. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesTokenCount.java | Models token count endpoint response. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesList.java | Generic list wrapper for Responses/Conversations endpoints. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesConversation.java | Models conversation resource. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesConversationRequest.java | Models conversation create/update request payload. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesConversationItemsRequest.java | Models conversation items create request payload. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesDeletionStatus.java | Models delete confirmation payload. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesError.java | Models error payload shape. |
| agentscope-extensions/agentscope-extensions-responses-web/src/main/java/io/agentscope/core/responses/model/ResponsesErrorResponse.java | Models error wrapper returned for 4xx validation errors. |
| agentscope-extensions/agentscope-extensions-responses-web/src/test/java/io/agentscope/core/responses/converter/ResponsesInputConverterTest.java | Unit tests for input conversion (multimodal, tools, stateful fields). |
| agentscope-extensions/agentscope-extensions-responses-web/src/test/java/io/agentscope/core/responses/converter/ResponsesToolConverterTest.java | Unit tests for tool schema + tool_choice conversion. |
| agentscope-extensions/agentscope-extensions-responses-web/src/test/java/io/agentscope/core/responses/converter/ResponsesGenerationOptionsConverterTest.java | Unit tests for generation option mapping. |
| agentscope-extensions/agentscope-extensions-responses-web/src/test/java/io/agentscope/core/responses/builder/ResponsesResponseBuilderTest.java | Unit tests for non-streaming + structured + error payload building. |
| agentscope-extensions/agentscope-extensions-responses-web/src/test/java/io/agentscope/core/responses/streaming/ResponsesStreamingAdapterTest.java | Unit tests for streaming event choreography and structured streaming. |
| agentscope-examples/pom.xml | Adds the new responses-web example module. |
| agentscope-examples/responses-web/pom.xml | New example Spring Boot app module wiring. |
| agentscope-examples/responses-web/src/main/resources/application.yml | Example configuration enabling responses/conversations endpoints. |
| agentscope-examples/responses-web/src/main/java/io/agentscope/examples/responses/ResponsesWebApplication.java | Sample app entrypoint that prints curl-based manual verification instructions. |
| agentscope-distribution/agentscope-bom/pom.xml | Adds new extension + starter to the BOM. |
| agentscope-distribution/agentscope-all/pom.xml | Adds new extension to the “all” distribution artifact. |
| StringBuilder builder = new StringBuilder(); | ||
| for (ContentBlock block : content) { | ||
| if (block instanceof TextBlock textBlock) { | ||
| if (!builder.isEmpty()) { |
| response.setStatus(status); | ||
| response.setModel(request != null ? request.getModel() : null); | ||
| response.setInstructions(request != null ? request.getInstructions() : null); | ||
| response.setStore(request == null || !Boolean.FALSE.equals(request.getStore())); |
| return createNonStreamingResponse(prepared.request(), conversion, responseId) | ||
| .map(response -> stateService.save(response, prepared)); | ||
| } catch (ResponsesValidationException e) { | ||
| return ResponseEntity.badRequest().body(responseBuilder.buildErrorResponse(e)); | ||
| } catch (IllegalArgumentException e) { | ||
| return ResponseEntity.badRequest().body(responseBuilder.buildInvalidRequestError(e)); | ||
| } |
| // Store the subscription separately so /cancel can dispose it while the model call is still | ||
| // running. | ||
| Disposable task = | ||
| createNonStreamingResponse(request, conversion, responseId) | ||
| .map(response -> stateService.save(response, prepared)) | ||
| .subscribe(); | ||
| stateService.attachBackgroundTask(responseId, task); |
| private ServerSentEvent<String> toSse(ResponsesStreamEvent event) { | ||
| try { | ||
| return ServerSentEvent.<String>builder() | ||
| .event(event.getType()) | ||
| .data(OBJECT_MAPPER.writeValueAsString(event)) | ||
| .build(); | ||
| } catch (JsonProcessingException e) { | ||
| return ServerSentEvent.<String>builder() | ||
| .event("error") | ||
| .data("{\"error\":\"Serialization error\"}") | ||
| .build(); | ||
| } |
| request != null && request.getItems() != null | ||
| ? withItemIds(request.getItems()) | ||
| : new ArrayList<>(); | ||
| conversations.put(conversation.getId(), new StoredConversation(conversation, items)); |
There was a problem hiding this comment.
createConversation stores items as a plain ArrayList. This list is later modified by multiple methods under concurrent requests via addAll, removeIf, and iteration/copying. Concurrent requests sharing the same conversationId will trigger ConcurrentModificationException, ArrayIndexOutOfBoundsException, or result in lost updates.
| return new ResponsesDeletionStatus(itemId, "conversation.item.deleted", true); | ||
| } | ||
|
|
||
| private void appendConversationItems( |
There was a problem hiding this comment.
There is no synchronization between the two addAll calls. If two concurrent requests reference the same conversation, the input/output order will get interleaved (e.g., "input₁ output₂ output₁ input₂"), which breaks the conversation semantics. Additionally, if the underlying ArrayList is being iterated by page() concurrently, it will throw a ConcurrentModificationException (CME).
| createNonStreamingResponse(request, conversion, responseId) | ||
| .map(response -> stateService.save(response, prepared)) | ||
| .subscribe(); | ||
| stateService.attachBackgroundTask(responseId, task); |
There was a problem hiding this comment.
There are two race conditions:
-
Fast responses make L443 run before L445, attaching a disposed task to a completed entry.
-
A /cancel or /delete between L437 and L445 misses the dispose() call. The task finishes and writes completed anyway, breaking cancel semantics.
Proposed fix:
-
Defer subscription (Mono.create / subscribeOn(...)) to get the Disposable and insert it into the map atomically.
-
Remove the two-phase attachBackgroundTask.
-
In cancelResponse/deleteResponse, use responses.compute(...) to set a terminal flag, ensuring any late-arriving save() short-circuits.
| prepared != null ? prepared.conversationId() : null, | ||
| null)); | ||
| } | ||
| return response; |
There was a problem hiding this comment.
Race condition between cancel and save writes:
-
cancelResponse retrieves stored, invokes task.dispose(), and updates the status to "cancelled".
-
The background pipeline completes before dispose takes effect and calls save(), writing back status="completed" and backgroundTask=null.
-
As a result, /cancel shows cancelled, but /retrieve shows completed.
| return ResponseEntity.badRequest().body(responseBuilder.buildErrorResponse(e)); | ||
| } catch (IllegalArgumentException e) { | ||
| return ResponseEntity.badRequest().body(responseBuilder.buildInvalidRequestError(e)); | ||
| } |
There was a problem hiding this comment.
createResponse currently does a blanket mapping of all ResponsesValidationExceptions to HTTP 400, even when e.getCode() == "not_found" (e.g., due to an unknown previous_response_id or conversation). This creates an inconsistency because listResponseInputItems in the same controller correctly maps "not_found" to a 404 using responseStatus(e). Let's use responseStatus(e) here as well to keep the behavior consistent.
| StringBuilder builder = new StringBuilder(); | ||
| for (ContentBlock block : content) { | ||
| if (block instanceof TextBlock textBlock) { | ||
| if (!builder.isEmpty()) { |
| new ArrayList<>(ordered.subList(start, end)), end < ordered.size()); | ||
| } | ||
|
|
||
| private String resolveConversationId(Object conversation) { |
There was a problem hiding this comment.
resolveConversationId creates and persists a new conversation via createConversation(null) early in the prepare() phase. However, the subsequent inputConverter.convert(...) can still throw a ResponsesValidationException. When this happens, the empty conversation is already persisted with no cleanup path, which will accumulate as memory garbage over time.
| } | ||
|
|
||
| return Flux.fromIterable(events); | ||
| } |
There was a problem hiding this comment.
This method is a bit monolithic (130 lines, 11 params) and state-heavy, making it hard to read and test.
Suggest extracting a private StreamingState class to hold all mutable state, and splitting the "text" branch and "tool-use" branch into distinct private methods.
| value = "${agentscope.responses.base-path:/v1/responses}/compact", | ||
| consumes = MediaType.APPLICATION_JSON_VALUE) | ||
| public Object compactResponseInput(@Valid @RequestBody ResponsesRequest request) { | ||
| String responseId = responseId(); |
There was a problem hiding this comment.
compactResponseInput hardcodes setStream(false), but a mistakenly passed background=true causes it to run synchronously and return completed (inconsistent with createResponse). Suggest explicitly rejecting/resetting the background flag after prepared.request().setStream(false).
| if (request.getTools() != null && !request.getTools().isEmpty()) { | ||
| agent.getToolkit() | ||
| .registerSchemas(toolConverter.convertToToolSchemas(request.getTools())); | ||
| } |
There was a problem hiding this comment.
Suggest adding a cache to minimize the cost of full JSON parsing and building.
AgentScope-Java Version
1.1.0
Description
This PR adds OpenAI Responses API style support to AgentScope Java.
It introduces a new Responses web extension, a Spring Boot starter, an example application, and documentation for using AgentScope Java through
/v1/responsesand/v1/conversationscompatible HTTP endpoints.Main changes:
Added
agentscope-extensions-responses-webAdded
agentscope-responses-web-starterPOST /v1/responses.previous_response_id,conversation,store, andbackground=true.Added
agentscope-examples/responses-webUpdated documentation
How to test:
Checklist
Please check the following items before code is ready to be reviewed.