Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/openhuman/inference/http/http_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,39 @@ fn strip_temperature_suffix_only_removes_numeric_suffixes() {
assert_eq!(strip_temperature_suffix("llama3.1:8b@1"), "llama3.1:8b");
assert_eq!(strip_temperature_suffix("gpt@beta"), "gpt@beta");
}

/// Asserts that `ChatCompletionRequest` parses `max_completion_tokens` and respects
/// fallback precedence over `max_tokens` (Refs #5498).
#[test]
fn test_chat_completion_request_deserializes_max_completion_tokens() {
use crate::openhuman::inference::http::types::ChatCompletionRequest;

let json_both = serde_json::json!({
"model": "gpt-5",
"messages": [{ "role": "user", "content": "hello" }],
"max_tokens": 100,
"max_completion_tokens": 200
});
let parsed: ChatCompletionRequest = serde_json::from_value(json_both).unwrap();
assert_eq!(parsed.max_tokens, Some(100));
assert_eq!(parsed.max_completion_tokens, Some(200));
assert_eq!(
parsed.max_completion_tokens.or(parsed.max_tokens),
Some(200)
);

let json_legacy = serde_json::json!({
"model": "gpt-4o",
"messages": [{ "role": "user", "content": "hello" }],
"max_tokens": 150
});
let parsed_legacy: ChatCompletionRequest = serde_json::from_value(json_legacy).unwrap();
assert_eq!(parsed_legacy.max_tokens, Some(150));
assert_eq!(parsed_legacy.max_completion_tokens, None);
assert_eq!(
parsed_legacy
.max_completion_tokens
.or(parsed_legacy.max_tokens),
Some(150)
);
}
5 changes: 4 additions & 1 deletion src/openhuman/inference/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,12 @@ async fn chat_completions_handler(
let completion_id = format!("chatcmpl-{}", uuid::Uuid::new_v4());
let created = chrono::Utc::now().timestamp();
let model_name = req.model.clone();
let model_request = ModelRequest::new(messages)
let mut model_request = ModelRequest::new(messages)
.with_model(model_id.clone())
.with_temperature(temperature);
if let Some(tokens) = req.max_completion_tokens.or(req.max_tokens) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium tests confident

Test the handler branch that forwards max_completion_tokens

The chat_completions_handler gained a new branch that calls model_request.with_max_tokens() when either max_completion_tokens or max_tokens is present. The existing test suite only exercises serde deserialization; the handler's logic — the or fallback and the actual forwarding into the model request — has no coverage. If with_max_tokens has a bug or the fallback semantics change, no test will catch it. Add a test that exercises the full handler path (e.g. via axum::test or a request against the mounted router) with both fields present, only max_tokens, only max_completion_tokens, and neither.

[RULE] untested-behaviour ·

model_request = model_request.with_max_tokens(tokens);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if req.stream {
let model_stream = match chat_model.stream(&(), model_request).await {
Expand Down
9 changes: 9 additions & 0 deletions src/openhuman/inference/http/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@ use serde::{Deserialize, Serialize};

// ── Chat Completions ──────────────────────────────────────────────────────────

/// Request payload for OpenAI-compatible chat completions (`POST /v1/chat/completions`).
#[derive(Debug, Deserialize)]
pub struct ChatCompletionRequest {
/// Identifier of the model to query.
pub model: String,
/// List of input messages in conversation order.
pub messages: Vec<ChatCompletionMessage>,
/// Whether to stream back partial progress via SSE chunks.
#[serde(default)]
pub stream: bool,
/// Sampling temperature between 0.0 and 2.0.
#[serde(default)]
pub temperature: Option<f64>,
/// Legacy maximum tokens limit (superseded by `max_completion_tokens`).
#[serde(default)]
pub max_tokens: Option<u32>,
/// Maximum number of output tokens for newer OpenAI / reasoning models (#5498).
#[serde(default)]
pub max_completion_tokens: Option<u32>,
/// Optional tool definitions (ignored if the provider doesn't support them).
#[serde(default)]
pub tools: Option<serde_json::Value>,
Expand Down
Loading