Skip to content

fix(server): update /v1/chat/completions endpoint to be backwards com…#1734

Open
christinaexyou wants to merge 1 commit intoNVIDIA-NeMo:developfrom
christinaexyou:fix-chat-completions
Open

fix(server): update /v1/chat/completions endpoint to be backwards com…#1734
christinaexyou wants to merge 1 commit intoNVIDIA-NeMo:developfrom
christinaexyou:fix-chat-completions

Conversation

@christinaexyou
Copy link
Copy Markdown
Contributor

@christinaexyou christinaexyou commented Mar 17, 2026

…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

  • I've read the CONTRIBUTING guidelines.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary by CodeRabbit

New Features

  • The OpenAI chat completion endpoint's model parameter now supports both string and dictionary formats. When provided as a dictionary, it should include an id field containing the model identifier. This enhancement offers increased flexibility in model specification for API requests while maintaining full backward compatibility with existing implementations.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR adds a Pydantic field_validator (mode="before") to OpenAIChatCompletionRequest.model that transparently coerces a dict payload (e.g. {"id": "gpt-4o", "name": "gpt-4o", ...}) into the plain string model identifier expected by the rest of the server. This restores backward compatibility for web-UI clients that send the model as a rich object rather than a bare string.

  • Schema change (nemoguardrails/server/schemas/openai.py): normalize_model validator extracts id from a dict or passes strings through unchanged; raises ValueError if id is missing or not a string.
  • New test (tests/server/test_openai_integration.py): test_chat_completion_model_as_dict covers the happy path via raw TestClient.post, correctly bypassing OpenAI SDK string coercion.
  • Minor gap: The validator accepts an empty string "" as a valid id, which would propagate to the LLM provider instead of failing early with a 422.

Confidence Score: 4/5

  • Safe to merge; the fix is minimal and well-targeted, with one small edge case (empty string id) worth addressing.
  • The core logic is correct and the happy-path test validates the intended behavior. The only notable gap is the empty-string id edge case and the missing error-branch tests, neither of which represents a regression or a functional break for real-world usage.
  • No files require special attention beyond the empty-string guard noted in nemoguardrails/server/schemas/openai.py.

Important Files Changed

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"]
Loading
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]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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.

Comment on lines +89 to +103
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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Model Field Validation
nemoguardrails/server/schemas/openai.py
Adds normalize_model validator to OpenAIChatCompletionRequest that converts dict-formatted models (with id key) to strings while maintaining backward compatibility with string inputs.
Test Coverage
tests/server/test_openai_integration.py
Adds test_chat_completion_model_as_dict() to verify the /v1/chat/completions endpoint correctly processes model parameter when provided as a dictionary structure.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title references backward compatibility for the /v1/chat/completions endpoint, which directly matches the main change: adding model field validation to accept both strings and objects.
Linked Issues check ✅ Passed The code changes successfully implement Option A from issue #1728 by adding a validator that converts model objects to strings, allowing the API to accept both formats.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: schema validation in openai.py and corresponding test coverage in test_openai_integration.py.
Test Results For Major Changes ✅ Passed PR contains minor backward compatibility fix with field validator and appropriate test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between f2e7beb and c167f3a.

📒 Files selected for processing (2)
  • nemoguardrails/server/schemas/openai.py
  • tests/server/test_openai_integration.py

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 19, 2026

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
nemoguardrails/server/schemas/openai.py 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Breaking API change in v0.21.0 - UI sends model as object but API expects string

1 participant