Skip to content

CAMEL-24527: camel-huggingface - apply the configured token to every task, not only chat - #25899

Merged
davsclaus merged 3 commits into
apache:mainfrom
oscerd:fix/CAMEL-24527
Aug 31, 2026
Merged

CAMEL-24527: camel-huggingface - apply the configured token to every task, not only chat#25899
davsclaus merged 3 commits into
apache:mainfrom
oscerd:fix/CAMEL-24527

Conversation

@oscerd

@oscerd oscerd commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Issue

CAMEL-24527

Problem

The authToken option (and the token resolved from oauthProfile) was honoured by only the chat
task. ChatPredictor.getPythonScript() builds a token='...' clause and passes it to
transformers.pipeline(); none of the other nine task predictors do. As a result, text generation,
summarization, question answering, classification, sentence embeddings, ASR, TTS, text-to-image and
zero-shot classification all failed with HTTP 401 when loading a gated or private model, even with a
token configured.

Fix

Apply the token centrally in AbstractTaskPredictor.loadModel() by exporting it as the standard
HF_TOKEN environment variable at the top of the generated handler script:

import os
os.environ['HF_TOKEN'] = '<token>'

transformers / huggingface_hub read HF_TOKEN from the environment when no explicit token is
passed, so this authenticates every task in a single place instead of threading a token clause
through nine predictors and nine Python templates. The token is read from config.getAuthToken(),
which holds either the authToken option or the value resolved from an OAuth profile
(HuggingFaceProducer.resolveOAuthToken). When no token is configured, the script is unchanged.

Testing

  • New AuthTokenInjectionTest verifies the token is exported as HF_TOKEN when configured and that
    the script is left untouched when it is not.
  • mvn -Psourcecheck validate green.

Claude Code on behalf of oscerd

…task, not only chat

Only ChatPredictor passed the configured token to its Python script (as a token=
kwarg), so every other task (text generation, summarization, question answering,
classification, embeddings, ASR, TTS, text-to-image, zero-shot) failed with HTTP
401 when loading a gated or private model even though authToken / oauthProfile was
configured.

Apply the token centrally in AbstractTaskPredictor.loadModel by exporting it as the
standard HF_TOKEN environment variable at the top of the generated handler.
transformers.pipeline() reads HF_TOKEN when no explicit token is passed, so this
covers all tasks in one place. The token is taken from config.getAuthToken(), which
holds either the authToken option or the value resolved from an OAuth profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd oscerd added this to the 4.23.0 milestone Aug 30, 2026
@oscerd oscerd added the bug Something isn't working label Aug 30, 2026
@oscerd
oscerd requested review from Croway and davsclaus August 30, 2026 10:07
@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.

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-ai/camel-huggingface

🔬 Scalpel shadow comparison — Scalpel: 9 tested, 27 compile-only — current: 9 all tested

Maveniverse Scalpel detected 36 affected modules (current approach: 9).

⚠️ Modules only in Scalpel (27)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 9 modules (1 direct + 8 downstream), skip tests for 27 (generated code, meta-modules)

Modules Scalpel would test (9)
  • camel-huggingface
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-launcher-container
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (27)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (36 modules)
  • Camel :: AI :: Hugging Face
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet gnodet 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.

Good approach — setting HF_TOKEN as an environment variable is the idiomatic way to authenticate with the Hugging Face ecosystem and avoids threading a token parameter through each Python script template individually.

Two observations after review:

1. Redundant token handling in ChatPredictor (medium)

After this PR, the chat task gets the token applied twice: once via the new central HF_TOKEN env var injection in withAuthToken(), and again via the explicit token='...' kwarg that ChatPredictor.getPythonScript() still builds at line 113. Since the PR's goal is to centralize token handling, the old per-task tokenClause in ChatPredictor is now dead code and should be removed (along with the corresponding %s placeholder in chat.py). The redundancy is not a runtime bug (token= takes precedence), but it contradicts the PR's design intent.

2. Missing empty-string test case (low)

The withAuthToken() method handles both null and "", but only null is tested in noAuthTokenLeavesTheScriptUnchanged(). Adding a second assertion for predictorWithToken("") would complete the coverage.

🔀 Backport Status

⚠️ This bug fix targets main but the same gap exists on camel-4.22.x:

  • camel-4.22.x — no backport PR found

Consider creating a backport PR or adding the backport-to-4.22.x label.

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

Claude Code on behalf of Guillaume Nodet

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

Reviewed against this project's rules and conventions (this does not replace CodeRabbit/Sourcery or SonarCloud-style static analysis).

Good fix overall — centralizing token injection in AbstractTaskPredictor is the right approach, and it comes with a new test (AuthTokenInjectionTest) covering the token-present/absent cases. I independently built and ran the module's tests on this branch and they pass.

Two things to address before merge:

  1. Inline comment below on the unescaped token interpolation.
  2. There's already an unresolved review thread from @gnodet on AbstractTaskPredictor.java pointing out that ChatPredictor's explicit token='...' clause (line 113) is now redundant with the centralized HF_TOKEN env var — chat would pass the token twice. Since this PR's stated intent is to centralize token handling, that cleanup (removing tokenClause from ChatPredictor and the %s placeholder from chat.py) seems worth doing here, or at least replying to close out the thread.

Minor, non-blocking: LOG.debug("Generated Python script for task {}:\n{}", ...) in loadModel() now logs the token-bearing script for all 10 tasks instead of just chat. Low severity since it's DEBUG-only, but worth considering redaction given this project's guidance on secrets in logs.

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

…dant chat token clause

- Escape single quotes when interpolating the token into the generated handler so a
  token (or OAuth-resolved value) containing a quote cannot break out of / inject into
  the Python string literal (raised by @davsclaus).
- Remove the per-task token clause from ChatPredictor.getPythonScript and the trailing
  placeholder from chat.py: the token is now applied centrally as HF_TOKEN, so the chat
  task no longer needs to pass it twice (raised by @gnodet).
- Extend AuthTokenInjectionTest with the empty-token case and a single-quote escape case,
  and add ChatScriptFormatTest to guard chat.py's format-argument alignment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd

oscerd commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @davsclaus and @gnodet — addressed in the latest commit:

  1. Token escaping (davsclaus): the token is now interpolated as authToken.replace("'", "\\'"), so a single quote in the token (or an OAuth-resolved value) can no longer break out of / inject into the Python string literal. Since this is the shared path for all 10 tasks, it's fixed in withAuthToken.
  2. Redundant chat token clause (gnodet): removed the tokenClause from ChatPredictor.getPythonScript() and the trailing placeholder from chat.py. The token now flows only through the central HF_TOKEN env var, so chat no longer passes it twice.
  3. Tests: added the empty-token case and a single-quote escape case to AuthTokenInjectionTest, plus a new ChatScriptFormatTest guarding chat.py's format-argument alignment after the placeholder was removed.

On the backport note: I'll open a camel-4.22.x backport once this merges (the camel-huggingface module doesn't exist on camel-4.18.x).

Claude Code on behalf of oscerd

@oscerd
oscerd requested review from davsclaus and gnodet August 31, 2026 08:01
@Croway

Croway commented Aug 31, 2026

Copy link
Copy Markdown
Contributor
There are uncommitted changes
HEAD detached at pull/25899/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.

Raised by Claus Ibsen on the PR. loadModel() logged the generated handler after
withAuthToken() had prepended os.environ['HF_TOKEN'] = '<token>' to it, so the
configured token reached the log at DEBUG level. That line previously only ran
for chat; centralising the token made it apply to all ten tasks.

Log the script before the token is prepended, and write the token-bearing version
to the handler file. The log keeps its diagnostic value: the token is a single
prepended line, not part of the script being debugged.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@oscerd

oscerd commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — the DEBUG note was worth acting on rather than filing away, so I have taken it too.

You are right about the change in blast radius: that line only ran for chat before, and centralising the token made it apply to all ten tasks, with the token now sitting in the script it prints. Rather than redact the string, loadModel() now logs the script before withAuthToken() prepends the token, and writes the token-bearing version to the handler file:

String pythonScript = getPythonScript();
// logged before the token is prepended: withAuthToken writes the configured token into the
// script, and this now runs for every task rather than only chat
if (LOG.isDebugEnabled()) {
    LOG.debug("Generated Python script for task {}:\n{}", config.getTask(), pythonScript);
}
Files.writeString(handlerPath, withAuthToken(pythonScript));

The log keeps its diagnostic value — the token is one prepended line, not part of the script anyone is debugging — and there is no redaction to get wrong later.

The other two points from this round were already handled in the previous commit: the token is escaped for single quotes, and ChatPredictor's tokenClause plus the %s placeholder in chat.py are gone, with a test asserting no token= survives in the generated script. @gnodet's empty-string coverage is in as well.

Module tests pass, and a full reactor build from the root is green with nothing left uncommitted.

Claude Code on behalf of oscerd

@gnodet gnodet 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.

Clean, well-structured bug fix that centralizes auth-token injection in AbstractTaskPredictor so all ten task predictors can load gated/private models, not only chat. The environment-variable approach (HF_TOKEN) is the standard mechanism used by the transformers library.

Good details:

  • The debug log was intentionally moved before withAuthToken() to avoid logging the secret token — nice security hygiene.
  • The format-placeholder realignment in chat.py is verified correct: one %s placeholder and one argument removed, leaving 6 matched pairs.
  • No other Python task scripts had a token clause, so they gain token support purely through the centralized withAuthToken in loadModel().

No backport needed — camel-huggingface was introduced in 4.21.0 and does not exist on camel-4.18.x.

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

Claude Code on behalf of Guillaume Nodet

@davsclaus
davsclaus merged commit e65d869 into apache:main Aug 31, 2026
5 checks passed
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.

4 participants