HemanM commited on
Commit
16103a7
·
verified ·
1 Parent(s): e94ab3b

Create evo_inference.py

Browse files
Files changed (1) hide show
  1. evo_inference.py +112 -44
evo_inference.py CHANGED
@@ -1,62 +1,97 @@
1
  """
2
  evo_inference.py
3
- Step 5: Evo synthesis hook.
4
 
5
  (Objective)
6
- - Define `synthesize_with_evo(user_query, lang, hits)` that returns a clean,
7
- step-by-step style answer grounded in the retrieved chunks.
8
- - For now, we DO NOT use a neural generator; we synthesize from the hits to
9
- avoid hallucinations. Later, you can plug your Evo model here.
10
-
11
- How to integrate your real Evo model later (Objective):
12
- 1) Load your Evo weights once at module import time.
13
- 2) Build a prompt with the top retrieved chunks.
14
- 3) Generate a response (max_new_tokens ~ 200–300).
15
- 4) Always include the key fields users expect (docs required, fees, where to apply, timing).
16
- 5) Return the generated text.
17
  """
18
 
19
- from typing import List, Dict
20
  from utils_lang import L, normalize_lang
21
 
22
- MAX_SNIPPET_CHARS = 400 # (Objective) keep answer concise
 
 
 
 
 
 
 
 
 
 
23
 
 
24
 
25
- def _bulletize(snippet: str) -> str:
 
 
 
 
 
 
26
  """
27
- (Objective) Clean a text snippet for bullet display.
 
 
 
28
  """
29
- snippet = " ".join(snippet.split())
30
- if len(snippet) > MAX_SNIPPET_CHARS:
31
- snippet = snippet[:MAX_SNIPPET_CHARS] + "..."
32
- return f"- {snippet}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
 
 
34
 
35
- def synthesize_with_evo(user_query: str, lang: str, hits: List[Dict]) -> str:
36
- """
37
- (Objective)
38
- Build a grounded answer in the user's language from the retrieved hits.
39
- This is extractive + templated; swap with your Evo generator later.
 
 
 
 
 
 
 
 
40
 
41
- Inputs:
42
- user_query: the user's question (string)
43
- lang: 'en' | 'fr' | 'mfe'
44
- hits: list of dicts with keys: 'text', 'meta', 'score'
45
 
46
- Output:
47
- A markdown string to show in the UI.
 
48
  """
49
- lang = normalize_lang(lang)
50
-
51
  if not hits:
52
  return L(lang, "intro_err")
53
 
54
- # Take the top ~4 chunks and present them as actionable bullets.
55
- bullets = [_bulletize(h["text"]) for h in hits[:4]]
56
- bullets_md = "\n".join(bullets)
57
 
58
- # Tiny language-specific headings (Objective)
59
- headings = {
60
  "en": [
61
  "• Step 1: Check eligibility & gather required documents.",
62
  "• Step 2: Confirm fees & payment options.",
@@ -75,14 +110,47 @@ def synthesize_with_evo(user_query: str, lang: str, hits: List[Dict]) -> str:
75
  "• Step 3: Fer demand online ouswa dan biro ki indike.",
76
  "• Step 4: Gard referans/reso; swiv letan tretman.",
77
  ],
78
- }[lang]
79
 
80
- # Compose final answer (Objective)
81
- intro = L(lang, "intro_ok")
82
  md = (
83
- f"**{intro}**\n\n"
84
  f"**Q:** {user_query}\n\n"
85
- f"**Key information:**\n{bullets_md}\n\n"
86
- f"**Suggested steps:**\n" + "\n".join(headings)
87
  )
88
  return md
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  evo_inference.py
3
+ Step 8: Evo-ready synthesis with plugin support + safe fallback.
4
 
5
  (Objective)
6
+ - Try to import your real Evo plugin: `evo_plugin.load_model()`.
7
+ - If not present, fall back to `evo_plugin_example.load_model()`.
8
+ - Provide `synthesize_with_evo(...)` that builds a grounded prompt from retrieved hits
9
+ and either:
10
+ a) calls the generator (generative mode), or
11
+ b) returns a clean extractive answer (safe mode).
 
 
 
 
 
12
  """
13
 
14
+ from typing import List, Dict, Optional
15
  from utils_lang import L, normalize_lang
16
 
17
+ # Try to load your real Evo plugin first; else example plugin
18
+ _GENERATOR = None
19
+ try:
20
+ from evo_plugin import load_model as _load_real # your file (optional)
21
+ _GENERATOR = _load_real()
22
+ except Exception:
23
+ try:
24
+ from evo_plugin_example import load_model as _load_example
25
+ _GENERATOR = _load_example()
26
+ except Exception:
27
+ _GENERATOR = None # fallback to extractive-only
28
 
29
+ MAX_SNIPPET_CHARS = 400 # (Objective) keep context concise
30
 
31
+
32
+ def _snippet(text: str) -> str:
33
+ text = " ".join(text.split())
34
+ return text[:MAX_SNIPPET_CHARS] + ("..." if len(text) > MAX_SNIPPET_CHARS else "")
35
+
36
+
37
+ def _build_grounded_prompt(question: str, lang: str, hits: List[Dict]) -> str:
38
  """
39
+ (Objective) Construct a compact prompt that includes:
40
+ - role + response style instruction (language-aware),
41
+ - the user's question,
42
+ - the top retrieved chunks as "Context #i".
43
  """
44
+ lang = normalize_lang(lang)
45
+ if lang == "fr":
46
+ system = (
47
+ "Tu es le Copilote Gouvernemental de Maurice. Réponds dans la langue demandée, "
48
+ "clairement et étape par étape, en te basant STRICTEMENT sur le contexte. "
49
+ "Inclure: documents requis, frais, où postuler, délais. Si une info manque, dis-le."
50
+ )
51
+ elif lang == "mfe":
52
+ system = (
53
+ "To enn Copilot Gouv Moris. Reponn dan langaz itilizater, kler ek pas-a-pas, "
54
+ "bas lor KI SUIVAN. Met: ki dokiman bizin, fre, kot pou al, delai. "
55
+ "Si pa ase info, dir li."
56
+ )
57
+ else:
58
+ system = (
59
+ "You are the Mauritius Government Copilot. Answer in the user's language, "
60
+ "clearly and step-by-step, using ONLY the provided context. Include: required documents, "
61
+ "fees, where to apply, processing time. If something is missing, say so."
62
+ )
63
 
64
+ ctx_lines = []
65
+ for i, h in enumerate(hits[:6], 1):
66
+ ctx_lines.append(f"[Context #{i}] { _snippet(h['text']) }")
67
 
68
+ ctx_block = "\n".join(ctx_lines) if ctx_lines else "[Context] (none)"
69
+ prompt = (
70
+ f"{system}\n\n"
71
+ f"[Question]\n{question}\n\n"
72
+ f"{ctx_block}\n\n"
73
+ f"[Instructions]\n"
74
+ f"- Be concise (6–10 lines).\n"
75
+ f"- Use bullet steps.\n"
76
+ f"- Do not invent links or fees; mention if unknown.\n"
77
+ f"- Answer in language code: {lang}.\n"
78
+ f"[Answer]\n"
79
+ )
80
+ return prompt
81
 
 
 
 
 
82
 
83
+ def _extractive_answer(user_query: str, lang: str, hits: List[Dict]) -> str:
84
+ """
85
+ (Objective) The safe fallback: bullet points from hits + standard steps.
86
  """
 
 
87
  if not hits:
88
  return L(lang, "intro_err")
89
 
90
+ bullets = []
91
+ for h in hits[:4]:
92
+ bullets.append(f"- {_snippet(h['text'])}")
93
 
94
+ steps = {
 
95
  "en": [
96
  "• Step 1: Check eligibility & gather required documents.",
97
  "• Step 2: Confirm fees & payment options.",
 
110
  "• Step 3: Fer demand online ouswa dan biro ki indike.",
111
  "• Step 4: Gard referans/reso; swiv letan tretman.",
112
  ],
113
+ }[normalize_lang(lang)]
114
 
 
 
115
  md = (
116
+ f"**{L(lang, 'intro_ok')}**\n\n"
117
  f"**Q:** {user_query}\n\n"
118
+ f"**Key information:**\n" + "\n".join(bullets) + "\n\n"
119
+ f"**Suggested steps:**\n" + "\n".join(steps)
120
  )
121
  return md
122
+
123
+
124
+ def synthesize_with_evo(
125
+ user_query: str,
126
+ lang: str,
127
+ hits: List[Dict],
128
+ mode: str = "extractive", # "extractive" | "generative"
129
+ max_new_tokens: int = 192,
130
+ temperature: float = 0.4,
131
+ ) -> str:
132
+ """
133
+ (Objective)
134
+ - If mode == 'generative' and a generator is available, build a grounded prompt and generate.
135
+ - Else, return the safe extractive answer.
136
+ """
137
+ lang = normalize_lang(lang)
138
+
139
+ if mode != "generative" or _GENERATOR is None:
140
+ return _extractive_answer(user_query, lang, hits)
141
+
142
+ prompt = _build_grounded_prompt(user_query, lang, hits)
143
+ try:
144
+ text = _GENERATOR.generate(
145
+ prompt=prompt,
146
+ max_new_tokens=int(max_new_tokens),
147
+ temperature=float(temperature),
148
+ )
149
+ # In case the generator echos or misses structure, still return something readable
150
+ text = text.strip()
151
+ if not text:
152
+ return _extractive_answer(user_query, lang, hits)
153
+ return text
154
+ except Exception:
155
+ # Any runtime issue falls back to safe mode
156
+ return _extractive_answer(user_query, lang, hits)