contents) {
- if (contents.isEmpty() || !Iterables.getLast(contents).role().orElse("").equals("user")) {
- Content userContent = Content.fromParts(Part.fromText(CONTINUE_OUTPUT_MESSAGE));
- return Stream.concat(contents.stream(), Stream.of(userContent)).collect(toImmutableList());
- }
- return contents;
- }
-
- private String extractInstructions(LlmRequest llmRequest) {
- return llmRequest
- .config()
- .flatMap(GenerateContentConfig::systemInstruction)
- .flatMap(Content::parts)
- .map(
- parts ->
- parts.stream()
- .filter(p -> p.text().isPresent())
- .map(p -> p.text().get())
- .collect(Collectors.joining("\n")))
- .filter(text -> !text.isEmpty())
- .orElse("");
- }
-
- /**
- * Converts ADK Content list to Responses API input items.
- *
- * Unlike Chat Completions (which uses a flat messages array with roles), the Responses API
- * uses typed items: plain messages use {@code {role, content}}, function calls use {@code {type:
- * "function_call", ...}}, and tool results use {@code {type: "function_call_output", ...}}.
- */
- private JSONArray buildInputItems(List contents) {
- JSONArray items = new JSONArray();
-
- for (Content item : contents) {
- String role = item.role().orElse("user");
- List parts = item.parts().orElse(ImmutableList.of());
-
- if (parts.isEmpty()) {
- JSONObject msg = new JSONObject();
- msg.put("role", role.equals("model") ? "assistant" : role);
- msg.put("content", item.text());
- items.put(msg);
- continue;
- }
-
- Part firstPart = parts.get(0);
-
- if (firstPart.functionResponse().isPresent()) {
- JSONObject output = new JSONObject();
- output.put("type", "function_call_output");
- output.put(
- "call_id", "call_" + firstPart.functionResponse().get().name().orElse("unknown"));
- output.put(
- "output",
- new JSONObject(firstPart.functionResponse().get().response().get()).toString());
- items.put(output);
- } else if (firstPart.functionCall().isPresent()) {
- FunctionCall fc = firstPart.functionCall().get();
- JSONObject fcItem = new JSONObject();
- fcItem.put("type", "function_call");
- fcItem.put("call_id", "call_" + fc.name().orElse("unknown"));
- fcItem.put("name", fc.name().orElse(""));
- fcItem.put("arguments", new JSONObject(fc.args().orElse(Map.of())).toString());
- items.put(fcItem);
- } else {
- JSONObject msg = new JSONObject();
- msg.put("role", role.equals("model") ? "assistant" : role);
- msg.put("content", item.text());
- items.put(msg);
- }
- }
- return items;
- }
-
- /**
- * Builds Responses API tool definitions (internally-tagged).
- *
- * Unlike Chat Completions' externally-tagged {@code {type:"function", function:{name:...}}},
- * the Responses API uses {@code {type:"function", name:..., parameters:...}} at the top level.
- */
- private JSONArray buildTools(LlmRequest llmRequest) {
- JSONArray tools = new JSONArray();
- llmRequest
- .tools()
- .forEach(
- (name, baseTool) -> {
- Optional declOpt = baseTool.declaration();
- if (declOpt.isEmpty()) {
- logger.warn("Skipping tool '{}' with missing declaration.", baseTool.name());
- return;
- }
-
- FunctionDeclaration decl = declOpt.get();
- JSONObject tool = new JSONObject();
- tool.put("type", "function");
- tool.put("name", cleanForIdentifierPattern(decl.name().get()));
- tool.put("description", decl.description().orElse(""));
-
- Optional paramsOpt = decl.parameters();
- if (paramsOpt.isPresent()) {
- Schema paramsSchema = paramsOpt.get();
- Map paramsMap = new HashMap<>();
- paramsMap.put("type", "object");
-
- Optional