fix: transmettre le moteur Référentiel IA
This commit is contained in:
@@ -48,9 +48,10 @@ describe("Référentiel IA", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("transmet une requête OpenAI-compatible à la passerelle sélectionnée", async () => {
|
||||
it("transmet le slug du moteur décidé avant la requête OpenAI-compatible", async () => {
|
||||
let receivedUrl = "";
|
||||
let receivedInit: RequestInit | undefined;
|
||||
let callCount = 0;
|
||||
const result = await invokeReferentialIa(
|
||||
{
|
||||
messages: [{ role: "user", content: "Extrais la facture" }],
|
||||
@@ -58,8 +59,22 @@ describe("Référentiel IA", () => {
|
||||
},
|
||||
connection,
|
||||
async (url, init) => {
|
||||
callCount += 1;
|
||||
receivedUrl = String(url);
|
||||
receivedInit = init;
|
||||
if (callCount === 1) {
|
||||
expect(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/recommendations");
|
||||
expect(JSON.parse(String(receivedInit?.body))).toEqual({
|
||||
useCase: "texte",
|
||||
optimizeCost: true,
|
||||
needsFreeTier: false,
|
||||
includeConnection: false,
|
||||
});
|
||||
return jsonResponse({
|
||||
application: { name: "Démat. Facturation DSI" },
|
||||
decision: { selected: { engine: { name: "SANTINO Expert", slug: "santino-expert" } } },
|
||||
});
|
||||
}
|
||||
return jsonResponse({
|
||||
id: "ref-1",
|
||||
created: 1,
|
||||
@@ -72,12 +87,15 @@ describe("Référentiel IA", () => {
|
||||
expect(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/invoke");
|
||||
expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" });
|
||||
expect(JSON.parse(String(receivedInit?.body))).toMatchObject({
|
||||
engineSlug: "santino-expert",
|
||||
useCase: "texte",
|
||||
payload: {
|
||||
messages: [{ role: "user", content: "Extrais la facture" }],
|
||||
max_tokens: 1200,
|
||||
stream: false,
|
||||
},
|
||||
});
|
||||
expect(callCount).toBe(2);
|
||||
expect(result.choices[0]?.message.content).toBe("{}");
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,16 @@ export type ReferentielIaConnection = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
type ReferentielIaRecommendation = {
|
||||
application?: { name?: unknown };
|
||||
decision?: {
|
||||
selected?: {
|
||||
engine?: { name?: unknown; slug?: unknown };
|
||||
connection?: { model?: unknown };
|
||||
} | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type Fetcher = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
const RECOMMENDATIONS_PATH = "/api/integration/v1/recommendations";
|
||||
@@ -68,6 +78,37 @@ function gatewayError(status: number): Error {
|
||||
return new Error(`Le Référentiel IA est indisponible (HTTP ${status}).`);
|
||||
}
|
||||
|
||||
/** Demande au Référentiel le moteur actuellement décidé pour cette application. */
|
||||
async function getReferentialIaRecommendation(
|
||||
connection: ReferentielIaConnection,
|
||||
fetcher: Fetcher,
|
||||
): Promise<ReferentielIaRecommendation> {
|
||||
const response = await fetcher(`${connection.baseUrl}${RECOMMENDATIONS_PATH}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Bearer ${connection.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
useCase: "texte",
|
||||
optimizeCost: true,
|
||||
needsFreeTier: false,
|
||||
includeConnection: false,
|
||||
}),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
if (!response.ok) throw gatewayError(response.status);
|
||||
return (await readJson(response)) as ReferentielIaRecommendation;
|
||||
}
|
||||
|
||||
function selectedEngineSlug(recommendation: ReferentielIaRecommendation): string {
|
||||
const slug = recommendation.decision?.selected?.engine?.slug;
|
||||
if (typeof slug !== "string" || !slug.trim()) {
|
||||
throw new Error("Le Référentiel IA ne fournit aucun moteur sélectionné pour cette application.");
|
||||
}
|
||||
return slug.trim();
|
||||
}
|
||||
|
||||
/** Exécute l'appel sur le moteur décidé et tracé par le Référentiel IA. */
|
||||
export async function invokeReferentialIa(
|
||||
params: InvokeParams,
|
||||
@@ -75,13 +116,19 @@ export async function invokeReferentialIa(
|
||||
fetcher: Fetcher = fetch,
|
||||
): Promise<InvokeResult> {
|
||||
const config = requireConnection(connection);
|
||||
const recommendation = await getReferentialIaRecommendation(config, fetcher);
|
||||
const engineSlug = selectedEngineSlug(recommendation);
|
||||
const response = await fetcher(`${config.baseUrl}${INVOCATION_PATH}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Bearer ${config.token}`,
|
||||
},
|
||||
body: JSON.stringify({ payload: toGatewayPayload(params) }),
|
||||
body: JSON.stringify({
|
||||
engineSlug,
|
||||
useCase: "texte",
|
||||
payload: toGatewayPayload(params),
|
||||
}),
|
||||
signal: AbortSignal.timeout(120_000),
|
||||
});
|
||||
if (!response.ok) throw gatewayError(response.status);
|
||||
@@ -100,26 +147,7 @@ export async function testReferentialIaConnection(
|
||||
fetcher: Fetcher = fetch,
|
||||
): Promise<ReferentielIaConnectionTest> {
|
||||
const config = requireConnection(connection);
|
||||
const response = await fetcher(`${config.baseUrl}${RECOMMENDATIONS_PATH}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Bearer ${config.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
useCase: "texte",
|
||||
optimizeCost: true,
|
||||
needsFreeTier: false,
|
||||
includeConnection: false,
|
||||
}),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
if (!response.ok) throw gatewayError(response.status);
|
||||
|
||||
const data = (await readJson(response)) as {
|
||||
application?: { name?: unknown };
|
||||
decision?: { selected?: { engine?: { name?: unknown }; connection?: { model?: unknown } } | null } | null;
|
||||
};
|
||||
const data = await getReferentialIaRecommendation(config, fetcher);
|
||||
return {
|
||||
applicationName: typeof data.application?.name === "string" ? data.application.name : null,
|
||||
engineName: typeof data.decision?.selected?.engine?.name === "string" ? data.decision.selected.engine.name : null,
|
||||
|
||||
Reference in New Issue
Block a user