130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
|
|
import type { BtpAiConfig } from "./config";
|
||
|
|
import { safeReadText } from "./http";
|
||
|
|
|
||
|
|
export const DEFAULT_RESOURCE_GROUP = "default";
|
||
|
|
|
||
|
|
/** Famille de modèle : détermine l'API à appeler sur le déploiement. */
|
||
|
|
export type ModelType = "claude" | "openai" | "other";
|
||
|
|
|
||
|
|
export interface Deployment {
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
version: string | null;
|
||
|
|
deploymentUrl: string;
|
||
|
|
type: ModelType;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hypothèse (la fonction n'était pas dans les samples) :
|
||
|
|
* - anthropic--* / claude → Claude (AWS Bedrock, API Converse)
|
||
|
|
* - gpt*, o1/o3/o4… → OpenAI (Azure OpenAI, chat/completions)
|
||
|
|
* - le reste (gemini, embeddings, mistral…) → non pris en charge pour l'instant
|
||
|
|
*/
|
||
|
|
export function detectModelType(name: string): ModelType {
|
||
|
|
const n = name.toLowerCase();
|
||
|
|
if (n.startsWith("anthropic--") || n.includes("claude")) return "claude";
|
||
|
|
if (/^(gpt|chatgpt|o\d)/.test(n)) return "openai";
|
||
|
|
return "other";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Liste les modèles/déploiements disponibles.
|
||
|
|
*
|
||
|
|
* GET {AI_API_URL}/v2/lm/deployments
|
||
|
|
* Headers: Authorization: Bearer {token}, AI-Resource-Group
|
||
|
|
*/
|
||
|
|
export async function listDeployments(config: BtpAiConfig, token: string): Promise<Deployment[]> {
|
||
|
|
const url = `${config.serviceurls.AI_API_URL.replace(/\/+$/, "")}/v2/lm/deployments`;
|
||
|
|
|
||
|
|
const response = await fetch(url, {
|
||
|
|
method: "GET",
|
||
|
|
headers: {
|
||
|
|
Authorization: `Bearer ${token}`,
|
||
|
|
"AI-Resource-Group": config.resourceGroup ?? DEFAULT_RESOURCE_GROUP,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const body = await safeReadText(response);
|
||
|
|
throw new Error(
|
||
|
|
`Échec de récupération de la liste des modèles (HTTP ${response.status} ${response.statusText}). ${body}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const data = (await response.json()) as { resources?: RawDeployment[] };
|
||
|
|
const resources = Array.isArray(data.resources) ? data.resources : [];
|
||
|
|
|
||
|
|
const deployments = resources
|
||
|
|
.filter((r) => r.deploymentUrl)
|
||
|
|
.map((r): Deployment => {
|
||
|
|
const model = extractModelInfo(r);
|
||
|
|
const name = model?.name ?? r.configurationName ?? r.id ?? "(nom inconnu)";
|
||
|
|
return {
|
||
|
|
id: r.id ?? r.deploymentUrl!,
|
||
|
|
name,
|
||
|
|
version: model?.version ?? null,
|
||
|
|
deploymentUrl: r.deploymentUrl!,
|
||
|
|
type: detectModelType(name),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
if (deployments.length === 0) {
|
||
|
|
throw new Error(
|
||
|
|
"Aucun modèle déployé (avec deploymentUrl) n'a été trouvé pour ce service/resource group.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return deployments;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Choisit le déploiement : nom/id exact, sinon nom partiel. Vide ou "auto" → premier
|
||
|
|
* déploiement de chat pris en charge.
|
||
|
|
*/
|
||
|
|
export function findDeployment(deployments: Deployment[], wanted: string): Deployment {
|
||
|
|
const w = wanted.trim().toLowerCase();
|
||
|
|
const usable = deployments.filter((d) => d.type !== "other");
|
||
|
|
|
||
|
|
if (!w || w === "auto") {
|
||
|
|
const first = usable[0];
|
||
|
|
if (!first) {
|
||
|
|
throw new Error("Aucun déploiement de chat pris en charge (Claude ou GPT) n'a été trouvé.");
|
||
|
|
}
|
||
|
|
return first;
|
||
|
|
}
|
||
|
|
|
||
|
|
const found =
|
||
|
|
usable.find((d) => d.name.toLowerCase() === w || d.id.toLowerCase() === w) ??
|
||
|
|
usable.find((d) => d.name.toLowerCase().includes(w));
|
||
|
|
if (!found) {
|
||
|
|
throw new Error(
|
||
|
|
`Modèle "${wanted}" introuvable. Disponibles : ${usable.map((d) => d.name).join(", ") || "(aucun)"}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return found;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface RawModel {
|
||
|
|
name?: string;
|
||
|
|
version?: string | null;
|
||
|
|
}
|
||
|
|
interface RawDeployment {
|
||
|
|
id?: string;
|
||
|
|
deploymentUrl?: string;
|
||
|
|
configurationName?: string;
|
||
|
|
details?: { resources?: { backend_details?: { model?: RawModel }; backendDetails?: { model?: RawModel } } };
|
||
|
|
backend_details?: { model?: RawModel };
|
||
|
|
backendDetails?: { model?: RawModel };
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Chemin confirmé "details.resources.backend_details.model", avec variantes de repli. */
|
||
|
|
function extractModelInfo(r: RawDeployment): { name: string; version: string | null } | null {
|
||
|
|
const candidates = [
|
||
|
|
r.details?.resources?.backend_details?.model,
|
||
|
|
r.details?.resources?.backendDetails?.model,
|
||
|
|
r.backend_details?.model,
|
||
|
|
r.backendDetails?.model,
|
||
|
|
];
|
||
|
|
const found = candidates.find((c) => c?.name);
|
||
|
|
return found?.name ? { name: found.name, version: found.version ?? null } : null;
|
||
|
|
}
|