Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions app/api/routes/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, or_, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError, OperationalError
from sqlalchemy.orm import Session as DBSession

from app.core.catalog import product_record, public_product
Expand Down Expand Up @@ -182,15 +182,28 @@ def assistant_query(payload: AssistantQueryCreate, db: DBSession = Depends(get_d
response_json=response_payload,
created_at=utc_now(),
)
db.add(record)
for position, product in enumerate(ranked, start=1):
candidate = _candidate_payload(product, position)
db.add(QueryCandidate(query_id=query_id, cited=product["id"] in cited_ids, **candidate))
try:
db.commit()
except IntegrityError as error:
db.rollback()
raise _bad_request(f"Database constraint: {error.orig}") from error
candidates = [
QueryCandidate(query_id=query_id, cited=product["id"] in cited_ids, **_candidate_payload(product, position))
for position, product in enumerate(ranked, start=1)
]

max_retries = 3
for attempt in range(max_retries):
try:
db.add(record)
db.add_all(candidates)
db.commit()
break
except OperationalError as error:
db.rollback()
if getattr(error.orig, "sqlstate", None) == "40001" and attempt < max_retries - 1:
import time
time.sleep(0.1 * (2 ** attempt))
continue
raise
except IntegrityError as error:
db.rollback()
raise _bad_request(f"Database constraint: {error.orig}") from error
return {"query_id": query_id, **response_payload}


Expand Down
8 changes: 5 additions & 3 deletions app/services/openai_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ def generate(self, context: GroundedGenerationContext) -> AdapterGeneration:
"1. You may only state facts provided in the ALLOWED CLAIMS list below.\n"
"2. When you use a claim, you MUST append its exact citation ID (e.g., [C1]).\n"
"3. Do not invent any facts, prices, ratings, or product details.\n"
"4. Output your response as a valid JSON object matching this schema:\n"
' {"answer": "Your natural language response with citation markers like [C1] and [C2]",\n'
"4. End your `answer` by actively interacting with the user or asking an engaging question to keep the conversation going.\n"
"5. Output your response as a valid JSON object matching this schema:\n"
' {"answer": "Your natural language response with citation markers like [C1] and [C2] ending with a conversational question",\n'
' "used_citation_ids": ["C1", "C2"],\n'
' "suggested_follow_ups": ["Short follow-up question 1?", "Question 2?"]}\n\n'
' "suggested_follow_ups": ["Can you tell me if [Product] has a warranty?", "What are the alternatives to [Brand]?"]}\n\n'
"Note: `suggested_follow_ups` MUST be 2-3 preemptive questions phrased from the USER's perspective that they might want to click next.\n\n"
"ALLOWED CLAIMS:\n"
)

Expand Down
Loading