Amogh1221 commited on
Commit
359edbb
·
verified ·
1 Parent(s): b65c302

Upload huggingface_llm_provider.py

Browse files
providers/llms/huggingface_llm_provider.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from huggingface_hub import InferenceClient
3
+ from core.ports import LLM1Output, LLM2Output, LLM3Output
4
+ from core.prompts import LLM1_SYSTEM_PROMPT, LLM2_SYSTEM_PROMPT, LLM3_SYSTEM_PROMPT
5
+
6
+
7
+ def _msg(role: str, content: str) -> dict:
8
+ return {"role": role, "content": content}
9
+
10
+
11
+ class HuggingFaceLLMProvider:
12
+ """
13
+ Concrete implementation of the LLMProvider protocol using the HuggingFace
14
+ Serverless Inference API (api-inference.huggingface.co).
15
+
16
+ Requires HF_TOKEN set as a Space Secret. Models used:
17
+ LLM1 / LLM3 : meta-llama/Llama-3.1-8B-Instruct (fast, free tier)
18
+ LLM2 : meta-llama/Llama-3.3-70B-Instruct (deep analysis)
19
+
20
+ NOTE: HF Inference API does NOT support prompt caching, so the
21
+ stable_prefix and medium_term_memory params are merged into regular
22
+ system messages instead.
23
+ """
24
+
25
+ def __init__(self, model1: str, model2: str):
26
+ token = os.environ.get("HF_TOKEN")
27
+ if not token:
28
+ raise RuntimeError(
29
+ "HF_TOKEN is not set. Add it as a Space Secret on HuggingFace."
30
+ )
31
+ self.model1 = model1
32
+ self.model2 = model2
33
+ # Single client — the model is passed per-call so one client is enough
34
+ self.client = InferenceClient(api_key=token)
35
+
36
+ # ------------------------------------------------------------------
37
+ # Internal helpers
38
+ # ------------------------------------------------------------------
39
+ def _build_stable_system(
40
+ self, patient_info: dict | None, medium_term_memory: str | None
41
+ ) -> str | None:
42
+ """Build the demographics + MTM system block (replaces Groq's cache_control)."""
43
+ parts = []
44
+ if patient_info:
45
+ parts.append(
46
+ f"PATIENT DEMOGRAPHICS:\n"
47
+ f"- Name: {patient_info.get('name', 'Unknown')}\n"
48
+ f"- Age: {patient_info.get('age', 'Unknown')}\n"
49
+ f"- Gender: {patient_info.get('gender', 'Unknown')}\n"
50
+ f"- Nationality: {patient_info.get('nationality', 'Unknown')}\n"
51
+ f"- Primary Concern: {patient_info.get('primary_concern', 'Unknown')}"
52
+ )
53
+ if medium_term_memory:
54
+ parts.append(
55
+ f"PATIENT MEDIUM-TERM MEMORY (prior sessions & patterns — do not quote verbatim):\n"
56
+ f"{medium_term_memory}"
57
+ )
58
+ return "\n\n".join(parts) if parts else None
59
+
60
+ def _call(self, model: str, messages: list, temperature: float, max_tokens: int) -> str:
61
+ response = self.client.chat.completions.create(
62
+ model=model,
63
+ messages=messages,
64
+ response_format={"type": "json_object"},
65
+ temperature=temperature,
66
+ max_tokens=max_tokens,
67
+ )
68
+ return response.choices[0].message.content
69
+
70
+ # ------------------------------------------------------------------
71
+ # LLMProvider Protocol methods
72
+ # ------------------------------------------------------------------
73
+ def generate_opening_context(self, profile_recap: str | None) -> list:
74
+ if profile_recap:
75
+ return [
76
+ {
77
+ "role": "user",
78
+ "content": (
79
+ f"{profile_recap}\n\n"
80
+ "Start the psychiatric session. Greet the patient warmly, "
81
+ "briefly acknowledge continuity if it feels right, then use && "
82
+ "to add an open question at the end — e.g. "
83
+ "'Welcome back, it's good to see you. && How have things been for you since we last spoke?'"
84
+ ),
85
+ }
86
+ ]
87
+ else:
88
+ return [
89
+ {
90
+ "role": "user",
91
+ "content": (
92
+ "Start the psychiatric session. Greet the patient warmly and naturally, "
93
+ "then use && to add an open question at the end — e.g. "
94
+ "'Hello, I'm Dr. Aiden. I'm here to listen. && What brings you here today?'"
95
+ ),
96
+ }
97
+ ]
98
+
99
+ def psychiatrist_response(
100
+ self,
101
+ context: list,
102
+ patient_info: dict = None,
103
+ medium_term_memory: str | None = None,
104
+ ) -> LLM1Output:
105
+ try:
106
+ messages = [_msg("system", LLM1_SYSTEM_PROMPT)]
107
+ stable = self._build_stable_system(patient_info, medium_term_memory)
108
+ if stable:
109
+ messages.append(_msg("system", stable))
110
+ for m in context:
111
+ messages.append(_msg(m["role"], m["content"]))
112
+
113
+ raw = self._call(self.model1, messages, temperature=0.6, max_tokens=1024)
114
+ return LLM1Output.model_validate_json(raw)
115
+ except Exception as e:
116
+ print(f"[HF LLM1 ERROR] {e}")
117
+ raise e
118
+
119
+ def internal_reasoning(
120
+ self,
121
+ context: list,
122
+ stable_prefix: str | None = None,
123
+ ) -> LLM2Output:
124
+ try:
125
+ messages = [_msg("system", LLM2_SYSTEM_PROMPT)]
126
+ # stable_prefix = demographics + profile recap (built by orchestrator)
127
+ if stable_prefix:
128
+ messages.append(_msg("user", stable_prefix))
129
+ for m in context:
130
+ messages.append(_msg(m["role"], m["content"]))
131
+
132
+ raw = self._call(self.model2, messages, temperature=0.1, max_tokens=1024)
133
+ return LLM2Output.model_validate_json(raw)
134
+ except Exception as e:
135
+ print(f"[HF LLM2 ERROR] {e}")
136
+ raise e
137
+
138
+ def psychiatrist_query_response(self, context: list, retrieved_context: str) -> str:
139
+ try:
140
+ sys_prompt = (
141
+ "You are a compassionate AI psychiatrist. The patient has asked for advice. "
142
+ "Synthesize a thoughtful response using the following clinical guidelines:\n\n"
143
+ f"{retrieved_context}\n\n"
144
+ "Keep your response empathetic, natural, and under 4 sentences. Do not use markdown."
145
+ )
146
+ messages = [_msg("system", sys_prompt)]
147
+ for m in context:
148
+ messages.append(_msg(m["role"], m["content"]))
149
+
150
+ response = self.client.chat.completions.create(
151
+ model=self.model1,
152
+ messages=messages,
153
+ temperature=0.5,
154
+ max_tokens=512,
155
+ )
156
+ return response.choices[0].message.content.strip()
157
+ except Exception as e:
158
+ print(f"[HF QUERY ERROR] {e}")
159
+ raise e
160
+
161
+ def generate_end_of_session_profile(
162
+ self, old_profile: dict, session_history: list, patient_info: dict = None
163
+ ) -> LLM3Output:
164
+ try:
165
+ messages = [_msg("system", LLM3_SYSTEM_PROMPT)]
166
+ if patient_info:
167
+ demographics = (
168
+ f"PATIENT DEMOGRAPHICS:\n"
169
+ f"- Name: {patient_info.get('name', 'Unknown')}\n"
170
+ f"- Age: {patient_info.get('age', 'Unknown')}\n"
171
+ f"- Gender: {patient_info.get('gender', 'Unknown')}\n"
172
+ f"- Nationality: {patient_info.get('nationality', 'Unknown')}\n"
173
+ f"- Primary Concern: {patient_info.get('primary_concern', 'Unknown')}"
174
+ )
175
+ messages.append(_msg("system", demographics))
176
+
177
+ formatted_history = "\n".join(
178
+ f"{t['role'].upper()}: {t['content']}" for t in session_history
179
+ )
180
+ user_prompt = (
181
+ f"EXISTING PROFILE:\n{old_profile}\n\n"
182
+ f"RECENT SESSION TRANSCRIPT:\n{formatted_history}\n\n"
183
+ "Please generate the 100-200 word session_summary and the merged, deduplicated clinical profile."
184
+ )
185
+ messages.append(_msg("user", user_prompt))
186
+
187
+ raw = self._call(self.model1, messages, temperature=0.2, max_tokens=1500)
188
+ return LLM3Output.model_validate_json(raw)
189
+ except Exception as e:
190
+ print(f"[HF LLM3 ERROR] {e}")
191
+ raise e
192
+
193
+ def summarize_history(self, turns: list) -> str:
194
+ if not turns:
195
+ return ""
196
+ try:
197
+ formatted = "\n".join(
198
+ f"{t['role'].upper()}: {t['content']}" for t in turns
199
+ )
200
+ messages = [
201
+ _msg(
202
+ "system",
203
+ "You are a clinical note-taker. Summarize the following "
204
+ "patient-psychiatrist conversation excerpt in 3-5 sentences, "
205
+ "third-person clinical register. Capture key symptoms, "
206
+ "themes, and any safety concerns. Be concise.",
207
+ ),
208
+ _msg("user", formatted),
209
+ ]
210
+ response = self.client.chat.completions.create(
211
+ model=self.model1,
212
+ messages=messages,
213
+ temperature=0.3,
214
+ max_tokens=300,
215
+ )
216
+ return response.choices[0].message.content.strip()
217
+ except Exception as e:
218
+ print(f"[HF SUMMARIZE ERROR] {e}")
219
+ raise e