Skip to content

feat(responses api): add responses api support#1454

Open
lynx009 wants to merge 6 commits into
agentscope-ai:mainfrom
lynx009:feat/responses-api-support
Open

feat(responses api): add responses api support#1454
lynx009 wants to merge 6 commits into
agentscope-ai:mainfrom
lynx009:feat/responses-api-support

Conversation

@lynx009
Copy link
Copy Markdown
Contributor

@lynx009 lynx009 commented May 21, 2026

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/responses and /v1/conversations compatible HTTP endpoints.

Main changes:

  • Added agentscope-extensions-responses-web

    • Converts Responses API request input into AgentScope internal messages.
    • Builds Responses API compatible response objects.
    • Supports non-streaming and streaming Responses API output.
    • Supports JSON Schema structured output in both non-streaming and streaming modes.
    • Supports function tool schemas, function call output items, multimodal/file-like input item shapes, and official-style response/conversation objects.
  • Added agentscope-responses-web-starter

    • Exposes POST /v1/responses.
    • Exposes response retrieval, deletion, cancellation, input item listing, token count, and compact endpoints.
    • Exposes conversation creation, retrieval, update, deletion, and item management endpoints.
    • Provides in-memory state support for previous_response_id, conversation, store, and background=true.
  • Added agentscope-examples/responses-web

    • Provides a runnable Spring Boot example for manual testing.
    • Prints useful curl examples at startup.
  • Updated documentation

    • Added English and Chinese Responses API docs.
    • Added installation references for the new starter.
    • Added manual verification examples for non-streaming, streaming, structured output, background responses, cancellation, conversations, and input items.

How to test:

mvn -pl agentscope-extensions/agentscope-extensions-responses-web,agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter spotless:apply
mvn -pl agentscope-extensions/agentscope-extensions-responses-web,agentscope-extensions/agentscope-spring-boot-starters/agentscope-responses-web-starter -am -Dtest='io.agentscope.core.responses.**,io.agentscope.spring.boot.responses.**' -DfailIfNoTests=false -DfailIfNoSpecifiedTests=false test
mvn -pl agentscope-examples/responses-web -am -DskipTests package
Manual test example:

curl -s -X POST http://localhost:8080/v1/responses \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "qwen3-max",
    "input": "Hello, briefly introduce AgentScope Java.",
    "store": true
  }'
Streaming test example:

curl -N -X POST http://localhost:8080/v1/responses \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{
    "model": "qwen3-max",
    "input": "Please stream three short sentences.",
    "stream": true
  }'

Checklist
Please check the following items before code is ready to be reviewed.

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (e.g. links, examples, etc.)
  • Code is ready for review

@lynx009 lynx009 requested review from a team and Copilot May 21, 2026 06:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-web for converting Responses requests into AgentScope messages and producing Responses-style non-streaming/streaming outputs (including JSON Schema structured output).
  • Added agentscope-responses-web-starter with Spring Boot auto-configuration, controllers, streaming service, and an in-memory state backend for stored responses/background/conversations.
  • Added a responses-web example 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()) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore

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()));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense

Comment on lines +168 to +174
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));
}
Comment on lines +439 to +445
// 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);
Comment on lines +153 to +164
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));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two race conditions:

  1. Fast responses make L443 run before L445, attaching a disposed task to a completed entry.

  2. 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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition between cancel and save writes:

  1. cancelResponse retrieves stored, invokes task.dispose(), and updates the status to "cancelled".

  2. The background pipeline completes before dispose takes effect and calls save(), writing back status="completed" and backgroundTask=null.

  3. 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));
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore

new ArrayList<>(ordered.subList(start, end)), end < ordered.size());
}

private String resolveConversationId(Object conversation) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()));
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a cache to minimize the cost of full JSON parsing and building.

@AgentScopeJavaBot AgentScopeJavaBot added enhancement New feature or request area/ext/integration External protocols & middleware integrations area/ext/spring-boot Spring Boot starters labels May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ext/integration External protocols & middleware integrations area/ext/spring-boot Spring Boot starters enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants