NeonCharlie-24 commited on
Commit
b2e8e99
·
unverified ·
1 Parent(s): be70bd4

Fix/json output (#40)

Browse files

* added json response mime type to generate_contextual_clarification.

* added structured JSON output support for vLLM client.

* added structured JSON output to get_top_personas and handled dict-wrapped responses.

multi_llm_chatbot_backend/app/core/improved_orchestrator.py CHANGED
@@ -226,6 +226,7 @@ class ImprovedChatOrchestrator:
226
  context=[{"role": "user", "content": user_prompt}],
227
  temperature=0.4,
228
  max_tokens=1024,
 
229
  )
230
 
231
  cleaned = raw.strip()
@@ -793,7 +794,8 @@ When analyzing the document context:
793
  system_prompt=f"You are an assistant that selects the best advisors for a user of {app_title}.",
794
  context=[{"role": "user", "content": prompt}],
795
  temperature=0.4,
796
- max_tokens=150
 
797
  )
798
 
799
  # Step 1: Try direct JSON load
@@ -804,6 +806,10 @@ When analyzing the document context:
804
  top_ids = re.findall(r'"(.*?)"', llm_response)
805
  logger.warning(f"Fallback JSON extraction used: {top_ids}")
806
 
 
 
 
 
807
  # Step 3: Filter valid persona IDs
808
  valid_ids = [pid for pid in top_ids if pid in self.personas]
809
 
 
226
  context=[{"role": "user", "content": user_prompt}],
227
  temperature=0.4,
228
  max_tokens=1024,
229
+ response_mime_type="application/json"
230
  )
231
 
232
  cleaned = raw.strip()
 
794
  system_prompt=f"You are an assistant that selects the best advisors for a user of {app_title}.",
795
  context=[{"role": "user", "content": prompt}],
796
  temperature=0.4,
797
+ max_tokens=150,
798
+ response_mime_type="application/json"
799
  )
800
 
801
  # Step 1: Try direct JSON load
 
806
  top_ids = re.findall(r'"(.*?)"', llm_response)
807
  logger.warning(f"Fallback JSON extraction used: {top_ids}")
808
 
809
+ # Handle models that wrap the list in an object (e.g. {"advisor_ids": [...]})
810
+ if isinstance(top_ids, dict):
811
+ top_ids = next(iter(top_ids.values()), [])
812
+
813
  # Step 3: Filter valid persona IDs
814
  valid_ids = [pid for pid in top_ids if pid in self.personas]
815
 
multi_llm_chatbot_backend/app/llm/improved_vllm_client.py CHANGED
@@ -42,13 +42,18 @@ class ImprovedVllmClient(LLMClient):
42
  if not self.model_name:
43
  await self.refresh_model()
44
 
45
- response = await self.client.chat.completions.create(
46
  model=self.model_name,
47
  messages=context_window.messages,
48
  temperature=temperature,
49
  max_tokens=max_tokens,
50
  )
51
 
 
 
 
 
 
52
  text = response.choices[0].message.content.strip()
53
  return self._clean_response(text)
54
 
 
42
  if not self.model_name:
43
  await self.refresh_model()
44
 
45
+ create_kwargs = dict(
46
  model=self.model_name,
47
  messages=context_window.messages,
48
  temperature=temperature,
49
  max_tokens=max_tokens,
50
  )
51
 
52
+ if response_mime_type == "application/json":
53
+ create_kwargs["response_format"] = {"type": "json_object"}
54
+
55
+ response = await self.client.chat.completions.create(**create_kwargs)
56
+
57
  text = response.choices[0].message.content.strip()
58
  return self._clean_response(text)
59
 
multi_llm_chatbot_backend/app/tests/unit/test_vllm_client.py CHANGED
@@ -160,6 +160,47 @@ class TestImprovedVllmClient(unittest.TestCase):
160
  ))
161
  self.assertIsNone(client.model_name)
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  # ------------------------------------------------------------------
164
  # _clean_response
165
  # ------------------------------------------------------------------
 
160
  ))
161
  self.assertIsNone(client.model_name)
162
 
163
+ # ------------------------------------------------------------------
164
+ # generate – response_format for JSON
165
+ # ------------------------------------------------------------------
166
+
167
+ def test_generate_passes_response_format_for_json(self, MockAsyncOpenAI, mock_get_ctx):
168
+ client = ImprovedVllmClient(
169
+ api_url=FAKE_URL, api_key=FAKE_KEY, model_name="test-model",
170
+ )
171
+ client.client.chat.completions.create = AsyncMock(
172
+ return_value=_make_completion_mock('{"key": "value"}')
173
+ )
174
+
175
+ asyncio.run(client.generate(
176
+ system_prompt="Return JSON",
177
+ context=[{"role": "user", "content": "Hi"}],
178
+ temperature=0.3,
179
+ max_tokens=100,
180
+ response_mime_type="application/json",
181
+ ))
182
+
183
+ call_kwargs = client.client.chat.completions.create.call_args.kwargs
184
+ self.assertEqual(call_kwargs["response_format"], {"type": "json_object"})
185
+
186
+ def test_generate_omits_response_format_when_no_mime_type(self, MockAsyncOpenAI, mock_get_ctx):
187
+ client = ImprovedVllmClient(
188
+ api_url=FAKE_URL, api_key=FAKE_KEY, model_name="test-model",
189
+ )
190
+ client.client.chat.completions.create = AsyncMock(
191
+ return_value=_make_completion_mock("plain text response")
192
+ )
193
+
194
+ asyncio.run(client.generate(
195
+ system_prompt="You are helpful.",
196
+ context=[{"role": "user", "content": "Hello"}],
197
+ temperature=0.7,
198
+ max_tokens=100,
199
+ ))
200
+
201
+ call_kwargs = client.client.chat.completions.create.call_args.kwargs
202
+ self.assertNotIn("response_format", call_kwargs)
203
+
204
  # ------------------------------------------------------------------
205
  # _clean_response
206
  # ------------------------------------------------------------------