Lucas ARRIESSE
commited on
Commit
·
36dc4ec
1
Parent(s):
ae51a9d
Add endpoints to use Insight Finder API
Browse files- app.py +71 -1
- prompts/if/format_requirements.txt +22 -0
- prompts/if/synthesize_solution.txt +43 -0
- schemas.py +24 -0
app.py
CHANGED
@@ -4,11 +4,12 @@ import os
|
|
4 |
import sys
|
5 |
import uvicorn
|
6 |
from fastapi import APIRouter, FastAPI
|
7 |
-
from schemas import _RefinedSolutionModel, _SearchedSolutionModel, _SolutionCriticismOutput, CriticizeSolutionsRequest, CritiqueResponse, RequirementInfo, ReqGroupingCategory, ReqGroupingResponse, ReqGroupingRequest, _ReqGroupingCategory, _ReqGroupingOutput, SolutionCriticism, SolutionModel, SolutionSearchResponse, SolutionSearchV2Request
|
8 |
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
9 |
from litellm.router import Router
|
10 |
from dotenv import load_dotenv
|
11 |
from util import retry_until
|
|
|
12 |
|
13 |
logging.basicConfig(
|
14 |
level=logging.INFO,
|
@@ -40,6 +41,11 @@ llm_router = Router(model_list=[
|
|
40 |
}
|
41 |
], num_retries=10, retry_after=30)
|
42 |
|
|
|
|
|
|
|
|
|
|
|
43 |
# Jinja2 environment to load prompt templates
|
44 |
prompt_env = Environment(loader=FileSystemLoader(
|
45 |
'prompts'), enable_async=True, undefined=StrictUndefined)
|
@@ -54,6 +60,11 @@ requirements_router = APIRouter(prefix="/reqs", tags=["requirements"])
|
|
54 |
solution_router = APIRouter(prefix="/solution", tags=["solution"])
|
55 |
|
56 |
|
|
|
|
|
|
|
|
|
|
|
57 |
@requirements_router.post("/categorize_requirements")
|
58 |
async def categorize_reqs(params: ReqGroupingRequest) -> ReqGroupingResponse:
|
59 |
"""Categorize the given service requirements into categories"""
|
@@ -300,6 +311,65 @@ async def refine_solutions(params: CritiqueResponse) -> SolutionSearchResponse:
|
|
300 |
return SolutionSearchResponse(solutions=refined_solutions)
|
301 |
|
302 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
api.include_router(requirements_router)
|
304 |
api.include_router(solution_router)
|
305 |
|
|
|
4 |
import sys
|
5 |
import uvicorn
|
6 |
from fastapi import APIRouter, FastAPI
|
7 |
+
from schemas import _RefinedSolutionModel, _SearchedSolutionModel, _SolutionCriticismOutput, CriticizeSolutionsRequest, CritiqueResponse, InsightFinderConstraintsList, RequirementInfo, ReqGroupingCategory, ReqGroupingResponse, ReqGroupingRequest, _ReqGroupingCategory, _ReqGroupingOutput, SolutionCriticism, SolutionModel, SolutionSearchResponse, SolutionSearchV2Request, TechnologyData
|
8 |
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
9 |
from litellm.router import Router
|
10 |
from dotenv import load_dotenv
|
11 |
from util import retry_until
|
12 |
+
from httpx import AsyncClient
|
13 |
|
14 |
logging.basicConfig(
|
15 |
level=logging.INFO,
|
|
|
41 |
}
|
42 |
], num_retries=10, retry_after=30)
|
43 |
|
44 |
+
# HTTP client
|
45 |
+
INSIGHT_FINDER_BASE_URL = "https://organizedprogrammers-insight-finder.hf.space/"
|
46 |
+
http_client = AsyncClient(verify=os.environ.get(
|
47 |
+
"NO_SSL", "0") == "1", timeout=None)
|
48 |
+
|
49 |
# Jinja2 environment to load prompt templates
|
50 |
prompt_env = Environment(loader=FileSystemLoader(
|
51 |
'prompts'), enable_async=True, undefined=StrictUndefined)
|
|
|
60 |
solution_router = APIRouter(prefix="/solution", tags=["solution"])
|
61 |
|
62 |
|
63 |
+
async def format_prompt(prompt_name: str, **args) -> str:
|
64 |
+
"""Helper to format a prompt"""
|
65 |
+
return await prompt_env.get_template(prompt_name).render_async(args)
|
66 |
+
|
67 |
+
|
68 |
@requirements_router.post("/categorize_requirements")
|
69 |
async def categorize_reqs(params: ReqGroupingRequest) -> ReqGroupingResponse:
|
70 |
"""Categorize the given service requirements into categories"""
|
|
|
311 |
return SolutionSearchResponse(solutions=refined_solutions)
|
312 |
|
313 |
|
314 |
+
# ======================================== Solution generation using Insights Finder ==================
|
315 |
+
|
316 |
+
@solution_router.post("/search_solutions_if")
|
317 |
+
async def search_solutions_if(req: SolutionSearchV2Request) -> SolutionSearchResponse:
|
318 |
+
|
319 |
+
async def _search_solution_inner(cat: ReqGroupingCategory):
|
320 |
+
# process requirements into insight finder format
|
321 |
+
fmt_completion = await llm_router.acompletion("chat", messages=[
|
322 |
+
{
|
323 |
+
"role": "user",
|
324 |
+
"content": await format_prompt("if/format_requirements.txt", **{
|
325 |
+
"category": cat.model_dump(),
|
326 |
+
"response_schema": InsightFinderConstraintsList.model_json_schema()
|
327 |
+
})
|
328 |
+
}], response_format=InsightFinderConstraintsList)
|
329 |
+
|
330 |
+
fmt_model = InsightFinderConstraintsList.model_validate_json(
|
331 |
+
fmt_completion.choices[0].message.content)
|
332 |
+
|
333 |
+
# fetch technologies from insight finder
|
334 |
+
technologies_req = await http_client.post(INSIGHT_FINDER_BASE_URL + "process-constraints", content=fmt_model.model_dump_json())
|
335 |
+
technologies = TechnologyData.model_validate(technologies_req.json())
|
336 |
+
|
337 |
+
# =============================================================== synthesize solution using LLM =========================================
|
338 |
+
|
339 |
+
format_solution = await llm_router.acompletion("chat", messages=[{
|
340 |
+
"role": "user",
|
341 |
+
"content": await format_prompt("if/synthesize_solution.txt", **{
|
342 |
+
"category": cat.model_dump(),
|
343 |
+
"technologies": technologies.model_dump()["technologies"],
|
344 |
+
"user_constraints": None,
|
345 |
+
"response_schema": _SearchedSolutionModel.model_json_schema()
|
346 |
+
})}
|
347 |
+
], response_format=_SearchedSolutionModel)
|
348 |
+
|
349 |
+
format_solution_model = _SearchedSolutionModel.model_validate_json(
|
350 |
+
format_solution.choices[0].message.content)
|
351 |
+
|
352 |
+
final_solution = SolutionModel(
|
353 |
+
Context="",
|
354 |
+
Requirements=[
|
355 |
+
cat.requirements[i].requirement for i in format_solution_model.requirement_ids
|
356 |
+
],
|
357 |
+
Problem_Description=format_solution_model.problem_description,
|
358 |
+
Solution_Description=format_solution_model.solution_description,
|
359 |
+
References=[],
|
360 |
+
Category_Id=cat.id,
|
361 |
+
)
|
362 |
+
|
363 |
+
# ========================================================================================================================================
|
364 |
+
|
365 |
+
return final_solution
|
366 |
+
|
367 |
+
tasks = await asyncio.gather(*[_search_solution_inner(cat) for cat in req.categories])
|
368 |
+
final_solutions = [sol for sol in tasks if not isinstance(sol, Exception)]
|
369 |
+
|
370 |
+
return SolutionSearchResponse(solutions=final_solutions)
|
371 |
+
|
372 |
+
|
373 |
api.include_router(requirements_router)
|
374 |
api.include_router(solution_router)
|
375 |
|
prompts/if/format_requirements.txt
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<role>You are an expert system designer</role>
|
2 |
+
<task>
|
3 |
+
Your task is to transform a category of requirements into a JSON object where the object key is a short title (with spaces) for the requirement and the value is the requirement.
|
4 |
+
Give the the 10 most important constraints that must be solved to address the general problem of the category.
|
5 |
+
</task>
|
6 |
+
|
7 |
+
<requirements>
|
8 |
+
Here is the category item and the associated requirements:
|
9 |
+
Category Title: {{category["title"]}}
|
10 |
+
Context: {{category["requirements"][0]["context"]}}
|
11 |
+
Requirements:
|
12 |
+
{% for req in category["requirements"] -%}
|
13 |
+
- {{loop.index0}} {{req["requirement"]}}
|
14 |
+
{% endfor -%}
|
15 |
+
</requirements>
|
16 |
+
|
17 |
+
<response_format>
|
18 |
+
Reply in JSON using the following schema:
|
19 |
+
{{response_schema}}
|
20 |
+
</response_format>
|
21 |
+
|
22 |
+
|
prompts/if/synthesize_solution.txt
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 one or multiple technologies that explain how something works or achieves its requirements.
|
5 |
+
|
6 |
+
You may use the technologies listed below for the mechanisms of your solution.
|
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 |
+
<requirements>
|
16 |
+
Here is the category item and the associated 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>
|
24 |
+
|
25 |
+
<available_technologies>
|
26 |
+
Here are the technologies you may use for the mechanisms that compose the solution:
|
27 |
+
{% for tech in technologies -%}
|
28 |
+
- {{tech["title"]}} : {{tech["purpose"]}}
|
29 |
+
* Key Components : {{tech["key_components"]}}
|
30 |
+
{% endfor %}
|
31 |
+
</available_technologies>
|
32 |
+
|
33 |
+
{% if user_constraints is not none %}
|
34 |
+
<user_constraints>
|
35 |
+
Here are additional user constraints the solution must respect:
|
36 |
+
{{user_constraints}}
|
37 |
+
</user_constraints>
|
38 |
+
{% endif %}
|
39 |
+
|
40 |
+
<response_format>
|
41 |
+
Reply in JSON using the following schema:
|
42 |
+
{{response_schema}}
|
43 |
+
</response_format>
|
schemas.py
CHANGED
@@ -134,3 +134,27 @@ class _RefinedSolutionModel(BaseModel):
|
|
134 |
description="New description of the problem being solved.")
|
135 |
solution_description: str = Field(...,
|
136 |
description="New detailed description of the solution.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
description="New description of the problem being solved.")
|
135 |
solution_description: str = Field(...,
|
136 |
description="New detailed description of the solution.")
|
137 |
+
|
138 |
+
|
139 |
+
# ================================================================= search solutions using insight finder endpoints
|
140 |
+
|
141 |
+
class InsightFinderConstraintsList(BaseModel):
|
142 |
+
constraints: dict = Field(
|
143 |
+
description="A dict of constraints where the key is a short title for the constraint and the value the whole constraint description")
|
144 |
+
|
145 |
+
|
146 |
+
class Technology(BaseModel):
|
147 |
+
"""Represents a single technology entry with its details."""
|
148 |
+
title: str
|
149 |
+
purpose: str
|
150 |
+
key_components: str
|
151 |
+
advantages: str
|
152 |
+
limitations: str
|
153 |
+
id: int
|
154 |
+
|
155 |
+
# This schema defines the root structure of the JSON
|
156 |
+
|
157 |
+
|
158 |
+
class TechnologyData(BaseModel):
|
159 |
+
"""Represents the top-level object containing a list of technologies."""
|
160 |
+
technologies: List[Technology]
|