Spaces:
Sleeping
Sleeping
NeonCharlie-24 commited on
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 |
-
|
| 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 |
# ------------------------------------------------------------------
|