dhammawatthumpra commited on
Commit
efc32dd
·
1 Parent(s): 9babb2f

refactor: disable LLM fallback for (ย่อ), fix JSON string array parsing

Browse files

All 1,742 pages with (ย่อ) markers are now pre-indexed in DB.
Remove expensive DeepSeek API call on every (ย่อ) click.
Fix expand_abbreviation to parse plain string arrays from preindex_abbrevs.

scripts/preindex_abbrevs.log CHANGED
The diff for this file is too large to render. See raw diff
 
scripts/preindex_abbrevs.py CHANGED
@@ -1,18 +1,22 @@
1
  """
2
- Pre-index all (ย่อ) abbreviation markers using NVIDIA NIM (free DeepSeek, 40 RPM).
 
 
 
 
3
 
4
  Usage:
5
- # Full run (all 1,743 pages)
6
  python scripts/preindex_abbrevs.py
7
 
8
- # Resume from interrupted run
9
- python scripts/preindex_abbrevs.py --resume
10
 
11
  # Test: first 10 pages
12
  python scripts/preindex_abbrevs.py --limit 10
13
 
14
  Scans all pages in tipitaka_mcu.db for (ย่อ) markers.
15
- For each page, calls NIM DeepSeek to expand ALL abbreviations in one request.
16
  Stores JSON array in reference_markers (type='abbrev', marker_id='_default').
17
  Supports resume — skips pages already in DB.
18
 
@@ -43,9 +47,16 @@ ENV_PATH = PROJECT_ROOT.parent / "tipitaka_context" / "nvidia" / ".env"
43
  # ══════════════════════════════════════════════════════════════
44
  load_dotenv(ENV_PATH)
45
 
 
46
  NIM_API_KEY = os.getenv("TIPITAKA_API_KEY") or os.getenv("NVIDIA_API_KEY") or ""
47
  NIM_BASE_URL = os.getenv("TIPITAKA_BASE_URL", "https://integrate.api.nvidia.com/v1")
48
  NIM_MODEL = os.getenv("TIPITAKA_MODEL", "deepseek-ai/deepseek-v4-flash")
 
 
 
 
 
 
49
  RATE_INTERVAL = float(os.getenv("TIPITAKA_RATE_INTERVAL", "1.6")) # 40 RPM
50
 
51
  MAX_RETRIES = 4
@@ -95,7 +106,7 @@ def build_prompt(content_text: str) -> str:
95
  # Parsing
96
  # ══════════════════════════════════════════════════════════════
97
  def parse_response(raw: str, expected_count: int) -> list[str]:
98
- """Parse JSON array from NIM response into list of expansion strings."""
99
  raw = re.sub(r'<think>.*?</think>', '', raw, flags=re.DOTALL).strip()
100
  raw = re.sub(r'^```(?:json)?\s*', '', raw)
101
  raw = re.sub(r'\s*```$', '', raw)
@@ -136,6 +147,7 @@ def parse_response(raw: str, expected_count: int) -> list[str]:
136
  # ══════════════════════════════════════════════════════════════
137
  async def expand_page(
138
  client: AsyncOpenAI,
 
139
  vol: int, page: int, content_text: str,
140
  conn: sqlite3.Connection,
141
  ) -> bool:
@@ -149,7 +161,7 @@ async def expand_page(
149
  for attempt in range(MAX_RETRIES):
150
  try:
151
  resp = await client.chat.completions.create(
152
- model=NIM_MODEL,
153
  messages=[
154
  {"role": "system", "content": SYSTEM_PROMPT},
155
  {"role": "user", "content": prompt},
@@ -206,24 +218,45 @@ async def expand_page(
206
  # Main
207
  # ══════════════════════════════════════════════════════════════
208
  async def main():
209
- parser = argparse.ArgumentParser(description="Pre-index (ย่อ) abbreviations via NVIDIA NIM")
 
 
210
  parser.add_argument("--resume", action="store_true", help="Skip already-indexed pages")
211
  parser.add_argument("--force", action="store_true", help="Re-index even if already in DB")
212
  parser.add_argument("--limit", type=int, default=0, help="Max pages (0 = all)")
 
 
 
 
213
  args = parser.parse_args()
214
 
215
- if not NIM_API_KEY:
216
- log.error(f"❌ No API key found. Check env file: {ENV_PATH}")
217
- log.error(" Set TIPITAKA_API_KEY or NVIDIA_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  return
219
 
220
  if not DB_PATH.exists():
221
  log.error(f"❌ DB not found: {DB_PATH}")
222
  return
223
 
224
- log.info(f"🚀 preindex_abbrevs | model={NIM_MODEL}")
225
  log.info(f" DB: {DB_PATH}")
226
- log.info(f" Key: {NIM_API_KEY[:8]}...")
227
  log.info(f" Rate: {RATE_INTERVAL}s")
228
 
229
  # ── Load pages ──
@@ -264,8 +297,8 @@ async def main():
264
 
265
  # ── Setup client ──
266
  client = AsyncOpenAI(
267
- base_url=NIM_BASE_URL,
268
- api_key=NIM_API_KEY,
269
  max_retries=0,
270
  )
271
 
@@ -290,7 +323,7 @@ async def main():
290
  if wait > 0:
291
  await asyncio.sleep(wait)
292
 
293
- ok = await expand_page(client, vol, page, text, conn)
294
 
295
  if ok:
296
  success += 1
 
1
  """
2
+ Pre-index all (ย่อ) abbreviation markers.
3
+
4
+ Supports:
5
+ - NVIDIA NIM (free DeepSeek, 40 RPM) → --provider nim
6
+ - DeepSeek API directly → --provider deepseek
7
 
8
  Usage:
9
+ # Full run (default: NVIDIA NIM)
10
  python scripts/preindex_abbrevs.py
11
 
12
+ # Resume with DeepSeek API
13
+ python scripts/preindex_abbrevs.py --resume --provider deepseek
14
 
15
  # Test: first 10 pages
16
  python scripts/preindex_abbrevs.py --limit 10
17
 
18
  Scans all pages in tipitaka_mcu.db for (ย่อ) markers.
19
+ For each page, calls LLM to expand ALL abbreviations in one request.
20
  Stores JSON array in reference_markers (type='abbrev', marker_id='_default').
21
  Supports resume — skips pages already in DB.
22
 
 
47
  # ══════════════════════════════════════════════════════════════
48
  load_dotenv(ENV_PATH)
49
 
50
+ # NVIDIA NIM
51
  NIM_API_KEY = os.getenv("TIPITAKA_API_KEY") or os.getenv("NVIDIA_API_KEY") or ""
52
  NIM_BASE_URL = os.getenv("TIPITAKA_BASE_URL", "https://integrate.api.nvidia.com/v1")
53
  NIM_MODEL = os.getenv("TIPITAKA_MODEL", "deepseek-ai/deepseek-v4-flash")
54
+
55
+ # DeepSeek API
56
+ DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY") or os.getenv("DEEPSEEK_API_KEY") or ""
57
+ DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
58
+ DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
59
+
60
  RATE_INTERVAL = float(os.getenv("TIPITAKA_RATE_INTERVAL", "1.6")) # 40 RPM
61
 
62
  MAX_RETRIES = 4
 
106
  # Parsing
107
  # ══════════════════════════════════════════════════════════════
108
  def parse_response(raw: str, expected_count: int) -> list[str]:
109
+ """Parse JSON array from LLM response into list of expansion strings."""
110
  raw = re.sub(r'<think>.*?</think>', '', raw, flags=re.DOTALL).strip()
111
  raw = re.sub(r'^```(?:json)?\s*', '', raw)
112
  raw = re.sub(r'\s*```$', '', raw)
 
147
  # ══════════════════════════════════════════════════════════════
148
  async def expand_page(
149
  client: AsyncOpenAI,
150
+ model: str,
151
  vol: int, page: int, content_text: str,
152
  conn: sqlite3.Connection,
153
  ) -> bool:
 
161
  for attempt in range(MAX_RETRIES):
162
  try:
163
  resp = await client.chat.completions.create(
164
+ model=model,
165
  messages=[
166
  {"role": "system", "content": SYSTEM_PROMPT},
167
  {"role": "user", "content": prompt},
 
218
  # Main
219
  # ══════════════════════════════════════════════════════════════
220
  async def main():
221
+ parser = argparse.ArgumentParser(
222
+ description="Pre-index (ย่อ) abbreviations via LLM"
223
+ )
224
  parser.add_argument("--resume", action="store_true", help="Skip already-indexed pages")
225
  parser.add_argument("--force", action="store_true", help="Re-index even if already in DB")
226
  parser.add_argument("--limit", type=int, default=0, help="Max pages (0 = all)")
227
+ parser.add_argument(
228
+ "--provider", choices=["nim", "deepseek"], default="nim",
229
+ help="LLM provider: nim (NVIDIA NIM, default) or deepseek (DeepSeek API)"
230
+ )
231
  args = parser.parse_args()
232
 
233
+ # ── Select provider config ──
234
+ if args.provider == "nim":
235
+ api_key = NIM_API_KEY
236
+ base_url = NIM_BASE_URL
237
+ model = NIM_MODEL
238
+ provider_name = "NVIDIA NIM"
239
+ else:
240
+ api_key = DEEPSEEK_API_KEY or os.getenv("DEEPSEEK_API_KEY") or ""
241
+ base_url = DEEPSEEK_BASE_URL
242
+ model = DEEPSEEK_MODEL
243
+ provider_name = "DeepSeek API"
244
+
245
+ if not api_key:
246
+ log.error(f"❌ No API key for provider '{args.provider}'. Check env file: {ENV_PATH}")
247
+ if args.provider == "nim":
248
+ log.error(" Set TIPITAKA_API_KEY or NVIDIA_API_KEY")
249
+ else:
250
+ log.error(" Set DEEPSEEK_API_KEY")
251
  return
252
 
253
  if not DB_PATH.exists():
254
  log.error(f"❌ DB not found: {DB_PATH}")
255
  return
256
 
257
+ log.info(f"🚀 preindex_abbrevs | provider={provider_name} model={model}")
258
  log.info(f" DB: {DB_PATH}")
259
+ log.info(f" Key: {api_key[:8]}...")
260
  log.info(f" Rate: {RATE_INTERVAL}s")
261
 
262
  # ── Load pages ──
 
297
 
298
  # ── Setup client ──
299
  client = AsyncOpenAI(
300
+ base_url=base_url,
301
+ api_key=api_key,
302
  max_retries=0,
303
  )
304
 
 
323
  if wait > 0:
324
  await asyncio.sleep(wait)
325
 
326
+ ok = await expand_page(client, model, vol, page, text, conn)
327
 
328
  if ok:
329
  success += 1
webapp/tipitaka-api/app/services/llm_service.py CHANGED
@@ -136,47 +136,19 @@ class LLMService:
136
  if content.startswith('['):
137
  try:
138
  entries = json.loads(content)
139
- parts = [e.get("expansion", "") for e in entries if e.get("expansion")]
140
- return "\n\n".join(parts) if parts else content
141
- except (json.JSONDecodeError, TypeError):
 
 
 
 
 
 
142
  pass
 
143
  return content
144
 
145
- # 2. LLM Fallback
146
- clean_ctx = self.search_service.clean_text(context) if context else ""
147
- prompt = (
148
- f"คุณคือผู้เชี่ยวชาญพระไตรปิฎก มจร. "
149
- f"ในเนื้อหาต่อไปนี้มีคำว่า '{abbrev_text}' (เช่น (ย่อ), ฯลฯ) "
150
- f"กรุณาอธิบายหรือขยายความสิ่งที่ถูกย่อไว้ให้สมบูรณ์ที่สุดตามหลักฐานในพระไตรปิฎก "
151
- f"โดยพิจารณาจากบริบทแวดล้อมที่ให้มา:\n\n"
152
- f"เนื้อหาบริบท:\n{clean_ctx[:4000]}\n\n"
153
- f"กรุณาตอบเป็นข้อความสั้นๆ ที่เป็นเนื้อหาที่ถูกย่อไว้ หรืออธิบายว่าส่วนนี้ย่อมาจากอะไร "
154
- f"ถ้าไม่แน่ใจให้บอกว่าเป็นการย่อเพื่อละเนื้อหาที่ซ้ำกัน"
155
- )
156
-
157
- try:
158
- response = await self.client.chat.completions.create(
159
- model=self.model_map["fast"],
160
- messages=[
161
- {"role": "system", "content": "คุณคือผู้เชี่ยวชาญพระไตรปิฎก"},
162
- {"role": "user", "content": prompt}
163
- ],
164
- max_tokens=500
165
- )
166
- result = response.choices[0].message.content.strip()
167
-
168
- # 3. Cache result in DB for future lookups
169
- try:
170
- with self.db.get_connection() as conn:
171
- cursor = conn.cursor()
172
- cursor.execute("""
173
- INSERT OR REPLACE INTO reference_markers
174
- (volume_num, page_num, marker_id, type, content)
175
- VALUES (?, ?, ?, 'abbrev', ?)
176
- """, (vol, page, lookup_id, result))
177
- except Exception:
178
- pass # Cache failure is non-critical
179
-
180
- return result
181
- except Exception as e:
182
- return f"ไม่สามารถขยายความได้ในขณะนี้: {str(e)}"
 
136
  if content.startswith('['):
137
  try:
138
  entries = json.loads(content)
139
+ if isinstance(entries, list):
140
+ if entries and isinstance(entries[0], str):
141
+ # Stored as plain string array (from preindex_abbrevs)
142
+ return "\n\n".join(entries)
143
+ # Stored as dict array with "expansion" keys
144
+ parts = [e.get("expansion", "") for e in entries if isinstance(e.get("expansion", ""), str)]
145
+ if parts:
146
+ return "\n\n".join(parts)
147
+ except (json.JSONDecodeError, TypeError, AttributeError):
148
  pass
149
+ return content # raw JSON fallback
150
  return content
151
 
152
+ # 2. LLM Fallback — DISABLED (all abbrevs pre-indexed in DB)
153
+ # If somehow missing, return a default message instead of calling AI
154
+ return f"(ย่อ) — ยังไม่มีข้อมูลขยายความในฐานข้อมูลเล่ม {vol} หน้า {page}"