Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.agentscope.core.chat.completions.model.ChatMessage;
import io.agentscope.core.chat.completions.model.ToolCall;
import io.agentscope.core.message.ContentBlock;
import io.agentscope.core.message.GenerateReason;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
Expand Down Expand Up @@ -99,9 +100,13 @@ public ChatCompletionsResponse buildResponse(
ChatMessage message = convertMsgToChatMessage(reply);
choice.setMessage(message);

// Set finish_reason based on whether there are tool calls
if (message.getToolCalls() != null && !message.getToolCalls().isEmpty()) {
// Set finish_reason based on GenerateReason or tool calls
GenerateReason generateReason = reply != null ? reply.getGenerateReason() : null;
if (generateReason == GenerateReason.TOOL_SUSPENDED
|| (message.getToolCalls() != null && !message.getToolCalls().isEmpty())) {
choice.setFinishReason("tool_calls");
} else if (generateReason == GenerateReason.MAX_ITERATIONS) {
choice.setFinishReason("length");
} else {
choice.setFinishReason("stop");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.core.chat.completions.converter;

import io.agentscope.core.chat.completions.model.OpenAITool;
import io.agentscope.core.chat.completions.model.OpenAIToolFunction;
import io.agentscope.core.model.ToolSchema;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Converter for converting OpenAI tool format to AgentScope ToolSchema.
*
* <p>This converter handles the transformation from OpenAI's tool format (used in Chat Completions
* API requests) to AgentScope's internal ToolSchema format. Tools converted by this converter are
* intended to be registered as schema-only tools, which will trigger tool suspension when called.
*/
public class OpenAIToolConverter {

private static final Logger log = LoggerFactory.getLogger(OpenAIToolConverter.class);

/**
* Converts a list of OpenAI tools to AgentScope ToolSchemas.
*
* <p>Only tools with type "function" are converted. Other tool types are skipped with a warning.
*
* @param tools The list of OpenAI tools to convert (may be null or empty)
* @return A list of converted ToolSchema objects; returns an empty list if input is null or
* empty
*/
public List<ToolSchema> convertToToolSchemas(List<OpenAITool> tools) {
if (tools == null || tools.isEmpty()) {
return List.of();
}

List<ToolSchema> schemas = new ArrayList<>();

for (OpenAITool tool : tools) {
if (tool == null) {
log.warn("Skipping null tool in conversion");
continue;
}

// Only support function type tools for now
if (!"function".equals(tool.getType())) {
log.warn(
"Skipping tool with unsupported type: {}. Only 'function' type is"
+ " supported",
tool.getType());
continue;
}

OpenAIToolFunction function = tool.getFunction();
if (function == null) {
log.warn("Skipping tool with null function definition");
continue;
}

String name = function.getName();
String description = function.getDescription();
Map<String, Object> parameters = function.getParameters();

// Validate required fields
if (name == null || name.isBlank()) {
log.warn("Skipping tool with null or empty name");
continue;
}

if (description == null || description.isBlank()) {
log.warn("Skipping tool '{}' with null or empty description", name);
// Use empty string as fallback for description
description = "";
}

try {
ToolSchema.Builder schemaBuilder =
ToolSchema.builder().name(name).description(description);

if (parameters != null) {
schemaBuilder.parameters(parameters);
}

if (function.getStrict() != null) {
schemaBuilder.strict(function.getStrict());
}

ToolSchema schema = schemaBuilder.build();
schemas.add(schema);
log.debug("Converted OpenAI tool to ToolSchema: {}", name);

} catch (Exception e) {
log.error("Failed to convert tool '{}' to ToolSchema: {}", name, e.getMessage(), e);
}
}

return schemas;
}

/**
* Converts a single OpenAI tool to a ToolSchema.
*
* @param tool The OpenAI tool to convert
* @return The converted ToolSchema, or null if conversion fails
*/
public ToolSchema convertToToolSchema(OpenAITool tool) {
if (tool == null) {
return null;
}

List<ToolSchema> schemas = convertToToolSchemas(List.of(tool));
return schemas.isEmpty() ? null : schemas.get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ public class ChatCompletionsRequest {
/** Whether to stream responses via Server-Sent Events (SSE). Optional, defaults to false. */
private Boolean stream;

/**
* A list of tools the model may call. Currently, only functions are supported as a tool.
*
* <p>When tools are provided, they are registered as schema-only tools. When the agent decides
* to call a tool, execution is suspended and the tool call is returned to the client for
* external execution.
*/
private List<OpenAITool> tools;

public String getModel() {
return model;
}
Expand All @@ -94,4 +103,12 @@ public Boolean getStream() {
public void setStream(Boolean stream) {
this.stream = stream;
}

public List<OpenAITool> getTools() {
return tools;
}

public void setTools(List<OpenAITool> tools) {
this.tools = tools;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.core.chat.completions.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* OpenAI tool definition for Chat Completions API requests.
*
* <p>This class represents a tool that can be called by the model, following OpenAI's format.
*
* <p>Example:
* <pre>{@code
* {
* "type": "function",
* "function": {
* "name": "get_weather",
* "description": "Get the current weather",
* "parameters": {
* "type": "object",
* "properties": {
* "location": {"type": "string"}
* },
* "required": ["location"]
* }
* }
* }
* }</pre>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OpenAITool {

/** Tool type, always "function" for now. */
@JsonProperty("type")
private String type = "function";

/** The function definition. */
@JsonProperty("function")
private OpenAIToolFunction function;

/** Default constructor for deserialization. */
public OpenAITool() {}

/**
* Creates a new OpenAITool with the specified function.
*
* @param function The function definition
*/
public OpenAITool(OpenAIToolFunction function) {
this.function = function;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public OpenAIToolFunction getFunction() {
return function;
}

public void setFunction(OpenAIToolFunction function) {
this.function = function;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.core.chat.completions.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;

/**
* OpenAI tool function definition for Chat Completions API requests.
*
* <p>This class represents the function definition in a tool, following OpenAI's format.
*
* <p>Example:
* <pre>{@code
* {
* "name": "get_weather",
* "description": "Get the current weather",
* "parameters": {
* "type": "object",
* "properties": {
* "location": {"type": "string"}
* },
* "required": ["location"]
* }
* }
* }</pre>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OpenAIToolFunction {

/** The name of the function. */
@JsonProperty("name")
private String name;

/** The description of the function. */
@JsonProperty("description")
private String description;

/** The JSON Schema for the function parameters. */
@JsonProperty("parameters")
private Map<String, Object> parameters;

/** Whether to enable strict mode for schema validation. */
@JsonProperty("strict")
private Boolean strict;

/** Default constructor for deserialization. */
public OpenAIToolFunction() {}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Map<String, Object> getParameters() {
return parameters;
}

public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}

public Boolean getStrict() {
return strict;
}

public void setStrict(Boolean strict) {
this.strict = strict;
}
}
Loading
Loading