Skip to content

Instantly share code, notes, and snippets.

@revolunet
Last active December 11, 2024 00:18
Show Gist options
  • Save revolunet/17eab2bb3b006e4911a266d95d1dc515 to your computer and use it in GitHub Desktop.
Save revolunet/17eab2bb3b006e4911a266d95d1dc515 to your computer and use it in GitHub Desktop.
Albert API With RAG and JavaScript
// @ts-check
// see also: https://albert.api.etalab.gouv.fr/documentation
import { readFile, readdir } from "fs/promises";
import pAll from "p-all";
const API_KEY = process.env.ALBERT_API_KEY;
const API_URL = "https://albert.api.etalab.gouv.fr";
// see https://albert.api.etalab.gouv.fr/v1/models
const languageModel = "AgentPublic/llama3-instruct-8b";
const embeddingModel = "BAAI/bge-m3";
const headers = {
Authorization: `Bearer ${API_KEY}`,
};
/**
*
* @returns {Promise<{data:{id: string, object: "model", type: "text-generation"|"text-embeddings-inference"|"automatic-speech-recognition", status:"available"}[]}>} list of models
*/
const getModels = () =>
fetch(`${API_URL}/v1/models`, {
method: "GET",
headers: {
...headers,
"Content-Type": "application/json",
},
}).then((r) => r.json());
/**
*
* @param {{name: string, model: string}} param0
* @returns {Promise<string>} Id of the new collection
*/
const createCollection = ({ name, model }) =>
fetch(`${API_URL}/v1/collections`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
},
body: JSON.stringify({ name, model }),
})
.then((r) => r.json())
.then((d) => {
console.log(d);
return d;
})
.then((d) => d.id);
/**
*
* @param {string} somePath
* @returns {string} base file name
*/
const baseName = (somePath) => {
const parts = somePath.split("/");
return parts[parts.length - 1];
};
/**
* Add some file to an albert collection
* @param {{file: Blob, fileName: string, collectionId: string}} param0
* @returns {Promise<string>}
*/
const addFileToCollection = async ({ file, fileName, collectionId }) => {
const formData = new FormData();
formData.append("file", file, fileName);
formData.append("request", JSON.stringify({ collection: collectionId }));
return fetch(`${API_URL}/v1/files`, {
method: "POST",
headers: {
...headers,
},
body: formData,
}).then(async (r) => r.text());
};
const importLocalMarkdown = async (collectionId, filePath) => {
const markdown = (await readFile(filePath)).toString();
return addFileToCollection({
file: new Blob([markdown], { type: "text/markdown" }),
fileName: baseName(filePath),
collectionId,
});
};
// albert API has a low rate-limit
const wait = (r) => new Promise((resolve) => setTimeout(() => resolve(r), 500));
/**
* Import local markdowns files to an albert collection
* @param {string} collectionId The collection to import to
* @param {string} baseDir The local path with some markdown
* @returns
*/
const importLocalMarkdownPath = async (collectionId, baseDir) => {
const files = await readdir(baseDir).then((f) =>
f.filter((n) => n.endsWith(".md")).filter((n) => !n.includes("anct-pni"))
);
// todo: improve headers management
return pAll(
files.map(
(f) => () =>
importLocalMarkdown(collectionId, `${baseDir}/${f}`).then(wait)
),
{ concurrency: 1 }
);
};
/**
*
* @param {{collections: string[], query: string}} param0
* @returns {Promise<{data: {chunk: {content: string, metadata: {document_name: string}}}[]}>} Search API result
*/
const getSearch = ({ collections, query }) =>
fetch(`${API_URL}/v1/search`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
},
body: JSON.stringify({ collections, k: 6, prompt: query }),
}).then((r) => r.json());
/**
*
* @param {{prompt: string}} param0
* @returns {Promise<{model: string, id: string, choices: {message: {content: string}}[]}>} Search API result
*/
const getCompletion = ({ prompt }) =>
fetch(`${API_URL}/v1/chat/completions`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: languageModel,
stream: false,
messages: [{ role: "user", content: prompt }],
}),
}).then((r) => r.json());
/**
* Query Albert
* @param {{query: string, collections: string[]}} param0
* @returns {string}
*/
const askAlbert = async ({ query, collections }) => {
const searchResult = await getSearch({
query,
collections,
});
const prompt = `Réponds à la question suivante en te basant sur les documents ci-dessous : ${query}
Documents :
${searchResult.data.map((c) => c.chunk.content)}`;
const result = await getCompletion({ prompt });
const sources = searchResult.data.map(
(c) =>
`https://beta.gouv.fr/startups/${c.chunk.metadata.document_name.replace(
/\.md/,
".html"
)}`
);
const sourcesList = sources.length
? "Sources:\n" +
Array.from(new Set(sources))
.sort()
.map((s) => `- ${s}`)
.join("\n")
: "";
return `${result.choices[0].message.content}\n\n${sourcesList}`;
};
const collectionId = "1ce00182-f2ef-4810-a913-94823b6d8cb4";
// // create collection
// const collectionId = await createCollection({
// name: "fiches beta.gouv.fr 3",
// model: embeddingModel,
// });
// console.log("collectionId", collectionId);
// // import local files to collection
// await importLocalMarkdownPath(
// collectionId,
// "../beta.gouv.fr/content/_startups"
// );
const queries = [
"C'est quoi domifa ?",
"Comment contacter demarches-simplifiées ?",
"Dans quelle phase est le code du travail numérique ?",
"Quelle sont les objectifs de mes aides reno ?",
"Quelle sont les métriques d'impact de dossier facile ?",
"Quel est le budget de l'annuaire entreprise ?",
];
queries.forEach(async (q) => {
const answer = await askAlbert({ query: q, collections: [collectionId] });
console.log(`\n\n### ${q}\n> ${answer}`);
});

Dans quelle phase est le code du travail numérique ?

Selon les documents, le code du travail numérique est en phase d'acceleration, qui a démarré le 1er janvier 2019.

Sources:

Quel est le budget de l'annuaire entreprise ?

Selon les documents, le budget de l'annuaire entreprise est disponible à l'adresse https://annuaire-entreprises.data.gouv.fr/budget.

Sources:

C'est quoi domifa ?

DomiFa est une Startup d'État qui permet de sécuriser le processus de domiciliation et de libérer du temps pour l'accompagnement social.

Sources:

Comment contacter demarches-simplifiées ?

Selon les documents, il est possible de contacter Demarches-Simplifiées en cliquant sur le lien suivant : https://www.demarches-simplifiees.fr/contact.

Sources:

Quelle sont les objectifs de mes aides reno ?

Selon les documents, les objectifs de Mes Aides Réno sont les suivants :

  1. Fournir aux usagers une estimation fiable et précise pour encourager les projets de rénovation.
  2. Ouvrir et diffuser le plus largement possible les calculs d'éligibilité des aides à la rénovation sur les parcours usagers (interface web et API).
  3. Informer les usagers sur les coûts des principaux gestes de rénovation, en exploitant les données MaprimeRénov'.

En résumé, les objectifs de Mes Aides Réno sont de fournir des informations précises et fiables aux usagers pour les aider à élaborer un projet de rénovation énergétique, en leur fournissant une estimation des aides disponibles et des coûts associés.

Sources:

Quelle sont les métriques d'impact de dossier facile ?

Selon les documents fournis, les métriques d'impact de DossierFacile sont :

  1. Pourcentage de dossiers dématérialisés déposés sur la plateforme par rapport au nombre total de dossiers reçus : cette métrique permettra de mesurer l'adoption de la plateforme par les différents utilisateurs.
  2. Réduction du temps de gestion pour les utilisateurs : cet indicateur est lié à la stratégie de création d'un portail unique de démarche, qui devrait prendre en charge les requérants dès la première requête et leur offrir accès à la vie du dossier.
  3. Réduction du délai des délais d'instructions : ce deuxième indicateur est également lié à la stratégie de portail unique de démarche, qui devrait permettre une simplification des processus et une réduction des délais.
  4. Réduction du temps journalier de gestion des dossiers par les greffes : ce troisième indicateur est lié à la stratégie de portail unique de démarche, qui devrait également améliorer l'efficacité des greffes.
  5. Nombre de décisions prises : ce cinquième indicateur est lié à la mesure d'impact, qui vise à atteindre un objectif de 1 décision prise dans les 6 prochains mois.
  6. Nombre de conseillers utilisateurs réguliers du tableau : ce sixième indicateur est lié à la mesure d'usage, qui vise à atteindre un objectif de 3 connexions par semaine dans les 6 prochains mois.
  7. Nombre d'indicateurs remontés dans le tableau : ce septième indicateur est lié à la mesure d'effort, qui vise à atteindre un objectif de 15 indicateurs par thématique et 3 thématiques.

Ces métriques d'impact permettent de mesurer l'efficacité de DossierFacile en termes de simplification des processus, de réduction des délais et d'amélioration de la gestion des dossiers, ainsi que son impact sur les utilisateurs et les décisions prises.

Sources:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment