fix(server): update /v1/chat/completions endpoint to be backwards com…#1734
fix(server): update /v1/chat/completions endpoint to be backwards com…#1734christinaexyou wants to merge 1 commit intoNVIDIA-NeMo:developfrom
Conversation
Greptile SummaryThis PR adds a Pydantic
|
| Filename | Overview |
|---|---|
| nemoguardrails/server/schemas/openai.py | Adds a field_validator on model (mode="before") to coerce dict inputs into a string by extracting the id key; logic is correct but passes empty strings through silently. |
| tests/server/test_openai_integration.py | Adds a happy-path integration test for the dict-model input; correctly uses raw TestClient to bypass SDK string coercion, but the error branch of the validator is not exercised. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["POST /v1/chat/completions\n(raw JSON body)"] --> B["Pydantic: OpenAIChatCompletionRequest\nfield_validator 'model' mode=before"]
B --> C{Is model a dict?}
C -- Yes --> D{"Has string 'id' field?"}
D -- No --> E["raise ValueError\n422 Unprocessable Entity"]
D -- Yes --> F["return v['id'] (string)"]
C -- No --> G["return v as-is\n(Pydantic validates str)"]
F --> H["model: str set on request"]
G --> H
H --> I["GuardrailsChatCompletionRequest\n→ LLM pipeline"]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: nemoguardrails/server/schemas/openai.py
Line: 61-64
Comment:
**Empty string `id` silently accepted**
The guard `not isinstance(v.get("id"), str)` passes when `id` is `""` (an empty string). An empty model identifier would propagate downstream and likely produce a confusing runtime failure (e.g., an empty model name being sent to the LLM provider) rather than a clean 422 validation error at the boundary.
Consider tightening the check to require a non-empty string:
```suggestion
if not isinstance(v.get("id"), str) or not v["id"]:
raise ValueError("Model object must contain a non-empty string 'id' field")
return v["id"]
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 9d024bc
|
|
||
| @field_validator("model", mode="before") | ||
| @classmethod | ||
| def normalize_model(cls, v: Any) -> Optional[str]: |
There was a problem hiding this comment.
Misleading
Optional[str] return type annotation
The return type is annotated as Optional[str], but the model field is declared as a required str (not Optional[str]). If the validator ever returns None (e.g., when an input of null is passed), Pydantic will raise its own type error rather than treating it as a valid value. The Optional[str] annotation therefore misleads readers into thinking None is a valid return value from this validator, when in practice it is not.
| def normalize_model(cls, v: Any) -> Optional[str]: | |
| def normalize_model(cls, v: Any) -> str: |
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/server/schemas/openai.py
Line: 60
Comment:
**Misleading `Optional[str]` return type annotation**
The return type is annotated as `Optional[str]`, but the `model` field is declared as a required `str` (not `Optional[str]`). If the validator ever returns `None` (e.g., when an input of `null` is passed), Pydantic will raise its own type error rather than treating it as a valid value. The `Optional[str]` annotation therefore misleads readers into thinking `None` is a valid return value from this validator, when in practice it is not.
```suggestion
def normalize_model(cls, v: Any) -> str:
```
How can I resolve this? If you propose a fix, please make it concise.| def test_chat_completion_model_as_dict(): | ||
| test_client = TestClient(api.app) | ||
| response = test_client.post( | ||
| "/v1/chat/completions", | ||
| json={ | ||
| "model": {"id": "gpt-4o", "name": "gpt-4o", "maxLength": 10000}, | ||
| "messages": [{"role": "user", "content": "hi"}], | ||
| "guardrails": {"config_id": "with_custom_llm"}, | ||
| }, | ||
| ) | ||
| assert response.status_code == 200 | ||
| data = response.json() | ||
| assert data["model"] == "gpt-4o" | ||
| assert data["choices"][0]["message"]["content"] == "Custom LLM response" | ||
| assert data["choices"][0]["finish_reason"] == "stop" |
There was a problem hiding this comment.
Missing error-case test coverage for the new validator
The new test only covers the happy path (a dict with a valid string id). The validator also has an explicit error branch — raise ValueError("Model object must contain a string 'id' field") — that is triggered when id is absent or is not a string. Consider adding tests for:
- A model dict where
idis missing entirely, e.g.{"model": {"name": "gpt-4o"}}. - A model dict where
idis a non-string type, e.g.{"model": {"id": 42}}.
Both cases should return a 422 Unprocessable Entity response. Without these tests the error path is untested and could silently regress.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/server/test_openai_integration.py
Line: 89-103
Comment:
**Missing error-case test coverage for the new validator**
The new test only covers the happy path (a dict with a valid string `id`). The validator also has an explicit error branch — `raise ValueError("Model object must contain a string 'id' field")` — that is triggered when `id` is absent or is not a string. Consider adding tests for:
- A model dict where `id` is missing entirely, e.g. `{"model": {"name": "gpt-4o"}}`.
- A model dict where `id` is a non-string type, e.g. `{"model": {"id": 42}}`.
Both cases should return a `422 Unprocessable Entity` response. Without these tests the error path is untested and could silently regress.
How can I resolve this? If you propose a fix, please make it concise.
📝 WalkthroughWalkthroughAdds a field validator to accept the model parameter in OpenAI chat completion requests as either a string or a dictionary with an id key, resolving compatibility with the web UI. Includes a corresponding test case validating dictionary-format model handling. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip Flake8 can be used to improve the quality of Python code reviews.Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script. To configure Flake8, add a '.flake8' or 'setup.cfg' file to your project root. See Flake8 Documentation for more details. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/server/test_openai_integration.py (1)
89-103: Add a malformed-model negative test to pin validator behavior.This new test covers the happy path well; adding one 422 case for invalid dict shape would make the compatibility contract much safer against regressions.
✅ Suggested test addition
+def test_chat_completion_model_as_dict_invalid_id(): + test_client = TestClient(api.app) + response = test_client.post( + "/v1/chat/completions", + json={ + "model": {"name": "gpt-4o"}, # missing string "id" + "messages": [{"role": "user", "content": "hi"}], + "guardrails": {"config_id": "with_custom_llm"}, + }, + ) + assert response.status_code == 422🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/server/test_openai_integration.py` around lines 89 - 103, Add a negative test alongside test_chat_completion_model_as_dict that posts to "/v1/chat/completions" with an invalid model dict shape (e.g., missing required keys or wrong types) and assert the server returns a 422 status; locate the test near test_chat_completion_model_as_dict in tests/server/test_openai_integration.py, call the same TestClient(api.app), send a malformed "model" payload, and assert response.status_code == 422 and that the response body contains validation error details to pin expected validator behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/server/test_openai_integration.py`:
- Around line 89-103: Add a negative test alongside
test_chat_completion_model_as_dict that posts to "/v1/chat/completions" with an
invalid model dict shape (e.g., missing required keys or wrong types) and assert
the server returns a 422 status; locate the test near
test_chat_completion_model_as_dict in tests/server/test_openai_integration.py,
call the same TestClient(api.app), send a malformed "model" payload, and assert
response.status_code == 422 and that the response body contains validation error
details to pin expected validator behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4e340869-0bc5-4b40-85ef-31d0a6061e8b
📒 Files selected for processing (2)
nemoguardrails/server/schemas/openai.pytests/server/test_openai_integration.py
c167f3a to
9d024bc
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…patabile
Description
This PR adds a field validator to the OpenAIChatCompletionRequest schema to convert models from objects to strings, ensuring backward compatibility of the web UI.
Related Issue(s)
Fixes #1728
Checklist
Summary by CodeRabbit
New Features