zeroclaw-labs/zeroclaw · error · SemanticEmptyTerminalCompletion
provider completed without final text or tool calls
Error message
provider completed without final text or tool calls
What it means
Azure OpenAI provider's require_terminal_text guard for string-only completions: it strips <think> tags from the content and, if nothing visible remains, raises the typed SemanticEmptyTerminalCompletion. A string-only API has no tool-call escape hatch, so an empty or reasoning-only result is a terminal failure, not a valid empty string.
Source
Thrown at crates/zeroclaw-providers/src/azure_openai.rs:71
#[derive(Debug, Deserialize)]
struct ResponseMessage {
#[serde(default)]
content: Option<String>,
}
impl ResponseMessage {
fn effective_content(&self) -> String {
self.content.clone().unwrap_or_default()
}
}
/// String-only completions have no native tool-call escape hatch. An empty or
/// reasoning-only result is therefore a typed terminal failure, not a valid
/// string result for direct callers that do not use the structured chat API.
fn require_terminal_text(content: String) -> anyhow::Result<String> {
if zeroclaw_api::model_provider::strip_think_tags(&content).is_empty() {
return Err(anyhow::Error::new(
zeroclaw_api::model_provider::SemanticEmptyTerminalCompletion,
));
}
Ok(content)
}
#[derive(Debug, Serialize)]
struct NativeChatRequest {
messages: Vec<NativeMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
tools: Option<Vec<NativeToolSpec>>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_choice: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
reasoning_effort: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]View on GitHub (pinned to 88bb9c8533)
Solutions
- Increase max_tokens on the deployment/request so the model finishes reasoning and emits final text.
- Switch the deployment (deployment id maps to a model version) to a non-reasoning chat model for string callers.
- Use the structured chat API if tool-call-only answers are legitimate for your flow.
- Check Azure content-filter results in the raw response; a filtered completion can arrive as empty content.
Example fix
// before
let text = azure.chat_with_system(Some(sys), user, "gpt-deploy", None).await?;
// deployment is a reasoning model, returns only <think>...</think>
// after: request a bigger budget / non-reasoning deployment
let text = azure.chat_with_system_opts(Some(sys), user, "gpt-4o-deploy", None, Opts { max_tokens: Some(4096), ..Default::default() }).await?; Defensive patterns
Strategy: fallback
Validate before calling
fn completion_is_usable(content: &str) -> bool {
!zeroclaw_api::model_provider::strip_think_tags(content).trim().is_empty()
} Try / catch
match azure.chat_with_system(system, message, deployment, temp).await {
Ok(text) => Ok(text),
Err(e) if e.is::<SemanticEmptyTerminalCompletion>() => {
// think-only or empty: switch deployment rather than retrying the same one
azure.chat_with_system(system, message, plain_deployment, temp).await
}
Err(e) => Err(e),
} Prevention
- Set explicit max_tokens on every Azure deployment used for string completions.
- Map deployment names to non-reasoning model versions for string-only integrations.
- Check content-filter results when an empty completion appears with a success status.
When it happens
Trigger: chat_with_system on Azure OpenAI returns content that is empty, whitespace, or entirely wrapped in think tags (reasoning models served via Azure, e.g. DeepSeek-R1 or o-series style endpoints), or finish_reason 'length' with only partial reasoning emitted.
Common situations: Deploying reasoning models through Azure OpenAI with default max_tokens (small), content filters blanking the response, deployment names resolving to a reasoning variant, prompt ends with a stop sequence before the answer.
Related errors
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/57c8f5bdd8050d2e.
Report an issue: GitHub.