use serde::Serialize;
use validator::Validate;
use super::super::{chat_base_request::*, tools::*, traits::*};
use crate::{client::ZaiClient, model::async_chat_get::AsyncResponse};
pub struct AsyncChatCompletion<N, M>
where
N: ChatRequestModel + AsyncChat,
M: Serialize,
(N, M): Bounded,
ChatBody<N, M>: Serialize,
{
body: ChatBody<N, M>,
}
impl<N, M> AsyncChatCompletion<N, M>
where
N: ChatRequestModel + AsyncChat,
M: Serialize,
(N, M): Bounded,
ChatBody<N, M>: Serialize,
{
pub fn new(model: N, messages: M) -> Self {
Self {
body: ChatBody::new(model, messages),
}
}
pub const fn body(&self) -> &ChatBody<N, M> {
&self.body
}
pub fn add_message(mut self, message: M) -> Self {
self.body = self.body.add_message(message);
self
}
pub fn extend_messages(mut self, messages: impl IntoIterator<Item = M>) -> Self {
self.body = self.body.extend_messages(messages);
self
}
pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
self.body = self.body.with_request_id(request_id);
self
}
pub fn with_do_sample(mut self, do_sample: bool) -> Self {
self.body = self.body.with_do_sample(do_sample);
self
}
pub fn with_temperature(mut self, temperature: f64) -> Self {
self.body = self.body.with_temperature(temperature);
self
}
pub fn with_top_p(mut self, top_p: f64) -> Self {
self.body = self.body.with_top_p(top_p);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.body = self.body.with_max_tokens(max_tokens);
self
}
pub fn add_tool(mut self, tool: N::Tool) -> Self
where
N: ChatToolSupport,
{
self.body = self.body.add_tool(tool);
self
}
pub fn add_tools(mut self, tools: impl IntoIterator<Item = N::Tool>) -> Self
where
N: ChatToolSupport,
{
self.body = self.body.add_tools(tools);
self
}
pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self
where
N: ChatToolSupport,
{
self.body = self.body.with_tool_choice(tool_choice);
self
}
pub fn clear_tools(mut self) -> Self
where
N: ChatToolSupport,
{
self.body = self.body.clear_tools();
self
}
pub fn with_response_format(mut self, format: ResponseFormat) -> Self
where
N: ResponseFormatEnable,
{
self.body = self.body.with_response_format(format);
self
}
pub fn with_watermark_enabled(mut self, enabled: bool) -> Self
where
N: WatermarkEnable,
{
self.body = self.body.with_watermark_enabled(enabled);
self
}
pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
self.body = self.body.with_user_id(user_id);
self
}
pub fn with_stop(mut self, stop: impl Into<String>) -> Self {
self.body = self.body.with_stop(stop);
self
}
pub fn with_thinking(mut self, thinking: ThinkingType) -> Self
where
N: ThinkEnable,
{
self.body = self.body.with_thinking(thinking);
self
}
pub fn with_reasoning_effort(mut self, effort: ReasoningEffort) -> Self
where
N: ReasoningEffortEnable,
{
self.body = self.body.with_reasoning_effort(effort);
self
}
pub fn validate(&self) -> crate::ZaiResult<()> {
self.body
.validate()
.map_err(crate::client::error::ZaiError::from)?;
Ok(())
}
pub async fn send_via(&self, client: &ZaiClient) -> crate::ZaiResult<AsyncResponse>
where
N: serde::Serialize,
M: serde::Serialize,
{
self.validate()?;
let route = crate::client::routes::CHAT_COMPLETE_ASYNC;
let url = client.endpoints().resolve_route(route, &[])?;
let response = client
.send_json::<_, AsyncResponse>(route.method(), url, &self.body)
.await?;
response.validate()?;
Ok(response)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{
chat_message_types::{TextMessage, VoiceMessage},
chat_models::{GLM4_voice, GLM5_2},
};
#[test]
fn async_text_schema_never_serializes_stream_fields() {
let request = AsyncChatCompletion::new(GLM5_2 {}, TextMessage::user("hello"))
.add_tool(Tools::Function {
function: Function::new("lookup", "Lookup", serde_json::json!({"type": "object"})),
})
.with_tool_choice(ToolChoice::auto())
.with_response_format(ResponseFormat::JsonObject);
let json = serde_json::to_value(request.body()).unwrap();
assert_eq!(json["tool_choice"], "auto");
assert_eq!(json["response_format"]["type"], "json_object");
assert!(json.get("stream").is_none());
assert!(json.get("tool_stream").is_none());
assert!(json.get("watermark_enabled").is_none());
}
#[test]
fn async_audio_schema_serializes_only_its_specialized_field() {
let request = AsyncChatCompletion::new(GLM4_voice {}, VoiceMessage::new_user())
.with_watermark_enabled(true);
let json = serde_json::to_value(request.body()).unwrap();
assert_eq!(json["watermark_enabled"], true);
for field in [
"stream",
"tool_stream",
"tools",
"tool_choice",
"response_format",
] {
assert!(
json.get(field).is_none(),
"unexpected async audio field: {field}"
);
}
}
}