Spaces:
Sleeping
Sleeping
import zod from 'https://cdn.jsdelivr.net/npm/zod@4.0.10/+esm' | |
/** | |
* Met en forme le prompt template passé en paramètres avec les arguments | |
* @param {String} template | |
* @param {Object} args | |
*/ | |
export function formatTemplate(template, args) { | |
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => { | |
// 'match' est la correspondance complète (ex: "{nom}") | |
// 'key' est le contenu du premier groupe capturé (ex: "nom") | |
if (key in args) | |
return args[key]; | |
// Si la clé n'est pas trouvée dans args, on laisse le placeholder tel quel. | |
return ""; | |
}); | |
} | |
/** | |
* Recupère le prompt pour la tâche spécifiée. | |
* @param {String} task | |
*/ | |
export async function retrieveTemplate(task) { | |
const req = await fetch(`/prompt/${task}`) | |
return await req.text(); | |
} | |
/** | |
* Lance un deep search sur le serveur pour les topics donnés. | |
* @param {Array} topics | |
*/ | |
export async function performDeepSearch(topics) { | |
const response = await fetch('/solutions/search_prior_art', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ topics: topics }) | |
}); | |
const results = await response.json(); | |
console.log(results); | |
return results.content; | |
} | |
/** | |
* Genère une completion avec le LLM specifié | |
* @param {String} providerUrl - URL du provider du LLM | |
* @param {String} modelName - Nom du modèle à appeler | |
* @param {String} apiKey - API key a utiliser | |
* @param {Array<{role: string, content: string}>} messages - Liste de messages à passer au modèle | |
* @param {Number} temperature - Température à utiliser pour la génération | |
*/ | |
export async function generateCompletion(providerUrl, modelName, apiKey, messages, temperature = 0.5) { | |
const genEndpoint = providerUrl + "/chat/completions" | |
try { | |
const response = await fetch(genEndpoint, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${apiKey}`, // OpenAI-like authorization header | |
}, | |
body: JSON.stringify({ | |
model: modelName, | |
messages: messages, | |
temperature: temperature, | |
}), | |
}); | |
if (!response.ok) { | |
const errorData = await response.json(); | |
throw new Error(`API request failed with status ${response.status}: ${errorData.error?.message || 'Unknown error'}`); | |
} | |
const data = await response.json(); | |
if (data.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content) | |
return data.choices[0].message.content; | |
} catch (error) { | |
console.error("Error calling private LLM :", error); | |
throw error; | |
} | |
} | |
/** | |
* Genère une completion structurée avec le LLM specifié | |
* @param {String} providerUrl - URL du provider du LLM | |
* @param {String} modelName - Nom du modèle à appeler | |
* @param {String} apiKey - API key a utiliser | |
* @param {Array<{role: string, content: string}>} messages - Liste de messages à passer au modèle | |
* @param {Object} schema - Zod schema to use for structured generation | |
* @param {Number} temperature - Température à utiliser pour la génération | |
*/ | |
//TODO: Find the correct args to constrain the LLM to the json schema instead of enforcing json correct parsing | |
export async function generateStructuredCompletion(providerUrl, modelName, apiKey, messages, schema, temperature = 0.5) { | |
const genEndpoint = providerUrl + "/chat/completions"; | |
try { | |
const response = await fetch(genEndpoint, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${apiKey}`, | |
}, | |
body: JSON.stringify({ | |
model: modelName, | |
messages: messages, | |
temperature: temperature, | |
response_format: { type: "json_object" } | |
}), | |
}); | |
if (!response.ok) { | |
const errorData = await response.json(); | |
throw new Error(`API request failed with status ${response.status}: ${errorData.error?.message || 'Unknown error'}`); | |
} | |
const data = await response.json(); | |
console.log(data.choices[0].message.content); | |
// parse json output | |
const parsedJSON = JSON.parse(data.choices[0].message.content.replace('```json', '').replace("```", "")); | |
// validate output with zod | |
const validatedSchema = schema.parse(parsedJSON); | |
return validatedSchema; | |
} catch (error) { | |
console.error("Error calling private LLM :", error); | |
throw error; | |
} | |
} | |
/** | |
* Retrieves a list of available models from an OpenAI-compatible API using fetch. | |
* | |
* @param {string} providerUrl The base URL of the OpenAI-compatible API endpoint (e.g., "http://localhost:8000/v1"). | |
* @param {string} apiKey The API key for authentication. | |
* @returns {Promise<Array<string>>} A promise that resolves with an array of model names, or rejects with an error. | |
*/ | |
export async function getModelList(providerUrl, apiKey) { | |
try { | |
// Construct the full URL for the models endpoint | |
const modelsUrl = `${providerUrl}/models`; | |
console.log(modelsUrl); | |
// Make a GET request to the models endpoint using fetch | |
const response = await fetch(modelsUrl, { | |
method: 'GET', // Explicitly state the method | |
headers: { | |
'Authorization': `Bearer ${apiKey}`, // OpenAI-compatible authorization header | |
'Content-Type': 'application/json', | |
}, | |
}); | |
// Check if the request was successful (status code 200-299) | |
if (!response.ok) { | |
// If the response is not OK, try to get more error details | |
const errorData = await response.json().catch(() => ({})); // Attempt to parse JSON error, fallback to empty object | |
throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || response.statusText}`); | |
} | |
// Parse the JSON response body | |
const data = await response.json(); | |
// The response data structure for OpenAI-compatible APIs usually contains a 'data' array | |
// where each item represents a model and has an 'id' property. | |
if (data && Array.isArray(data.data)) { | |
const allModelNames = data.data.map(model => model.id); | |
// Filter out models containing "embedding" (case-insensitive) | |
const filteredModelNames = allModelNames.filter(modelName => | |
!modelName.toLowerCase().includes('embedding') | |
); | |
return filteredModelNames; | |
} else { | |
// Handle cases where the response format is unexpected | |
throw new Error('Unexpected response format from the API. Could not find model list.'); | |
} | |
} catch (error) { | |
console.error('Error fetching model list:', error.message); | |
// Re-throw the error to allow the caller to handle it | |
throw error; | |
} | |
} | |
// # ========================================================================================== Idea assessment logic ================================================================== | |
// JS schema for the assessment output. | |
// keep in sync with contents of "extract" prompt | |
const StructuredAssessmentOutput = zod.object({ | |
final_verdict: zod.string(), | |
summary: zod.string(), | |
insights: zod.array(zod.string()), | |
}); | |
export async function assessSolution(providerUrl, modelName, apiKey, solution, assessment_rules, portfolio_info) { | |
const template = await retrieveTemplate("assess"); | |
const assessment_template = formatTemplate(template, { | |
notation_criterias: assessment_rules, | |
business: portfolio_info, | |
problem_description: solution.problem_description, | |
solution_description: solution.solution_description, | |
}); | |
const assessment_full = await generateCompletion(providerUrl, modelName, apiKey, [ | |
{ role: "user", content: assessment_template } | |
]); | |
const structured_template = await retrieveTemplate("extract"); | |
const structured_filled_template = formatTemplate(structured_template, { | |
"report": assessment_full, | |
"response_schema": zod.toJSONSchema(StructuredAssessmentOutput) | |
}) | |
const extracted_info = await generateStructuredCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: structured_filled_template }], StructuredAssessmentOutput); | |
return { assessment_full, extracted_info }; | |
} | |
export async function refineSolution(providerUrl, modelName, apiKey, solution, insights, user_insights, assessment_rules, portfolio_info) { | |
const template = await retrieveTemplate("refine"); | |
const refine_template = formatTemplate(template, { | |
"problem_description": solution.problem_description, | |
"solution_description": solution.solution_description, | |
"insights": insights.join("\n -"), | |
"user_insights": user_insights, | |
"business_info": portfolio_info, | |
}); | |
console.log(refine_template); | |
const refined_idea = await generateCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: refine_template }]); | |
const newSolution = structuredClone(solution); | |
newSolution.solution_description = refined_idea; | |
return newSolution; | |
} | |
// FTO analysis | |
// JS schema for FTO analysis topic extraction | |
const FTOAnalysisTopicsSchema = zod.object({ | |
topics: zod.array(zod.string()) | |
}); | |
/** | |
* Extract the topics to search for FTO | |
*/ | |
async function getFtoAnalysisTopics(providerUrl, modelName, apiKey, idea, count) { | |
const template = await retrieveTemplate("fto_topics"); | |
const structured_template = formatTemplate(template, { | |
"problem_description": idea.problem_description, | |
"solution_description": idea.solution_description, | |
"response_schema": zod.toJSONSchema(FTOAnalysisTopicsSchema), | |
"max_topic_count": count | |
}); | |
const topics = await generateStructuredCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: structured_template }], FTOAnalysisTopicsSchema); | |
return topics; | |
} | |
/* | |
* Assess the infringement of the idea wrt | |
*/ | |
async function assessFTOReport(providerUrl, modelName, apiKey, solution, fto_report, portfolio_info) { | |
const template = await retrieveTemplate("fto_assess"); | |
const assessment_template = formatTemplate(template, { | |
business: portfolio_info, | |
fto_report: fto_report, | |
problem_description: solution.problem_description, | |
solution_description: solution.solution_description, | |
}); | |
console.log("FTO Length: " + assessment_template.length); | |
const assessment_full = await generateCompletion(providerUrl, modelName, apiKey, [ | |
{ role: "user", content: assessment_template } | |
]); | |
const structured_template = await retrieveTemplate("extract"); | |
const structured_filled_template = formatTemplate(structured_template, { | |
"report": assessment_full, | |
"response_schema": zod.toJSONSchema(StructuredAssessmentOutput) | |
}) | |
const extracted_info = await generateStructuredCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: structured_filled_template }], StructuredAssessmentOutput); | |
return { assessment_full, extracted_info }; | |
} | |
export async function runFTOAnalysis(providerUrl, providerModel, apiKey, solution, portfolio_info, ftoTopicCount) { | |
const fto_topics = await getFtoAnalysisTopics(providerUrl, providerModel, apiKey, solution, ftoTopicCount); | |
console.log(fto_topics); | |
const fto_report = await performDeepSearch(fto_topics.topics); | |
const assess_results = await assessFTOReport(providerUrl, providerModel, apiKey, solution, fto_report, portfolio_info); | |
console.log(assess_results.extracted_info); | |
return { | |
fto_topics: fto_topics, | |
fto_report: fto_report, | |
assessment_full: assess_results.assessment_full, | |
extracted_info: assess_results.extracted_info, | |
}; | |
} | |