Skip to content

Commit 98ad45d

Browse files
committed
allow generate responses without source documents
1 parent c92f6a0 commit 98ad45d

File tree

9 files changed

+159
-22
lines changed

9 files changed

+159
-22
lines changed

backend/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Added support for queries without source data in vector database
1213
- Graceful failure of triple export when no chunks are found
1314

1415
## [v0.1.5] - 2024-10-29

backend/src/app/api/v1/endpoints/query.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from app.services.query_service import (
1717
decomposition_query,
1818
hybrid_query,
19+
inference_query,
1920
simple_vector_query,
2021
)
2122
from app.services.vector_db.base import VectorDBService
@@ -59,6 +60,30 @@ async def run_query(
5960
HTTPException
6061
If there's an error processing the query.
6162
"""
63+
if request.document_id == "ffffffffffffffffffffffffffffffff":
64+
query_response = await inference_query(
65+
request.prompt.query,
66+
request.prompt.rules,
67+
request.prompt.type,
68+
llm_service,
69+
)
70+
71+
if not isinstance(query_response, QueryResult):
72+
query_response = QueryResult(**query_response)
73+
74+
answer = QueryAnswer(
75+
id=uuid.uuid4().hex,
76+
document_id=request.document_id,
77+
prompt_id=request.prompt.id,
78+
answer=query_response.answer,
79+
type=request.prompt.type,
80+
)
81+
response_data = QueryAnswerResponse(
82+
answer=answer, chunks=query_response.chunks
83+
)
84+
85+
return response_data
86+
6287
try:
6388
logger.info(f"Received query request: {request.model_dump()}")
6489

backend/src/app/models/llm_responses.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ def validate_array(
8282
v = cls.validate_none(v)
8383
if v is None:
8484
return None
85+
if len(v) == 1 and v[0] == "None":
86+
return None
8587
if not isinstance(v, list):
8688
raise ValueError("Must be a list or None")
8789
if max_length and len(v) > max_length:

backend/src/app/services/llm/prompts.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,29 @@
2424
- Provide your answer based strictly on the given context.
2525
- Be concise and accurate.
2626
- Do not include any introductory or concluding remarks.
27-
- If the answer is not present in the context, respond exactly with `"None"` (without quotes).
27+
- If the answer is not present in the context, respond exactly with "None".
2828
2929
**Answer**:
3030
"""
3131
)
3232

33+
INFERRED_BASE_PROMPT = Template(
34+
"""
35+
Answer the following question following the formatting instructions at the bottom. Do not include, quotes, formatting, or any explanation or extra information. Just answer the question.
36+
37+
**Question**: $query
38+
**Answer**:
39+
40+
$format_specific_instructions
41+
42+
"""
43+
)
44+
3345
BOOL_INSTRUCTIONS = """
3446
**Special Instructions for Boolean Questions**:
3547
36-
- If the question is asking for a verification or requires a boolean answer, respond with `"True"` or `"False"` (as a string).
37-
- If you cannot determine the answer from the context, respond exactly with `"None"`.
48+
- If the question is asking for a verification or requires a boolean answer, respond with True or False.
49+
- If you cannot answer the question, respond exactly with 'None'.
3850
- Do not provide any explanations or additional information.
3951
"""
4052

@@ -45,9 +57,9 @@
4557
4658
**Special Instructions for String Responses**:
4759
48-
- If the answer is a single string, provide the string enclosed in double quotes.
60+
- If the answer is a single string, provide a single string.
4961
- If multiple strings are required, provide them as a JSON array of strings.
50-
- If you cannot find an answer, respond exactly with `"None"`.
62+
- If you cannot find an answer, respond exactly with 'None'.
5163
- Do not include any additional text or explanation.
5264
"""
5365
)
@@ -60,7 +72,7 @@
6072
6173
- If the answer is a single integer, provide the integer as a number.
6274
- If multiple integers are required, provide them as a JSON array of integers.
63-
- If you cannot find an answer, respond exactly with `"None"`.
75+
- If you cannot find an answer, respond exactly with 'None'.
6476
- Do not include any additional text or explanation.
6577
"""
6678
)
@@ -79,7 +91,7 @@
7991
8092
- Provide the keywords as a JSON array of strings.
8193
- Ensure all words are in their base (lemmatized) form.
82-
- If you cannot extract any relevant keywords, respond exactly with `"None"`.
94+
- If you cannot extract any relevant keywords, respond exactly with 'None'.
8395
- Do not include any additional text or explanation.
8496
8597
**Keywords**:
@@ -105,7 +117,7 @@
105117
106118
- Provide the similar keywords as a JSON array of strings.
107119
- Only include words that are present in the context and are semantically related to the provided keywords.
108-
- If you cannot find any similar keywords in the context, respond exactly with `"None"`.
120+
- If you cannot find any similar keywords in the context, respond exactly with 'None'.
109121
- Do not include any additional text or explanation.
110122
111123
**Similar Keywords**:
@@ -125,7 +137,7 @@
125137
**Instructions**:
126138
127139
- Provide up to 3 sub-questions as a JSON array of strings.
128-
- If the question is already simple or cannot be decomposed, respond exactly with `"None"`.
140+
- If the question is already simple or cannot be decomposed, respond exactly with 'None'.
129141
- Do not include any additional text or explanation.
130142
131143
**Sub-Questions**:

backend/src/app/services/llm_service.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
BASE_PROMPT,
2222
BOOL_INSTRUCTIONS,
2323
DECOMPOSE_QUERY_PROMPT,
24+
INFERRED_BASE_PROMPT,
2425
INT_ARRAY_INSTRUCTIONS,
2526
KEYWORD_PROMPT,
2627
SCHEMA_PROMPT,
@@ -153,6 +154,58 @@ async def generate_response(
153154
return {"answer": None}
154155

155156

157+
async def generate_inferred_response(
158+
llm_service: LLMService,
159+
query: str,
160+
rules: list[Rule],
161+
format: FormatType,
162+
) -> dict[str, Any]:
163+
"""
164+
Generate a response from the language model based on the given query and format.
165+
166+
Parameters
167+
----------
168+
llm_service : LLMService
169+
The language model service to use for generating the response.
170+
query : str
171+
The user's query to be answered.
172+
rules : list[Rule]
173+
A list of rules to apply when generating the response.
174+
format : Literal["int", "str", "bool", "int_array", "str_array"]
175+
The desired format of the response.
176+
177+
Returns
178+
-------
179+
dict[str, Any]
180+
A dictionary containing the generated answer or None if an error occurs.
181+
"""
182+
logger.info(
183+
f"Generating inferred response for query: {query} in format: {format}"
184+
)
185+
186+
output_model, format_specific_instructions = _get_model_and_instructions(
187+
format, rules, query
188+
)
189+
prompt = INFERRED_BASE_PROMPT.substitute(
190+
query=query,
191+
format_specific_instructions=format_specific_instructions,
192+
)
193+
194+
try:
195+
response = await llm_service.generate_completion(prompt, output_model)
196+
logger.info(f"Raw response from LLM: {response}")
197+
198+
if response is None or response.answer is None:
199+
logger.warning("LLM returned None response")
200+
return {"answer": None}
201+
202+
logger.info(f"Processed response: {response.answer}")
203+
return {"answer": response.answer}
204+
except Exception as e:
205+
logger.error(f"Error generating response: {str(e)}", exc_info=True)
206+
return {"answer": None}
207+
208+
156209
async def get_keywords(
157210
llm_service: LLMService, query: str
158211
) -> dict[str, list[str] | None]:

backend/src/app/services/query_service.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
from app.models.query_core import Chunk, FormatType, QueryType, Rule
77
from app.schemas.query_api import QueryResult, SearchResponse
8-
from app.services.llm_service import LLMService, generate_response
8+
from app.services.llm_service import (
9+
LLMService,
10+
generate_inferred_response,
11+
generate_response,
12+
)
913

1014
logging.basicConfig(level=logging.INFO)
1115
logger = logging.getLogger(__name__)
@@ -124,3 +128,22 @@ async def simple_vector_query(
124128
llm_service,
125129
vector_db_service,
126130
)
131+
132+
133+
async def inference_query(
134+
query: str,
135+
rules: List[Rule],
136+
format: FormatType,
137+
llm_service: LLMService,
138+
) -> QueryResult:
139+
"""Generate a response, no need for vector retrieval."""
140+
141+
# Since we are just answering this query based on data provided in the query,
142+
# ther is no need to retrieve any chunks from the vector database.
143+
144+
answer = await generate_inferred_response(
145+
llm_service, query, rules, format
146+
)
147+
answer_value = answer["answer"]
148+
149+
return QueryResult(answer=answer_value, chunks=[])

frontend/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- Allow query without source data if mention is present
1213
- Removed replace rule as an option for now
1314

1415
## [v0.1.5] - 2024-10-29

frontend/src/config/api.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ export async function runQuery(
7171
column: AnswerTableColumn,
7272
globalRules: AnswerTableGlobalRule[]
7373
) {
74-
if (!row.sourceData || !column.entityType.trim() || !column.generate) {
74+
// if (!row.sourceData || !column.entityType.trim() || !column.generate) {
75+
// throw new Error(
76+
// "Row or column doesn't allow running query (missing row source data or column is empty or has generate set to false)"
77+
// );
78+
// }
79+
if (!column.entityType.trim() || !column.generate) {
7580
throw new Error(
7681
"Row or column doesn't allow running query (missing row source data or column is empty or has generate set to false)"
7782
);
@@ -88,7 +93,9 @@ export async function runQuery(
8893
"Content-Type": "application/json"
8994
},
9095
body: JSON.stringify({
91-
document_id: row.sourceData.document.id,
96+
document_id: row.sourceData?.document?.id
97+
? row.sourceData.document.id
98+
: "ffffffffffffffffffffffffffffffff",
9299
prompt: {
93100
id: column.id,
94101
entity_type: column.entityType,

frontend/src/config/store/store.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ export const useStore = create<Store>()(
308308
row &&
309309
column.entityType.trim() &&
310310
column.generate &&
311-
row.sourceData &&
312311
!loadingCells[key]
313312
? { key, column, row }
314313
: null;
@@ -324,28 +323,42 @@ export const useStore = create<Store>()(
324323

325324
for (const { key, row, column: column_ } of batch) {
326325
const column = cloneDeep(column_);
326+
let shouldRunQuery = true;
327+
let hasMatches = false;
327328

328329
// Replace all column references with the row's answer to that column
329330
for (const [match, columnId] of column.query.matchAll(
330331
/@\[[^\]]+\]\(([^)]+)\)/g
331332
)) {
333+
hasMatches = true;
332334
const targetColumn = columns.find(c => c.id === columnId);
333335
if (!targetColumn) continue;
334336
const cell = row.cells[targetColumn.id];
335-
if (isNil(cell)) continue;
337+
if (isNil(cell) || (isNil(cell) && isNil(row.sourceData))) {
338+
shouldRunQuery = false;
339+
break;
340+
}
336341
column.query = column.query.replace(match, String(cell));
337342
}
338-
339-
runQuery(row, column, globalRules).then(({ answer, chunks }) => {
340-
editCells(
341-
[{ rowId: row.id, columnId: column.id, cell: answer.answer }],
342-
activeTableId
343-
);
343+
if (!hasMatches && isNil(row.sourceData)) {
344+
shouldRunQuery = false;
345+
}
346+
if (shouldRunQuery) {
347+
runQuery(row, column, globalRules).then(({ answer, chunks }) => {
348+
editCells(
349+
[{ rowId: row.id, columnId: column.id, cell: answer.answer }],
350+
activeTableId
351+
);
352+
editTable(activeTableId, {
353+
chunks: { ...getTable(activeTableId).chunks, [key]: chunks },
354+
loadingCells: omit(getTable(activeTableId).loadingCells, key)
355+
});
356+
});
357+
} else {
344358
editTable(activeTableId, {
345-
chunks: { ...getTable(activeTableId).chunks, [key]: chunks },
346359
loadingCells: omit(getTable(activeTableId).loadingCells, key)
347360
});
348-
});
361+
}
349362
}
350363
},
351364

0 commit comments

Comments
 (0)