merlyn/server/utils/agents/aibitat/providers/azure.js
Timothy Carambat d6f0d305ab
Enable real-time agent tool call streaming for all providers (#4279)
* WIP agentic tool call streaming
- OpenAI
- Anthropic
- Azure OpenAI

* WIP rest of providers EXCLUDES Bedrock and GenericOpenAI

* patch untooled complete/streaming to use chatCallback provider from provider class and not assume OpenAI client struct
example: Ollama

* modify ollama to function with its own overrides
normalize completion/stream outputs across providers/untooled

* dev build

* fix message sanization for anthropic agent streaming

* wip fix anthropic agentic streaming sanitization

* patch gemini, webgenui, generic aibitat providers + disable providers unable to test

* refactor anthropic aibitat provider for empty message and tool call formatting

* Add frontend missing prop check
update Azure for streaming support
update Gemini to streamting support on gemini-* models
generic OpenAI disable streaming
verify localAI support
verify NVIDIA Nim support

* DPAIS, remove temp from call, support streaming'

* remove 0 temp to remove possibility of bad temp error/500s/400s

* Patch condition where model is non-streamable and no tools are present or called resulting in the provider `handleFunctionCallChat` being called - which returns a string.

This would then fail in Untooled.complete since response would be a string and not the expected `response.choices?.[0]?.message`

Modified this line to handle both conditions for stream/non-streaming and tool presence or lack thereof

* Allow generic Openai to be streamable since using untooled it should work fine
honor disabled streaming for provider where that concern may apply for regular chats

* rename function and more gemini-specific function to gemini provider

* add comments for readability
.complete on azure should be non-streaming as this is the sync response

* migrate CometAPI, but disable as we cannot test

---------

Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
2025-10-01 10:17:18 -07:00

114 lines
3.0 KiB
JavaScript

const { AzureOpenAI } = require("openai");
const Provider = require("./ai-provider.js");
const { RetryError } = require("../error.js");
/**
* The agent provider for the Azure OpenAI API.
*/
class AzureOpenAiProvider extends Provider {
model;
constructor(config = { model: null }) {
const client = new AzureOpenAI({
apiKey: process.env.AZURE_OPENAI_KEY,
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
apiVersion: "2024-12-01-preview",
});
super(client);
this.model = config.model ?? process.env.OPEN_MODEL_PREF;
this.verbose = true;
}
get supportsAgentStreaming() {
return true;
}
/**
* Create a completion based on the received messages.
*
* @param messages A list of messages to send to the OpenAI API.
* @param functions
* @returns The completion.
*/
async complete(messages, functions = []) {
try {
const response = await this.client.chat.completions.create({
model: this.model,
stream: false,
messages,
...(Array.isArray(functions) && functions?.length > 0
? { functions }
: {}),
});
// Right now, we only support one completion,
// so we just take the first one in the list
const completion = response.choices[0].message;
const cost = this.getCost(response.usage);
// treat function calls
if (completion.function_call) {
let functionArgs = {};
try {
functionArgs = JSON.parse(completion.function_call.arguments);
} catch (error) {
// call the complete function again in case it gets a json error
return this.complete(
[
...messages,
{
role: "function",
name: completion.function_call.name,
function_call: completion.function_call,
content: error?.message,
},
],
functions
);
}
// console.log(completion, { functionArgs })
return {
textResponse: null,
functionCall: {
name: completion.function_call.name,
arguments: functionArgs,
},
cost,
};
}
return {
textResponse: completion.content,
cost,
};
} catch (error) {
// If invalid Auth error we need to abort because no amount of waiting
// will make auth better.
if (error instanceof AzureOpenAI.AuthenticationError) throw error;
if (
error instanceof AzureOpenAI.RateLimitError ||
error instanceof AzureOpenAI.InternalServerError ||
error instanceof AzureOpenAI.APIError // Also will catch AuthenticationError!!!
) {
throw new RetryError(error.message);
}
throw error;
}
}
/**
* Get the cost of the completion.
* Stubbed since Azure OpenAI has no public cost basis.
*
* @param _usage The completion to get the cost for.
* @returns The cost of the completion.
*/
getCost(_usage) {
return 0;
}
}
module.exports = AzureOpenAiProvider;