Skip to content

Commit af6c81e

Browse files
chandrasekharan-zipstackclaudepk-zipstack
authored
UN-3159 [MISC] Add improved logging for retrieval operations (#1747)
* UN-3159 [FEAT] Add improved logging for retrieval operations - Add cache statistics logging to variable_replacement lru_cache (logs every 50 calls with hit rate, cache size) - Move retry logging in simple retriever to only log when actually retrying (not for initial attempts) - Optimize json_repair_helper with heuristic to skip double parsing when unnecessary - Add detailed retrieval metrics logging to services/retrieval.py for better observability Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert json_repair_helper.py changes Remove the heuristic optimization - keeping only logging improvements in this PR. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Praveen Kumar <praveen@zipstack.com>
1 parent 2a433ea commit af6c81e

File tree

3 files changed

+67
-17
lines changed

3 files changed

+67
-17
lines changed

prompt-service/src/unstract/prompt_service/core/retrievers/simple.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99

1010
class SimpleRetriever(BaseRetriever):
1111
def retrieve(self) -> set[str]:
12-
app.logger.info(
13-
f"Retrieving context for prompt: {self.prompt} with doc_id: {self.doc_id}"
14-
)
1512
context = self._simple_retrieval()
1613
if not context:
1714
# UN-1288 For Pinecone, we are seeing an inconsistent case where
@@ -21,6 +18,10 @@ def retrieve(self) -> set[str]:
2118
# the following sleep is added
2219
# Note: This will not fix the issue. Since this issue is inconsistent
2320
# and not reproducible easily, this is just a safety net.
21+
app.logger.info(
22+
f"[doc_id: {self.doc_id}] Could not retrieve context, "
23+
"retrying after 2 secs to handle issues due to lag"
24+
)
2425
time.sleep(2)
2526
context = self._simple_retrieval()
2627
return context

prompt-service/src/unstract/prompt_service/helpers/variable_replacement.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,33 @@ def replace_custom_data_variable(
161161

162162
@staticmethod
163163
@lru_cache(maxsize=128)
164+
def _extract_variables_cached(prompt_text: str) -> tuple[str, ...]:
165+
"""Internal cached extraction - returns tuple for lru_cache compatibility."""
166+
return tuple(re.findall(VariableConstants.VARIABLE_REGEX, prompt_text))
167+
168+
@staticmethod
164169
def extract_variables_from_prompt(prompt_text: str) -> list[str]:
165-
variable: list[str] = []
166-
variable = re.findall(VariableConstants.VARIABLE_REGEX, prompt_text)
167-
return variable
170+
"""Extract variables from prompt with caching and stats logging.
171+
172+
Uses lru_cache internally and logs cache statistics periodically
173+
to help determine if caching is beneficial.
174+
"""
175+
result = VariableReplacementHelper._extract_variables_cached(prompt_text)
176+
177+
# Log stats periodically (every 50 calls)
178+
info_after = VariableReplacementHelper._extract_variables_cached.cache_info()
179+
total_calls = info_after.hits + info_after.misses
180+
181+
if total_calls % 50 == 0 and total_calls > 0:
182+
hit_rate = info_after.hits / total_calls * 100
183+
app.logger.info(
184+
f"[VariableCache] total={total_calls} hits={info_after.hits} "
185+
f"misses={info_after.misses} hit_rate={hit_rate:.1f}% "
186+
f"size={info_after.currsize}/{info_after.maxsize} "
187+
f"prompt_chars={len(prompt_text)}"
188+
)
189+
190+
return list(result)
168191

169192
@staticmethod
170193
def fetch_dynamic_variable_value(url: str, data: str) -> Any:

prompt-service/src/unstract/prompt_service/services/retrieval.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import datetime
22
from typing import Any
33

4+
from flask import current_app as app
5+
46
from unstract.prompt_service.constants import PromptServiceConstants as PSKeys
57
from unstract.prompt_service.constants import RetrievalStrategy
68
from unstract.prompt_service.core.retrievers.automerging import AutomergingRetriever
@@ -32,13 +34,23 @@ def perform_retrieval( # type:ignore
3234
file_path: str,
3335
context_retrieval_metrics: dict[str, Any],
3436
) -> tuple[str, list[str]]:
37+
prompt_name = output.get(PSKeys.NAME, "<unknown>")
38+
vector_db_id = (
39+
getattr(vector_db, "_adapter_instance_id", None) if vector_db else None
40+
)
41+
app.logger.info(
42+
f"[Retrieval] prompt='{prompt_name}' doc_id={doc_id} "
43+
f"chunk_size={chunk_size} method={'complete_context' if chunk_size == 0 else 'chunked'}"
44+
+ (f" vector_db={vector_db_id}" if vector_db_id else "")
45+
)
46+
3547
context: list[str]
3648
if chunk_size == 0:
3749
context = RetrievalService.retrieve_complete_context(
3850
execution_source=execution_source,
3951
file_path=file_path,
4052
context_retrieval_metrics=context_retrieval_metrics,
41-
prompt_key=output[PSKeys.PROMPTX],
53+
prompt_key=prompt_name,
4254
)
4355
else:
4456
context = RetrievalService.run_retrieval(
@@ -101,9 +113,14 @@ def run_retrieval( # type:ignore
101113
llm=llm,
102114
)
103115
context = retriever.retrieve()
104-
context_retrieval_metrics[prompt_key] = {
105-
"time_taken(s)": Metrics.elapsed_time(start_time=retrieval_start_time)
106-
}
116+
elapsed = Metrics.elapsed_time(start_time=retrieval_start_time)
117+
context_retrieval_metrics[prompt_key] = {"time_taken(s)": elapsed}
118+
119+
app.logger.info(
120+
f"[Retrieval] prompt='{prompt_key}' doc_id={doc_id} "
121+
f"strategy='{retrieval_type}' top_k={top_k} chunks={len(context)} time={elapsed:.3f}s"
122+
)
123+
107124
return list(context)
108125

109126
@staticmethod
@@ -113,18 +130,27 @@ def retrieve_complete_context(
113130
context_retrieval_metrics: dict[str, Any],
114131
prompt_key: str,
115132
) -> list[str]:
116-
"""Loads full context from raw file for zero chunk size retrieval
133+
"""Loads full context from raw file for zero chunk size retrieval.
134+
117135
Args:
118-
execution_source (str): Source of execution.
119-
file_path (str): Path to the directory containing text file.
136+
execution_source: Source of execution (e.g., "api", "workflow").
137+
file_path: Path to the extracted text file.
138+
context_retrieval_metrics: Dict to store retrieval timing metrics
139+
(modified in-place).
140+
prompt_key: Name/identifier of the prompt for metrics tracking.
120141
121142
Returns:
122-
list[str]: context from extracted file.
143+
List containing the complete file content as a single string.
123144
"""
124145
fs_instance = FileUtils.get_fs_instance(execution_source=execution_source)
125146
retrieval_start_time = datetime.datetime.now()
126147
context = fs_instance.read(path=file_path, mode="r")
127-
context_retrieval_metrics[prompt_key] = {
128-
"time_taken(s)": Metrics.elapsed_time(start_time=retrieval_start_time)
129-
}
148+
elapsed = Metrics.elapsed_time(start_time=retrieval_start_time)
149+
context_retrieval_metrics[prompt_key] = {"time_taken(s)": elapsed}
150+
151+
app.logger.info(
152+
f"[Retrieval] prompt='{prompt_key}' complete_context "
153+
f"chars={len(context)} time={elapsed:.3f}s"
154+
)
155+
130156
return [context]

0 commit comments

Comments
 (0)