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 receivedUrl = "";
|
||||||
let receivedInit: RequestInit | undefined;
|
let receivedInit: RequestInit | undefined;
|
||||||
|
let callCount = 0;
|
||||||
const result = await invokeReferentialIa(
|
const result = await invokeReferentialIa(
|
||||||
{
|
{
|
||||||
messages: [{ role: "user", content: "Extrais la facture" }],
|
messages: [{ role: "user", content: "Extrais la facture" }],
|
||||||
@@ -58,8 +59,22 @@ describe("Référentiel IA", () => {
|
|||||||
},
|
},
|
||||||
connection,
|
connection,
|
||||||
async (url, init) => {
|
async (url, init) => {
|
||||||
|
callCount += 1;
|
||||||
receivedUrl = String(url);
|
receivedUrl = String(url);
|
||||||
receivedInit = init;
|
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({
|
return jsonResponse({
|
||||||
id: "ref-1",
|
id: "ref-1",
|
||||||
created: 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(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/invoke");
|
||||||
expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" });
|
expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" });
|
||||||
expect(JSON.parse(String(receivedInit?.body))).toMatchObject({
|
expect(JSON.parse(String(receivedInit?.body))).toMatchObject({
|
||||||
|
engineSlug: "santino-expert",
|
||||||
|
useCase: "texte",
|
||||||
payload: {
|
payload: {
|
||||||
messages: [{ role: "user", content: "Extrais la facture" }],
|
messages: [{ role: "user", content: "Extrais la facture" }],
|
||||||
max_tokens: 1200,
|
max_tokens: 1200,
|
||||||
stream: false,
|
stream: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
expect(callCount).toBe(2);
|
||||||
expect(result.choices[0]?.message.content).toBe("{}");
|
expect(result.choices[0]?.message.content).toBe("{}");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ export type ReferentielIaConnection = {
|
|||||||
token: string;
|
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>;
|
type Fetcher = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
||||||
|
|
||||||
const RECOMMENDATIONS_PATH = "/api/integration/v1/recommendations";
|
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}).`);
|
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. */
|
/** Exécute l'appel sur le moteur décidé et tracé par le Référentiel IA. */
|
||||||
export async function invokeReferentialIa(
|
export async function invokeReferentialIa(
|
||||||
params: InvokeParams,
|
params: InvokeParams,
|
||||||
@@ -75,13 +116,19 @@ export async function invokeReferentialIa(
|
|||||||
fetcher: Fetcher = fetch,
|
fetcher: Fetcher = fetch,
|
||||||
): Promise<InvokeResult> {
|
): Promise<InvokeResult> {
|
||||||
const config = requireConnection(connection);
|
const config = requireConnection(connection);
|
||||||
|
const recommendation = await getReferentialIaRecommendation(config, fetcher);
|
||||||
|
const engineSlug = selectedEngineSlug(recommendation);
|
||||||
const response = await fetcher(`${config.baseUrl}${INVOCATION_PATH}`, {
|
const response = await fetcher(`${config.baseUrl}${INVOCATION_PATH}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
authorization: `Bearer ${config.token}`,
|
authorization: `Bearer ${config.token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ payload: toGatewayPayload(params) }),
|
body: JSON.stringify({
|
||||||
|
engineSlug,
|
||||||
|
useCase: "texte",
|
||||||
|
payload: toGatewayPayload(params),
|
||||||
|
}),
|
||||||
signal: AbortSignal.timeout(120_000),
|
signal: AbortSignal.timeout(120_000),
|
||||||
});
|
});
|
||||||
if (!response.ok) throw gatewayError(response.status);
|
if (!response.ok) throw gatewayError(response.status);
|
||||||
@@ -100,26 +147,7 @@ export async function testReferentialIaConnection(
|
|||||||
fetcher: Fetcher = fetch,
|
fetcher: Fetcher = fetch,
|
||||||
): Promise<ReferentielIaConnectionTest> {
|
): Promise<ReferentielIaConnectionTest> {
|
||||||
const config = requireConnection(connection);
|
const config = requireConnection(connection);
|
||||||
const response = await fetcher(`${config.baseUrl}${RECOMMENDATIONS_PATH}`, {
|
const data = await getReferentialIaRecommendation(config, fetcher);
|
||||||
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;
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
applicationName: typeof data.application?.name === "string" ? data.application.name : null,
|
applicationName: typeof data.application?.name === "string" ? data.application.name : null,
|
||||||
engineName: typeof data.decision?.selected?.engine?.name === "string" ? data.decision.selected.engine.name : null,
|
engineName: typeof data.decision?.selected?.engine?.name === "string" ? data.decision.selected.engine.name : null,
|
||||||
|
|||||||
Reference in New Issue
Block a user