Skip to content

Commit e2eb0b6

Browse files
committed
updated schemas and drafting wip
1 parent d9db1d7 commit e2eb0b6

21 files changed

Lines changed: 418 additions & 94 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pydantic import BaseModel
2+
from .vacancies import VacancyListing
3+
4+
5+
class DraftRequest(BaseModel):
6+
description: str
7+
similar_vacancies: list[VacancyListing]
8+
9+
10+
class DraftResponse(BaseModel):
11+
draft: str

jao-backend/src/jao_backend/api/v0/endpoints.py

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from jao_backend_schemas.advice import AdviceResponse
1212
from jao_backend_schemas.advice import AdviceRequest
13+
from jao_backend_schemas.draft import DraftResponse
14+
from jao_backend_schemas.draft import DraftRequest
1315
from jao_backend_schemas.maps import AreaFrequenciesResponse
1416
from jao_backend_schemas.plots import PlotlyFiguresResponse
1517
from jao_backend_schemas.vacancies import SimilarVacanciesResponse
@@ -19,8 +21,8 @@
1921
from jao_backend.common.text_processing.clean_oleeo import parse_oleeo_bbcode
2022
from jao_backend.embeddings.models import EmbeddingTag
2123
from jao_backend.vacancies.models import VacancyEmbedding
22-
from jao_backend.advice.advice import AdviceService
23-
advice_service = AdviceService()
24+
from jao_backend.llm.llm_service import LLMService
25+
llm_service = LLMService()
2426

2527
logger = logging.getLogger(__name__)
2628

@@ -78,6 +80,23 @@ def get_similar_vacancies(text, top_n=10):
7880
]
7981

8082

83+
@api.post("/similar_adverts", response=SimilarVacanciesResponse)
84+
def similar_adverts(
85+
request: HttpRequest, payload: JobDescriptionRequest
86+
) -> SimilarVacanciesResponse:
87+
similar_vacancies_list = [
88+
VacancyListing.model_validate(
89+
{
90+
"job_title": vacancy.title,
91+
"full_job_desc": parse_oleeo_bbcode(vacancy.description),
92+
"vacancy_id": vacancy.pk,
93+
}
94+
)
95+
for vacancy in get_similar_vacancies_cached(payload.description, top_n=10)
96+
]
97+
return SimilarVacanciesResponse(similar_vacancies=similar_vacancies_list)
98+
99+
81100
@api.post("/advice")
82101
def advice(request: HttpRequest, payload: AdviceRequest) -> StreamingHttpResponse:
83102
formatted_vacancies = [
@@ -88,9 +107,9 @@ def advice(request: HttpRequest, payload: AdviceRequest) -> StreamingHttpRespons
88107

89108
def generate():
90109
"""Generator for streaming response"""
91-
for chunk in advice_service.get_advice(payload.description,
92-
formatted_vacancies,
93-
payload.advice_type):
110+
for chunk in llm_service.get_advice(payload.description,
111+
formatted_vacancies,
112+
payload.advice_type):
94113
logger.debug(f"Streaming chunk: {chunk}")
95114
yield f"data: {json.dumps({'content': chunk})}\n\n"
96115

@@ -101,69 +120,71 @@ def generate():
101120
)
102121

103122

104-
@api.post("/similar_adverts", response=SimilarVacanciesResponse)
105-
def similar_adverts(
106-
request: HttpRequest, payload: JobDescriptionRequest
107-
) -> SimilarVacanciesResponse:
108-
similar_vacancies_list = [
109-
VacancyListing.model_validate(
110-
{
111-
"job_title": vacancy.title,
112-
"full_job_desc": parse_oleeo_bbcode(vacancy.description),
113-
"vacancy_id": vacancy.pk,
114-
}
115-
)
116-
for vacancy in get_similar_vacancies_cached(payload.description, top_n=10)
123+
@api.post("/draft")
124+
def draft(request: HttpRequest, payload: DraftResponse) -> StreamingHttpResponse:
125+
formatted_vacancies = [
126+
f"Job Title: {vacancy.job_title}\nDescription: {
127+
parse_oleeo_bbcode(vacancy.full_job_desc)}"
128+
for vacancy in payload.similar_vacancies
117129
]
118-
return SimilarVacanciesResponse(similar_vacancies=similar_vacancies_list)
119-
120-
# Write a query using the similar vacancies data from application stastics -
121-
# aggregated application statistic model
122-
123130

124-
@api.post("/similar_advert_plots")
125-
def similar_advert_plots(
126-
request: HttpRequest, payload: JobDescriptionRequest
127-
) -> PlotlyFiguresResponse:
128-
"""
129-
Get a graph of the job description.
130-
"""
131-
# Stub: this required aggregated data
132-
logger.info(
133-
"STUB: similar_advert_plots endpoint called with description: %s",
134-
payload.description,
131+
def generate():
132+
for chunk in llm_service.get_draft(payload.description, formatted_vacancies):
133+
yield f"data: {json.dumps({'content': chunk})}\n\n"
134+
yield "data: [DONE]\n\n"
135+
return StreamingHttpResponse(
136+
generate(),
137+
content_type='text/event-stream'
135138
)
136-
graphs = []
137-
return PlotlyFiguresResponse(plotly_figures=graphs)
138-
139-
# ADD Skills Ingester to write skills to DB
140139

141140

142-
@api.post("/skills_plots")
143-
def skills_plots(request, payload: JobDescriptionRequest) -> PlotlyFiguresResponse:
144-
"""
145-
Get a graph of the job description.
146-
"""
147-
# Stub: skills are not ingested right now.
148-
logger.info(
149-
"STUB: skills_plots endpoint called with description: %s", payload.description
150-
)
151-
graphs = []
152-
result = PlotlyFiguresResponse(plotly_figures=graphs)
153-
return result
154-
155-
# Maybe REMOVE the location response
141+
# Write a query using the similar vacancies data from application stastics -
142+
# aggregated application statistic model
156143

157144

158-
@api.post("/applicant_locations")
159-
def applicant_locations(
160-
request, payload: JobDescriptionRequest
161-
) -> AreaFrequenciesResponse:
162-
# Stub: OLEEO ingestion of locations is TBD
163-
logger.info(
164-
"STUB: applicant_locations endpoint called with description: %s",
165-
payload.description,
166-
)
167-
area_frequencies: AreaFrequencyProperties = []
168-
# TODO: populate area_frequencies with instances of AreaFrequencyProperties from the database.
169-
return AreaFrequenciesResponse(area_frequencies=area_frequencies)
145+
# @api.post("/similar_advert_plots")
146+
# def similar_advert_plots(
147+
# request: HttpRequest, payload: JobDescriptionRequest
148+
# ) -> PlotlyFiguresResponse:
149+
# """
150+
# Get a graph of the job description.
151+
# """
152+
# # Stub: this required aggregated data
153+
# logger.info(
154+
# "STUB: similar_advert_plots endpoint called with description: %s",
155+
# payload.description,
156+
# )
157+
# graphs = []
158+
# return PlotlyFiguresResponse(plotly_figures=graphs)
159+
#
160+
# # ADD Skills Ingester to write skills to DB
161+
#
162+
#
163+
# @api.post("/skills_plots")
164+
# def skills_plots(request, payload: JobDescriptionRequest) -> PlotlyFiguresResponse:
165+
# """
166+
# Get a graph of the job description.
167+
# """
168+
# # Stub: skills are not ingested right now.
169+
# logger.info(
170+
# "STUB: skills_plots endpoint called with description: %s", payload.description
171+
# )
172+
# graphs = []
173+
# result = PlotlyFiguresResponse(plotly_figures=graphs)
174+
# return result
175+
#
176+
# # Maybe REMOVE the location response
177+
#
178+
#
179+
# @api.post("/applicant_locations")
180+
# def applicant_locations(
181+
# request, payload: JobDescriptionRequest
182+
# ) -> AreaFrequenciesResponse:
183+
# # Stub: OLEEO ingestion of locations is TBD
184+
# logger.info(
185+
# "STUB: applicant_locations endpoint called with description: %s",
186+
# payload.description,
187+
# )
188+
# area_frequencies: AreaFrequencyProperties = []
189+
# # TODO: populate area_frequencies with instances of AreaFrequencyProperties from the database.
190+
# return AreaFrequenciesResponse(area_frequencies=area_frequencies)
File renamed without changes.
File renamed without changes.

jao-backend/src/jao_backend/advice/apps.py renamed to jao-backend/src/jao_backend/llm/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
class AdviceConfig(AppConfig):
55
default_auto_field = 'django.db.models.BigAutoField'
6-
name = 'jao_backend.advice'
6+
name = 'jao_backend.llm'
File renamed without changes.

jao-backend/src/jao_backend/advice/advice.py renamed to jao-backend/src/jao_backend/llm/llm_service.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from jao_backend.application_statistics.models.lists import Gender, Disability
1111
from jao_backend.application_statistics.models.statistics import AggregatedApplicationStatistic
1212
from jao_backend_schemas.advice import AdviceResponse
13+
from jao_backend_schemas.draft import DraftResponse
1314
from jao_backend.settings.common import EMBEDDING_TAG_JOB_TITLE_RESPONSIBILITIES_ID
14-
from jao_backend.advice.prompts import ADVICE_PROMPTS
15+
from jao_backend.llm.prompts import ADVICE_PROMPTS, DRAFT_PROMPT
1516

1617
LITELLM_API_BASE = settings.LITELLM_API_BASE
1718
LITELLM_CUSTOM_PROVIDER = settings.LITELLM_CUSTOM_PROVIDER
@@ -20,7 +21,7 @@
2021
logger = logging.getLogger(__name__)
2122

2223

23-
class AdviceService():
24+
class LLMService():
2425

2526
def __init__(self):
2627
self.tag = EmbeddingTag.get_tag(
@@ -79,30 +80,45 @@ def _update_rag_content(self, user_input, filters):
7980
'No similar vacancies found with the applied filters.'))
8081
return updated_vacancies
8182

83+
def _draft_handler(self, user_input, rag_content):
84+
85+
prompt_config = DRAFT_PROMPT
86+
87+
messages = [
88+
{"role": "system", "content": prompt_config["system"]},
89+
{"role": "user", "content": prompt_config["user_template"].format(
90+
rag_content=rag_content,
91+
user_input=user_input
92+
)}
93+
]
94+
95+
try:
96+
return completion(
97+
model=self.model,
98+
messages=messages,
99+
stream=True,
100+
max_tokens=1500,
101+
api_base=LITELLM_API_BASE,
102+
custom_llm_provider=LITELLM_CUSTOM_PROVIDER
103+
)
104+
except Exception as e:
105+
logger.error(f'Error generating advice: {str(e)}')
106+
raise
107+
82108
def _advice_handler(self, user_input, rag_content, advice_type, options=None):
83-
# For non-general advice, apply filters and get updated RAG content
84109
if advice_type != "general" and options:
85110
filters = self.build_filters(options)
86111
try:
87112
updated_vacancies = self._update_rag_content(
88113
user_input, filters)
89-
# Rebuild RAG content with filtered vacancies
90114
rag_content = "\n\n".join(
91115
[f"Job Ad {i+1}:\n{vacancy.vacancy.full_job_desc}"
92116
for i, vacancy in enumerate(updated_vacancies)]
93117
)
94118
except Exception as e:
95119
logger.error(f'Error filtering vacancies: {str(e)}')
96-
# Fall back to original rag_content if filtering fails
97120

98121
prompt_config = ADVICE_PROMPTS.get(advice_type)
99-
print("********")
100-
print("********")
101-
print(advice_type)
102-
print(prompt_config)
103-
104-
print("********")
105-
print("********")
106122
if not prompt_config:
107123
raise ValueError(f"Unknown advice type: {advice_type}")
108124

@@ -148,3 +164,24 @@ def get_advice(self, user_input, similar_vacancies, advice_type,
148164
except Exception as e:
149165
logger.error(f"Error generating advice with LiteLLM: {str(e)}")
150166
yield "Sorry, I'm unable to generate advice at the moment. Please try again later."
167+
168+
def get_draft(self, user_input, similar_vacancies):
169+
rag_content = "\n\n".join(
170+
[f"Job Ad {i+1}:\n{ad}" for i, ad in enumerate(similar_vacancies)])
171+
try:
172+
response = self._draft_handler(
173+
user_input, rag_content)
174+
for chunk in response:
175+
if chunk.choices[0].delta.content:
176+
yield chunk.choices[0].delta.content
177+
178+
except APIConnectionError as e:
179+
logger.error(
180+
"Connection refused to the completion service. "
181+
"Ensure the service is running and accessible: %s",
182+
e,
183+
)
184+
raise
185+
except Exception as e:
186+
logger.error(f"Error generating advice with LiteLLM: {str(e)}")
187+
yield "Sorry, I'm unable to generate drafts at the moment. Please try again later."

jao-backend/src/jao_backend/advice/management/commands/__init__.py renamed to jao-backend/src/jao_backend/llm/management/__init__.py

File renamed without changes.

jao-backend/src/jao_backend/advice/migrations/__init__.py renamed to jao-backend/src/jao_backend/llm/management/commands/__init__.py

File renamed without changes.

jao-backend/src/jao_backend/advice/management/commands/generate_advice.py renamed to jao-backend/src/jao_backend/llm/management/commands/generate_advice.py

File renamed without changes.

0 commit comments

Comments
 (0)