@openai/agents-openai
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
300 lines • 11.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIChatCompletionsModel = exports.FAKE_ID = void 0;
const agents_core_1 = require("@openai/agents-core");
const logger_1 = __importDefault(require("./logger.js"));
const defaults_1 = require("./defaults.js");
const openaiChatCompletionsStreaming_1 = require("./openaiChatCompletionsStreaming.js");
const openaiChatCompletionsConverter_1 = require("./openaiChatCompletionsConverter.js");
exports.FAKE_ID = 'FAKE_ID';
function hasReasoningContent(message) {
return ('reasoning' in message &&
typeof message.reasoning === 'string' &&
message.reasoning !== '');
}
/**
* A model that uses (or is compatible with) OpenAI's Chat Completions API.
*/
class OpenAIChatCompletionsModel {
#client;
#model;
constructor(client, model) {
this.#client = client;
this.#model = model;
}
async getResponse(request) {
const response = await (0, agents_core_1.withGenerationSpan)(async (span) => {
span.spanData.model = this.#model;
span.spanData.model_config = request.modelSettings
? {
temperature: request.modelSettings.temperature,
top_p: request.modelSettings.topP,
frequency_penalty: request.modelSettings.frequencyPenalty,
presence_penalty: request.modelSettings.presencePenalty,
reasoning_effort: request.modelSettings.reasoning?.effort,
verbosity: request.modelSettings.text?.verbosity,
}
: { base_url: this.#client.baseURL };
const response = await this.#fetchResponse(request, span, false);
if (span && request.tracing === true) {
span.spanData.output = [response];
}
return response;
});
const output = [];
if (response.choices && response.choices[0]) {
const message = response.choices[0].message;
if (hasReasoningContent(message)) {
output.push({
type: 'reasoning',
content: [],
rawContent: [
{
type: 'reasoning_text',
text: message.reasoning,
},
],
});
}
if (message.content !== undefined &&
message.content !== null &&
// Azure OpenAI returns empty string instead of null for tool calls, causing parser rejection
!(message.tool_calls && message.content === '')) {
const { content, ...rest } = message;
output.push({
id: response.id,
type: 'message',
role: 'assistant',
content: [
{
type: 'output_text',
text: content || '',
providerData: rest,
},
],
status: 'completed',
});
}
else if (message.refusal) {
const { refusal, ...rest } = message;
output.push({
id: response.id,
type: 'message',
role: 'assistant',
content: [
{
type: 'refusal',
refusal: refusal || '',
providerData: rest,
},
],
status: 'completed',
});
}
else if (message.audio) {
const { data, ...remainingAudioData } = message.audio;
output.push({
id: response.id,
type: 'message',
role: 'assistant',
content: [
{
type: 'audio',
audio: data,
providerData: remainingAudioData,
},
],
status: 'completed',
});
}
else if (message.tool_calls) {
for (const tool_call of message.tool_calls) {
if (tool_call.type === 'function') {
// Note: custom tools are not supported for now
const { id: callId, ...remainingToolCallData } = tool_call;
const { arguments: args, name, ...remainingFunctionData } = tool_call.function;
output.push({
id: response.id,
type: 'function_call',
arguments: args,
name: name,
callId: callId,
status: 'completed',
providerData: {
...remainingToolCallData,
...remainingFunctionData,
},
});
}
}
}
}
const modelResponse = {
usage: response.usage
? new agents_core_1.Usage(toResponseUsage(response.usage))
: new agents_core_1.Usage(),
output,
responseId: response.id,
providerData: response,
};
return modelResponse;
}
async *getStreamedResponse(request) {
const span = request.tracing ? (0, agents_core_1.createGenerationSpan)() : undefined;
try {
if (span) {
span.start();
(0, agents_core_1.setCurrentSpan)(span);
}
const stream = await this.#fetchResponse(request, span, true);
const response = {
id: exports.FAKE_ID,
created: Math.floor(Date.now() / 1000),
model: this.#model,
object: 'chat.completion',
choices: [],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
for await (const event of (0, openaiChatCompletionsStreaming_1.convertChatCompletionsStreamToResponses)(response, stream)) {
yield event;
}
if (span && response && request.tracing === true) {
span.spanData.output = [response];
}
}
catch (error) {
if (span) {
span.setError({
message: 'Error streaming response',
data: {
error: request.tracing === true
? String(error)
: error instanceof Error
? error.name
: undefined,
},
});
}
throw error;
}
finally {
if (span) {
span.end();
(0, agents_core_1.resetCurrentSpan)();
}
}
}
async #fetchResponse(request, span, stream) {
const tools = [];
if (request.tools) {
for (const tool of request.tools) {
tools.push((0, openaiChatCompletionsConverter_1.toolToOpenAI)(tool));
}
}
if (request.handoffs) {
for (const handoff of request.handoffs) {
tools.push((0, openaiChatCompletionsConverter_1.convertHandoffTool)(handoff));
}
}
const responseFormat = getResponseFormat(request.outputType);
let parallelToolCalls = undefined;
if (typeof request.modelSettings.parallelToolCalls === 'boolean') {
if (request.modelSettings.parallelToolCalls && tools.length === 0) {
throw new Error('Parallel tool calls are not supported without tools');
}
parallelToolCalls = request.modelSettings.parallelToolCalls;
}
const messages = (0, openaiChatCompletionsConverter_1.itemsToMessages)(request.input);
if (request.systemInstructions) {
messages.unshift({
content: request.systemInstructions,
role: 'system',
});
}
if (span && request.tracing === true) {
span.spanData.input = messages;
}
const providerData = request.modelSettings.providerData ?? {};
if (request.modelSettings.reasoning &&
request.modelSettings.reasoning.effort) {
// merge the top-level reasoning.effort into provider data
providerData.reasoning_effort = request.modelSettings.reasoning.effort;
}
if (request.modelSettings.text && request.modelSettings.text.verbosity) {
// merge the top-level text.verbosity into provider data
providerData.verbosity = request.modelSettings.text.verbosity;
}
const requestData = {
model: this.#model,
messages,
tools: tools.length ? tools : undefined,
temperature: request.modelSettings.temperature,
top_p: request.modelSettings.topP,
frequency_penalty: request.modelSettings.frequencyPenalty,
presence_penalty: request.modelSettings.presencePenalty,
max_tokens: request.modelSettings.maxTokens,
tool_choice: (0, openaiChatCompletionsConverter_1.convertToolChoice)(request.modelSettings.toolChoice),
response_format: responseFormat,
parallel_tool_calls: parallelToolCalls,
stream,
store: request.modelSettings.store,
...providerData,
};
if (logger_1.default.dontLogModelData) {
logger_1.default.debug('Calling LLM');
}
else {
logger_1.default.debug(`Calling LLM. Request data: ${JSON.stringify(requestData, null, 2)}`);
}
const completion = await this.#client.chat.completions.create(requestData, {
headers: defaults_1.HEADERS,
signal: request.signal,
});
if (logger_1.default.dontLogModelData) {
logger_1.default.debug('Response received');
}
else {
logger_1.default.debug(`Response received: ${JSON.stringify(completion, null, 2)}`);
}
return completion;
}
}
exports.OpenAIChatCompletionsModel = OpenAIChatCompletionsModel;
function getResponseFormat(outputType) {
if (outputType === 'text') {
return { type: 'text' };
}
if (outputType.type === 'json_schema') {
return {
type: 'json_schema',
json_schema: {
name: outputType.name,
strict: outputType.strict,
schema: outputType.schema,
},
};
}
return { type: 'json_object' };
}
function toResponseUsage(usage) {
return {
requests: 1,
input_tokens: usage.prompt_tokens,
output_tokens: usage.completion_tokens,
total_tokens: usage.total_tokens,
input_tokens_details: {
cached_tokens: usage.prompt_tokens_details?.cached_tokens || 0,
},
output_tokens_details: {
reasoning_tokens: usage.completion_tokens_details?.reasoning_tokens || 0,
},
};
}
//# sourceMappingURL=openaiChatCompletionsModel.js.map