Skip to content

CAMEL-24538: camel-openai - fix operator precedence so an empty userMessage does not drop the body prompt - #25939

Open
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24538
Open

CAMEL-24538: camel-openai - fix operator precedence so an empty userMessage does not drop the body prompt#25939
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24538

Conversation

@oscerd

@oscerd oscerd commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Issue

CAMEL-24538

Problem

OpenAIProducer.buildUserMessage resolves the user prompt with:

if (userPrompt == null || userPrompt.isEmpty() && ObjectHelper.isNotEmpty(config.getUserMessage())) {
    userPrompt = config.getUserMessage();
}

&& binds tighter than ||, so this parses as userPrompt == null || (userPrompt.isEmpty() && configHasMessage).
When the CamelOpenAIUserMessage header is absent (userPrompt == null) and the configured userMessage
option is an empty string, the first branch is already true, so userPrompt is set to that empty
string. buildTextMessage then does userPrompt != null ? userPrompt : body, picks the empty string over
the message body, and the request fails with "No input provided to LLM" — the body prompt is silently
dropped.

Fix

Parenthesise as (userPrompt == null || userPrompt.isEmpty()) && ObjectHelper.isNotEmpty(config.getUserMessage())
so the configured message is only substituted when it is actually set; otherwise userPrompt stays null and
the body is used. The identical precedence in the system-message branch is corrected too for consistency
(that one was benign because its downstream check uses isNotEmpty).

Testing

  • New OpenAIEmptyUserMessageBodyPromptTest configures the endpoint with an empty userMessage and asserts
    the body prompt still reaches the model. Verified it fails with "No input provided" against the
    unpatched code and passes with the fix.
  • mvn -Psourcecheck validate green.

Claude Code on behalf of oscerd

…essage does not drop the body prompt

buildUserMessage used `userPrompt == null || userPrompt.isEmpty() && isNotEmpty(config.getUserMessage())`.
Because && binds tighter than ||, this is `userPrompt == null || (userPrompt.isEmpty() && ...)`, so when
the USER_MESSAGE header is absent and the configured userMessage is an empty string, userPrompt was set to
that empty string and buildTextMessage then used "" instead of the message body, failing with
"No input provided". Parenthesize as `(userPrompt == null || userPrompt.isEmpty()) && isNotEmpty(...)` so
the configured message is only used when it is actually set, otherwise the body is used. The same
precedence is corrected for the (benign) system-message twin for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd oscerd added the bug Something isn't working label Aug 31, 2026
@oscerd oscerd added this to the 4.23.0 milestone Aug 31, 2026
@oscerd
oscerd requested review from Croway and davsclaus August 31, 2026 08:18
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@davsclaus davsclaus left a comment

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.

Review

Verified beyond the diff:

  • Checked out the PR branch and ran OpenAIEmptyUserMessageBodyPromptTest — passes.
  • Reverted just the userPrompt precedence fix locally and re-ran the same test — it fails with exactly the reported symptom (IllegalArgumentException: No input provided to LLM), confirming the test genuinely exercises the bug rather than passing vacuously. Restored the fix afterward.
  • Confirmed JIRA CAMEL-24538 is Bug, In Progress, correctly assigned to the author.
  • Checked git blame on OpenAIProducer.java — the buggy precedence has no intentional design history; it's leftover boilerplate from several unrelated feature commits, not a deliberate choice, so this fix doesn't revert anything intentional.
  • Confirmed the systemPrompt companion fix is behavior-neutral today (masked by the downstream ObjectHelper.isNotEmpty(systemPrompt) guard before the message is added) — the PR's own reasoning for touching it "for consistency" checks out.

Suggestion (non-blocking)

The identical precedence bug still exists a few lines below the systemPrompt fix, in the developerPrompt branch (outside this diff's hunk, so I can't anchor an inline suggestion to it):

// components/camel-ai/camel-openai/.../OpenAIProducer.java, buildMessages()
if (developerPrompt == null
        || developerPrompt.isEmpty() && ObjectHelper.isNotEmpty(config.getDeveloperMessage())) {
    developerPrompt = config.getDeveloperMessage();
}

should become:

if ((developerPrompt == null || developerPrompt.isEmpty()) && ObjectHelper.isNotEmpty(config.getDeveloperMessage())) {
    developerPrompt = config.getDeveloperMessage();
}

It's currently harmless (masked by the downstream ObjectHelper.isNotEmpty(developerPrompt) guard before the message is added), same as the systemPrompt case this PR already fixed "for consistency." Since two of three identical instances are being cleaned up here, it seems like an oversight to leave this third one — worth completing in the same pass, or in a fast follow-up.

Good fix, well tested, no blocking concerns.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@Croway

Croway commented Aug 31, 2026

Copy link
Copy Markdown
Contributor
There are uncommitted changes
HEAD detached at pull/25939/merge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-agent-component.adoc
	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ai-chat-component.adoc

no changes added to commit (use "git add" and/or "git commit -a")


diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-agent-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-agent-component.adoc
index a331982d11df..e70651121988 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-agent-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-agent-component.adoc
@@ -1178,7 +1178,6 @@ You can also define the `ResponseFormat` at the `ChatModel` level. See the https
 * The same schema file can be shared across `camel-openai` and `camel-langchain4j-agent` components
 ====
 
-[[structured_error_exchange_properties]]
 === Structured error exchange properties
 
 When a LangChain4j agent call fails, Camel sets structured metadata on the exchange **before** the model exception propagates. This works even when GenAI observability is disabled.
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
index 387d9e382f24..7ea8ec94431c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/langchain4j-embeddings-component.adoc
@@ -395,7 +395,6 @@ YAML::
 ----
 ====
 
-[[structured_error_exchange_properties]]
 === Structured error exchange properties
 
 When a LangChain4j embeddings call fails, Camel sets structured metadata on the exchange **before** the model exception propagates. This works even when GenAI observability is disabled.
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ai-chat-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ai-chat-component.adoc
index c8d8c0a84040..2dea4dd89a09 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ai-chat-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ai-chat-component.adoc
@@ -1407,7 +1407,6 @@ The component automatically adds Spring AI's `SimpleLoggerAdvisor` to log reques
 logging.level.org.springframework.ai.chat.client.advisor=DEBUG
 ----
 
-[[structured_error_exchange_properties]]
 === Structured error exchange properties
 
 When a Spring AI chat call fails, Camel sets structured metadata on the exchange **before** the Spring AI exception propagates. This works even when GenAI observability is disabled.

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

Labels

bug Something isn't working components components-ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants