Lucas ARRIESSE commited on
Commit
46d8b46
·
1 Parent(s): f6bffda

Remove old endpoints

Browse files
api/solutions.py CHANGED
@@ -7,13 +7,13 @@ from jinja2 import Environment
7
  from litellm.router import Router
8
  from dependencies import INSIGHT_FINDER_BASE_URL, get_http_client, get_llm_router, get_prompt_templates
9
  from typing import Awaitable, Callable, TypeVar
10
- from schemas import _RefinedSolutionModel, _ReqGroupingCategory, _ReqGroupingOutput, _SearchedSolutionModel, _SolutionCriticismOutput, CriticizeSolutionsRequest, CritiqueResponse, InsightFinderConstraintsList, ReqGroupingCategory, ReqGroupingRequest, ReqGroupingResponse, ReqSearchLLMResponse, ReqSearchRequest, ReqSearchResponse, SolutionCriticism, SolutionModel, SolutionSearchResponse, SolutionSearchV2Request, TechnologyData
11
 
12
  # Router for solution generation and critique
13
  router = APIRouter(tags=["solution generation and critique"])
14
 
15
 
16
- # ============== utilities =======================
17
  T = TypeVar("T")
18
  A = TypeVar("A")
19
 
@@ -32,68 +32,65 @@ async def retry_until(
32
  last_value = await func(arg)
33
  return last_value
34
 
35
- # =================================================
36
 
 
 
37
 
38
- @router.post("/search_solutions_gemini/v2", response_model=SolutionSearchResponse)
39
- async def search_solutions(params: SolutionSearchV2Request, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router)) -> SolutionSearchResponse:
40
- """Searches solutions solving the given grouping params and respecting the user constraints using Gemini and grounded on google search"""
41
-
42
- logging.info(f"Searching solutions for categories: {params}")
 
 
 
 
 
43
 
44
- async def _search_inner(cat: ReqGroupingCategory) -> SolutionModel:
45
- # ================== generate the solution with web grounding
46
- req_prompt = await prompt_env.get_template("search_solution_v2.txt").render_async(**{
47
- "category": cat.model_dump(),
48
- "user_constraints": params.user_constraints,
49
- })
50
 
51
- # generate the completion in non-structured mode.
52
- # the googleSearch tool enables grounding gemini with google search
53
- # this also forces gemini to perform a tool call
54
- req_completion = await llm_router.acompletion(model="gemini-v2", messages=[
55
- {"role": "user", "content": req_prompt}
56
- ], tools=[{"googleSearch": {}}], tool_choice="required")
57
 
58
- # ==================== structure the solution as a json ===================================
 
 
59
 
60
- structured_prompt = await prompt_env.get_template("structure_solution.txt").render_async(**{
61
- "solution": req_completion.choices[0].message.content,
62
- "response_schema": _SearchedSolutionModel.model_json_schema()
63
- })
64
 
65
- structured_completion = await llm_router.acompletion(model="gemini-v2", messages=[
66
- {"role": "user", "content": structured_prompt}
 
 
 
 
 
 
67
  ], response_format=_SearchedSolutionModel)
68
- solution_model = _SearchedSolutionModel.model_validate_json(
69
- structured_completion.choices[0].message.content)
70
-
71
- # ======================== build the final solution object ================================
72
 
73
- sources_metadata = []
74
- # extract the source metadata from the search items, if gemini actually called the tools to search .... and didn't hallucinated
75
- try:
76
- sources_metadata.extend([{"name": a["web"]["title"], "url": a["web"]["uri"]}
77
- for a in req_completion["vertex_ai_grounding_metadata"][0]['groundingChunks']])
78
- except KeyError as ke:
79
- pass
80
 
81
- final_sol = SolutionModel(
82
  Context="",
83
  Requirements=[
84
- cat.requirements[i].requirement for i in solution_model.requirement_ids
85
  ],
86
- Problem_Description=solution_model.problem_description,
87
- Solution_Description=solution_model.solution_description,
88
- References=sources_metadata,
89
  Category_Id=cat.id,
90
  )
91
- return final_sol
92
 
93
- solutions = await asyncio.gather(*[retry_until(_search_inner, cat, lambda v: len(v.References) > 0, 2) for cat in params.categories], return_exceptions=True)
94
- logging.info(solutions)
95
- final_solutions = [
96
- sol for sol in solutions if not isinstance(sol, Exception)]
 
 
97
 
98
  return SolutionSearchResponse(solutions=final_solutions)
99
 
@@ -155,65 +152,3 @@ async def refine_solutions(params: CritiqueResponse, prompt_env: Environment = D
155
 
156
  return SolutionSearchResponse(solutions=refined_solutions)
157
 
158
- # =============================================================== Search solutions =========================================
159
-
160
-
161
- @router.post("/search_solutions_if")
162
- async def search_solutions_if(req: SolutionSearchV2Request, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router), http_client: AsyncClient = Depends(get_http_client)) -> SolutionSearchResponse:
163
-
164
- async def _search_solution_inner(cat: ReqGroupingCategory):
165
- # process requirements into insight finder format
166
- fmt_completion = await llm_router.acompletion("gemini-v2", messages=[
167
- {
168
- "role": "user",
169
- "content": await prompt_env.get_template("if/format_requirements.txt").render_async(**{
170
- "category": cat.model_dump(),
171
- "response_schema": InsightFinderConstraintsList.model_json_schema()
172
- })
173
- }], response_format=InsightFinderConstraintsList)
174
-
175
- fmt_model = InsightFinderConstraintsList.model_validate_json(
176
- fmt_completion.choices[0].message.content)
177
-
178
- # translate from a structured output to a dict for insights finder
179
- formatted_constraints = {'constraints': {
180
- cons.title: cons.description for cons in fmt_model.constraints}}
181
-
182
- # fetch technologies from insight finder
183
- technologies_req = await http_client.post(INSIGHT_FINDER_BASE_URL + "process-constraints", content=json.dumps(formatted_constraints))
184
- technologies = TechnologyData.model_validate(technologies_req.json())
185
-
186
- # =============================================================== synthesize solution using LLM =========================================
187
-
188
- format_solution = await llm_router.acompletion("gemini-v2", messages=[{
189
- "role": "user",
190
- "content": await prompt_env.get_template("if/synthesize_solution.txt").render_async(**{
191
- "category": cat.model_dump(),
192
- "technologies": technologies.model_dump()["technologies"],
193
- "user_constraints": req.user_constraints,
194
- "response_schema": _SearchedSolutionModel.model_json_schema()
195
- })}
196
- ], response_format=_SearchedSolutionModel)
197
-
198
- format_solution_model = _SearchedSolutionModel.model_validate_json(
199
- format_solution.choices[0].message.content)
200
-
201
- final_solution = SolutionModel(
202
- Context="",
203
- Requirements=[
204
- cat.requirements[i].requirement for i in format_solution_model.requirement_ids
205
- ],
206
- Problem_Description=format_solution_model.problem_description,
207
- Solution_Description=format_solution_model.solution_description,
208
- References=[],
209
- Category_Id=cat.id,
210
- )
211
-
212
- # ========================================================================================================================================
213
-
214
- return final_solution
215
-
216
- tasks = await asyncio.gather(*[_search_solution_inner(cat) for cat in req.categories], return_exceptions=True)
217
- final_solutions = [sol for sol in tasks if not isinstance(sol, Exception)]
218
-
219
- return SolutionSearchResponse(solutions=final_solutions)
 
7
  from litellm.router import Router
8
  from dependencies import INSIGHT_FINDER_BASE_URL, get_http_client, get_llm_router, get_prompt_templates
9
  from typing import Awaitable, Callable, TypeVar
10
+ from schemas import _RefinedSolutionModel, _SearchedSolutionModel, _SolutionCriticismOutput, CriticizeSolutionsRequest, CritiqueResponse, InsightFinderConstraintsList, ReqGroupingCategory, ReqGroupingRequest, ReqGroupingResponse, ReqSearchLLMResponse, ReqSearchRequest, ReqSearchResponse, SolutionCriticism, SolutionModel, SolutionSearchResponse, SolutionSearchV2Request, TechnologyData
11
 
12
  # Router for solution generation and critique
13
  router = APIRouter(tags=["solution generation and critique"])
14
 
15
 
16
+ # ============== utilities ===========================
17
  T = TypeVar("T")
18
  A = TypeVar("A")
19
 
 
32
  last_value = await func(arg)
33
  return last_value
34
 
35
+ # =================================================== Search solutions ============================================================================
36
 
37
+ @router.post("/search_solutions")
38
+ async def search_solutions_if(req: SolutionSearchV2Request, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router), http_client: AsyncClient = Depends(get_http_client)) -> SolutionSearchResponse:
39
 
40
+ async def _search_solution_inner(cat: ReqGroupingCategory):
41
+ # process requirements into insight finder format
42
+ fmt_completion = await llm_router.acompletion("gemini-v2", messages=[
43
+ {
44
+ "role": "user",
45
+ "content": await prompt_env.get_template("format_requirements.txt").render_async(**{
46
+ "category": cat.model_dump(),
47
+ "response_schema": InsightFinderConstraintsList.model_json_schema()
48
+ })
49
+ }], response_format=InsightFinderConstraintsList)
50
 
51
+ fmt_model = InsightFinderConstraintsList.model_validate_json(
52
+ fmt_completion.choices[0].message.content)
 
 
 
 
53
 
54
+ # translate from a structured output to a dict for insights finder
55
+ formatted_constraints = {'constraints': {
56
+ cons.title: cons.description for cons in fmt_model.constraints}}
 
 
 
57
 
58
+ # fetch technologies from insight finder
59
+ technologies_req = await http_client.post(INSIGHT_FINDER_BASE_URL + "process-constraints", content=json.dumps(formatted_constraints))
60
+ technologies = TechnologyData.model_validate(technologies_req.json())
61
 
62
+ # =============================================================== synthesize solution using LLM =========================================
 
 
 
63
 
64
+ format_solution = await llm_router.acompletion("gemini-v2", messages=[{
65
+ "role": "user",
66
+ "content": await prompt_env.get_template("synthesize_solution.txt").render_async(**{
67
+ "category": cat.model_dump(),
68
+ "technologies": technologies.model_dump()["technologies"],
69
+ "user_constraints": req.user_constraints,
70
+ "response_schema": _SearchedSolutionModel.model_json_schema()
71
+ })}
72
  ], response_format=_SearchedSolutionModel)
 
 
 
 
73
 
74
+ format_solution_model = _SearchedSolutionModel.model_validate_json(
75
+ format_solution.choices[0].message.content)
 
 
 
 
 
76
 
77
+ final_solution = SolutionModel(
78
  Context="",
79
  Requirements=[
80
+ cat.requirements[i].requirement for i in format_solution_model.requirement_ids
81
  ],
82
+ Problem_Description=format_solution_model.problem_description,
83
+ Solution_Description=format_solution_model.solution_description,
84
+ References=[],
85
  Category_Id=cat.id,
86
  )
 
87
 
88
+ # ========================================================================================================================================
89
+
90
+ return final_solution
91
+
92
+ tasks = await asyncio.gather(*[_search_solution_inner(cat) for cat in req.categories], return_exceptions=True)
93
+ final_solutions = [sol for sol in tasks if not isinstance(sol, Exception)]
94
 
95
  return SolutionSearchResponse(solutions=final_solutions)
96
 
 
152
 
153
  return SolutionSearchResponse(solutions=refined_solutions)
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prompts/{if/format_requirements.txt → format_requirements.txt} RENAMED
File without changes
prompts/search_solution.txt DELETED
@@ -1,23 +0,0 @@
1
- <role>You are an expert system designer</role>
2
- <task>
3
- Your task is to create a solution that should cover the maximum possible of requirements at once.
4
- The solution should be composed of multiple mechanisms, that are defined as processes, methods, or sequences of steps using a technology that explain how something works or achieves its requirements.
5
- You may for that search mechanisms for the different requirements.
6
- **Please actually make searches and do not simulate them.**
7
-
8
- You must aim for the following goals:
9
- - The solution must aim to maximize requirement satisfaction while respecting the context.
10
- - Provide a list of requirements addressed by the solution (provide only the requirement IDs)
11
- - Please also detail each mechanism and how they work in the final solution description.
12
- - Please describe the solution description using markdown in a consistent format.
13
- </task>
14
-
15
- Here is the category item and the associated requirements:
16
- <requirements>
17
- Category Title: {{category["title"]}}
18
- Context: {{category["requirements"][0]["context"]}}
19
- Requirements:
20
- {% for req in category["requirements"] -%}
21
- - {{loop.index0}} {{req["requirement"]}}
22
- {% endfor -%}
23
- </requirements>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prompts/search_solution_v2.txt DELETED
@@ -1,31 +0,0 @@
1
- <role>You are an expert system designer</role>
2
- <task>
3
- Your task is to create a solution that should cover the maximum possible of requirements at once.
4
- The solution should be composed of multiple mechanisms, that are defined as processes, methods, or sequences of steps using a technology that explain how something works or achieves its requirements.
5
- You may for that search mechanisms for the different requirements.
6
-
7
- **Please actually make searches and do not simulate them.**
8
-
9
- You must aim for the following goals:
10
- - The solution must aim to maximize requirement satisfaction while respecting the context.
11
- - Provide a list of requirements addressed by the solution (provide only the requirement IDs)
12
- - Please also detail each mechanism and how they work in the final solution description.
13
- - Please describe the solution description using markdown in a consistent format.
14
- </task>
15
-
16
- Here is the category item and the associated requirements:
17
- <requirements>
18
- Category Title: {{category["title"]}}
19
- Context: {{category["requirements"][0]["context"]}}
20
- Requirements:
21
- {% for req in category["requirements"] -%}
22
- - {{loop.index0}} {{req["requirement"]}}
23
- {% endfor -%}
24
- </requirements>
25
-
26
- {% if user_constraints is not none %}
27
- Here are additional user constraints the solution must respect:
28
- <user_constraints>
29
- {{user_constraints}}
30
- </user_constraints>
31
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prompts/{if/synthesize_solution.txt → synthesize_solution.txt} RENAMED
File without changes
static/index.html CHANGED
@@ -270,7 +270,7 @@
270
  Categorize
271
  </button>
272
  </div>
273
- <div class="flex flex-wrap justify-end items-center gap-6">
274
  <div class="tooltip" data-tip="Use Insight Finder's API to generate solutions">
275
  <label class="label">
276
  <span class="label-text text-base-content">Use insight finder solver</span>
@@ -278,7 +278,7 @@
278
  <input type="checkbox" class="toggle toggle-primary" id="use-insight-finder-solver"
279
  checked="false" />
280
  </div>
281
- </div>
282
  </div>
283
  <div id="categorized-requirements-container pt-10" class="mb-6">
284
  <div class="flex mb-4 pt-10">
 
270
  Categorize
271
  </button>
272
  </div>
273
+ <!-- <div class="flex flex-wrap justify-end items-center gap-6">
274
  <div class="tooltip" data-tip="Use Insight Finder's API to generate solutions">
275
  <label class="label">
276
  <span class="label-text text-base-content">Use insight finder solver</span>
 
278
  <input type="checkbox" class="toggle toggle-primary" id="use-insight-finder-solver"
279
  checked="false" />
280
  </div>
281
+ </div> -->
282
  </div>
283
  <div id="categorized-requirements-container pt-10" class="mb-6">
284
  <div class="flex mb-4 pt-10">
static/js/script.js CHANGED
@@ -335,13 +335,6 @@ async function extractRequirements() {
335
  }
336
  });
337
 
338
-
339
- // const response = await fetch('/generate_requirements/', {
340
- // method: 'POST',
341
- // headers: { 'Content-Type': 'application/json' },
342
- // body: req
343
- // });
344
-
345
  const data = response.data; // data in the SSE message contains the requirements response
346
  requirements = data.requirements;
347
  let req_id = 0;
@@ -1047,33 +1040,30 @@ function clearAllSolutions() {
1047
  document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
1048
  }
1049
 
1050
- async function generateSolutionsV1(selected_categories, user_constraints = null, useIFSolver = false) {
1051
  console.log(selected_categories);
1052
 
1053
  let input_req = structuredClone(selected_categories);
1054
  input_req.user_constraints = user_constraints;
1055
- console.log(useIFSolver);
1056
-
1057
- const base_url = useIFSolver ? "/solutions/search_solutions_if" : "/solutions/search_solutions_gemini/v2"
1058
 
1059
- let response = await fetch(base_url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input_req) })
1060
  let responseObj = await response.json()
1061
  return responseObj;
1062
  }
1063
 
1064
- async function generateCriticismsV1(solutions) {
1065
  let response = await fetch('/solutions/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
1066
  let responseObj = await response.json()
1067
  solutionsCriticizedVersions.push(responseObj)
1068
  }
1069
 
1070
- async function refineSolutionsV1(critiques) {
1071
  let response = await fetch('/solutions/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
1072
  let responseObj = await response.json()
1073
- await generateCriticismsV1(responseObj)
1074
  }
1075
 
1076
- async function workflow(steps = 1, useIFSolver = false) {
1077
  let soluce;
1078
  showLoadingOverlay('Génération des solutions & critiques ....');
1079
 
@@ -1093,11 +1083,11 @@ async function workflow(steps = 1, useIFSolver = false) {
1093
  }
1094
 
1095
  if (solutionsCriticizedVersions.length == 0) {
1096
- soluce = await generateSolutionsV1(selected_requirements, user_constraints ? user_constraints : null, useIFSolver);
1097
- await generateCriticismsV1(soluce)
1098
  } else {
1099
  let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
1100
- await refineSolutionsV1(prevSoluce)
1101
  }
1102
  }
1103
  hideLoadingOverlay();
@@ -1129,10 +1119,10 @@ document.addEventListener('DOMContentLoaded', function () {
1129
  // Événements pour les boutons de solutions (à implémenter plus tard)
1130
  document.getElementById('get-solutions-btn').addEventListener('click', () => {
1131
  const n_steps = document.getElementById('solution-gen-nsteps').value;
1132
- workflow(n_steps, document.getElementById('use-insight-finder-solver').checked);
1133
  });
1134
- document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
1135
- workflow(1, document.getElementById('use-insight-finder-solver').checked);
1136
  });
1137
  });
1138
 
 
335
  }
336
  });
337
 
 
 
 
 
 
 
 
338
  const data = response.data; // data in the SSE message contains the requirements response
339
  requirements = data.requirements;
340
  let req_id = 0;
 
1040
  document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
1041
  }
1042
 
1043
+ async function generateSolutions(selected_categories, user_constraints = null) {
1044
  console.log(selected_categories);
1045
 
1046
  let input_req = structuredClone(selected_categories);
1047
  input_req.user_constraints = user_constraints;
 
 
 
1048
 
1049
+ let response = await fetch("/solutions/search_solutions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input_req) })
1050
  let responseObj = await response.json()
1051
  return responseObj;
1052
  }
1053
 
1054
+ async function generateCriticisms(solutions) {
1055
  let response = await fetch('/solutions/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
1056
  let responseObj = await response.json()
1057
  solutionsCriticizedVersions.push(responseObj)
1058
  }
1059
 
1060
+ async function refineSolutions(critiques) {
1061
  let response = await fetch('/solutions/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
1062
  let responseObj = await response.json()
1063
+ await generateCriticisms(responseObj)
1064
  }
1065
 
1066
+ async function workflow(steps = 1) {
1067
  let soluce;
1068
  showLoadingOverlay('Génération des solutions & critiques ....');
1069
 
 
1083
  }
1084
 
1085
  if (solutionsCriticizedVersions.length == 0) {
1086
+ soluce = await generateSolutions(selected_requirements, user_constraints ? user_constraints : null);
1087
+ await generateCriticisms(soluce)
1088
  } else {
1089
  let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
1090
+ await refineSolutions(prevSoluce)
1091
  }
1092
  }
1093
  hideLoadingOverlay();
 
1119
  // Événements pour les boutons de solutions (à implémenter plus tard)
1120
  document.getElementById('get-solutions-btn').addEventListener('click', () => {
1121
  const n_steps = document.getElementById('solution-gen-nsteps').value;
1122
+ workflow(n_steps);
1123
  });
1124
+ document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
1125
+ workflow(1);
1126
  });
1127
  });
1128