mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
This commit introduces integrations for three new LLM providers: - Azure OpenAI: Leverages the @ai-sdk/openai package for Azure deployments. Configuration includes API Key, Endpoint, Deployment Name, and API Version. - Vertex AI: Utilizes the @ai-sdk/google/vertex package for Google Cloud's Vertex AI models (e.g., Gemini). Configuration includes Project ID and Region, relying on Application Default Credentials for authentication. - Granite AI: Provides a custom implementation using direct fetch calls. Configuration includes API Key and Base URL. The api.llmcall.ts route has been updated to handle this provider's custom generate method. Key changes include: - New provider implementation files in app/lib/modules/llm/providers/. - Updates to app/lib/modules/llm/registry.ts and manager.ts to include the new providers. - Enhancements to app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx to support configuration UI for the new providers, including specific fields like Azure Deployment Name, Vertex Project ID/Region. - Adjustments in app/routes/api.llmcall.ts to accommodate the Granite AI provider's direct fetch implementation alongside SDK-based providers. - Addition of placeholder icons for the new providers. Additionally, this commit includes initial scaffolding for a document upload feature: - A new FileUpload.tsx UI component for selecting files. - A new /api/document-upload API route that acknowledges file uploads but does not yet process or store them. This is a placeholder for future knowledge base integration.
154 lines
5.5 KiB
TypeScript
154 lines
5.5 KiB
TypeScript
import { BaseProvider } from '~/lib/modules/llm/base-provider';
|
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
|
import type { IProviderSetting } from '~/types/model';
|
|
// We are not using a specific AI SDK for Granite, so no 'ai' package imports here for model instantiation.
|
|
|
|
export interface GraniteAIProviderOptions {
|
|
model: string;
|
|
prompt: string;
|
|
stream?: boolean;
|
|
providerSettings?: IProviderSetting; // Re-using IProviderSetting for consistency
|
|
signal?: AbortSignal;
|
|
}
|
|
|
|
export default class GraniteAIProvider extends BaseProvider {
|
|
name = 'GraniteAI';
|
|
// TODO: Update with actual link if available
|
|
getApiKeyLink = 'https://www.ibm.com/granite'; // Placeholder
|
|
|
|
config = {
|
|
apiTokenKey: 'GRANITE_AI_API_KEY',
|
|
baseUrlKey: 'GRANITE_AI_BASE_URL',
|
|
};
|
|
|
|
staticModels: ModelInfo[] = []; // Will be populated by getDynamicModels
|
|
|
|
constructor() {
|
|
super();
|
|
// Constructor is light, config is applied in methods.
|
|
}
|
|
|
|
private getGraniteConfig(settings?: IProviderSetting): {
|
|
apiKey: string;
|
|
baseUrl: string;
|
|
} {
|
|
const apiKey = settings?.apiKey || this.getEnv(this.config.apiTokenKey) || '';
|
|
const baseUrl = settings?.baseUrl || this.getEnv(this.config.baseUrlKey) || '';
|
|
|
|
if (!apiKey) {
|
|
console.warn(`Granite AI API key is missing for provider ${this.name}.`);
|
|
// throw new Error(`Granite AI API key is missing for provider ${this.name}.`);
|
|
}
|
|
if (!baseUrl) {
|
|
console.warn(`Granite AI Base URL is missing for provider ${this.name}.`);
|
|
// throw new Error(`Granite AI Base URL is missing for provider ${this.name}.`);
|
|
}
|
|
|
|
return { apiKey, baseUrl };
|
|
}
|
|
|
|
async getDynamicModels(
|
|
_apiKeys?: Record<string, string>,
|
|
settings?: IProviderSetting,
|
|
_serverEnv?: Record<string, string>,
|
|
): Promise<ModelInfo[]> {
|
|
const config = this.getGraniteConfig(settings);
|
|
// Ensure config is present, even if not used for this hardcoded list yet
|
|
if (!config.apiKey || !config.baseUrl) {
|
|
// Provider not configured, return no models
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
id: 'granite-model-example', // Example model ID
|
|
name: 'Granite Model (Example)',
|
|
provider: this.name,
|
|
maxTokenAllowed: 8000, // Example token limit
|
|
},
|
|
// Add other Granite models if known
|
|
];
|
|
}
|
|
|
|
// This generate method is specific to GraniteAIProvider and uses fetch directly.
|
|
// It does not return a LanguageModelV1 instance from the 'ai' SDK.
|
|
async generate(options: GraniteAIProviderOptions): Promise<string> {
|
|
const { model, prompt, stream, providerSettings, signal } = options;
|
|
|
|
const { apiKey, baseUrl } = this.getGraniteConfig(providerSettings);
|
|
|
|
if (!apiKey || !baseUrl) {
|
|
throw new Error(`Granite AI provider is not configured. Missing API key or base URL.`);
|
|
}
|
|
|
|
// TODO: Confirm the actual API endpoint for Granite AI
|
|
const apiEndpoint = `${baseUrl}/v1/chat/completions`; // Common pattern, adjust if needed
|
|
|
|
const payload = {
|
|
model: model,
|
|
messages: [{ role: 'user', content: prompt }],
|
|
stream: stream || false, // Default to non-streaming
|
|
};
|
|
|
|
// TODO: Implement actual streaming support if required by the application.
|
|
// For now, stream: false is hardcoded in payload effectively,
|
|
// and we will parse a JSON response.
|
|
if (stream) {
|
|
console.warn('GraniteAIProvider: Streaming requested but not fully implemented. Returning non-streamed response.');
|
|
// For true streaming, would return response.body ReadableStream here,
|
|
// and the caller would need to handle it (e.g. using AI SDK's stream processing).
|
|
}
|
|
|
|
const response = await fetch(apiEndpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
signal: signal,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
throw new Error(`Granite AI API request failed with status ${response.status} ${response.statusText}: ${errorBody}`);
|
|
}
|
|
|
|
const jsonResponse = await response.json();
|
|
|
|
// TODO: Adjust based on actual Granite AI response structure.
|
|
// Common paths: choices[0].message.content or choices[0].text
|
|
const messageContent = jsonResponse.choices?.[0]?.message?.content || jsonResponse.choices?.[0]?.text;
|
|
|
|
if (typeof messageContent !== 'string') {
|
|
console.error('Granite AI response format unexpected:', jsonResponse);
|
|
throw new Error('Granite AI provider received an unexpected response format.');
|
|
}
|
|
|
|
return messageContent;
|
|
}
|
|
|
|
// getModelInstance is typically for AI SDK integration.
|
|
// Since Granite is using fetch directly via `generate`, this might not be needed
|
|
// or would need to return a custom object that wraps `generate`.
|
|
// For this subtask, we are focusing on the direct `generate` method.
|
|
/*
|
|
getModelInstance(options: {
|
|
model: string;
|
|
providerSettings?: Record<string, IProviderSetting>;
|
|
}): any { // Return type would need to be compatible with how LLMManager uses it
|
|
// This would need to return an object that has methods expected by the calling code,
|
|
// potentially wrapping the `this.generate` call.
|
|
// For example:
|
|
// return {
|
|
// generate: async (promptContent: string) => this.generate({
|
|
// model: options.model,
|
|
// prompt: promptContent,
|
|
// providerSettings: options.providerSettings?.[this.name]
|
|
// })
|
|
// };
|
|
throw new Error("getModelInstance not implemented for GraniteAIProvider in this manner. Use generate().");
|
|
}
|
|
*/
|
|
}
|