oscerd commented on code in PR #29066:
URL: https://github.com/apache/camel/pull/29066#discussion_r4454321758


##########
components/camel-ai/camel-huggingface/src/main/java/org/apache/camel/component/huggingface/tasks/ChatPredictor.java:
##########
@@ -203,10 +208,35 @@ protected void processOutput(Exchange exchange, Output 
output) throws Exception
         if (node.isArray() && !node.isEmpty()) {
             JsonNode first = node.get(0);
             if (first.has("generated_text") && 
first.get("generated_text").isArray()) {
-                String fullHistoryJson = 
first.get("generated_text").toString();
+                String fullHistoryJson = 
trimHistory(first.get("generated_text")).toString();
                 chatHistories.put(memoryId, fullHistoryJson);
                 LOG.debug("Updated chat history for ID: {}", memoryId);
             }
         }
     }
+
+    /**
+     * Keeps the system message and at most maxHistoryMessages of the other 
messages. The oldest messages are dropped
+     * first, and the kept messages start with a user message, as chat 
templates that alternate the roles expect.
+     */
+    private JsonNode trimHistory(JsonNode history) {
+        int maxMessages = config.getMaxHistoryMessages();
+        int systemMessages = !history.isEmpty() && 
"system".equals(history.get(0).path("role").asText()) ? 1 : 0;
+        if (maxMessages <= 0 || history.size() - systemMessages <= 
maxMessages) {
+            return history;
+        }
+        int from = history.size() - maxMessages;
+        while (from < history.size() && 
!config.getUserRole().equals(history.get(from).path("role").asText())) {

Review Comment:
   Good catch, thanks. A value of 1 can never keep a reply, because the kept 
messages have to start with a user message. The chat predictor now rejects a 
`maxHistoryMessages` of 1 or below 0 when it is created, so the route fails at 
start with "The maxHistoryMessages option must be 0 (no limit) or at least 2, 
but was 1". I kept the option in messages rather than turns, as that is what 
the history holds and what `maxHistoryMessages` of camel-openai counts, and the 
description says that an odd value keeps one message less. `ChatHistoryTest` 
now checks that 1 and -1 are rejected and that 2 keeps the last turn.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



##########
components/camel-ai/camel-huggingface/src/main/java/org/apache/camel/component/huggingface/HuggingFaceConfiguration.java:
##########
@@ -81,6 +81,18 @@ public class HuggingFaceConfiguration implements Cloneable {
     @UriParam(defaultValue = "CamelChatMemoryId", description = "Header name 
for conversation memory ID (for multi-user chats)")
     private String memoryIdHeader = "CamelChatMemoryId";
 
+    @UriParam(defaultValue = "1000",
+              description = "The maximum number of chat histories, one per 
memory ID, that the chat task keeps in memory."
+                            + " When there are more, the least recently used 
history is dropped. Zero or a negative value"
+                            + " keeps them all")
+    private int maxChatHistories = 1000;
+
+    @UriParam(defaultValue = "0",
+              description = "The maximum number of messages that the chat task 
keeps in a chat history, besides the system"
+                            + " prompt. The oldest messages are dropped first, 
and the kept messages start with a user"
+                            + " message. Zero or a negative value keeps them 
all")
+    private int maxHistoryMessages;

Review Comment:
   Agreed, the issue asks to bound the length of each history too. 
`maxHistoryMessages` now defaults to 50 messages, besides the system prompt, 
and `0` keeps every message as before. The upgrade guide says that the default 
is now 50 and that `0` gives the old behaviour, and the component page points 
long-running conversations to `maxHistoryMessages`. `ChatHistoryTest` checks 
that with the default the 27th turn on one memory ID sends 51 messages, 
starting from the second turn, and that `0` sends all 53.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



##########
components/camel-ai/camel-huggingface/src/main/java/org/apache/camel/component/huggingface/tasks/TextToSpeechPredictor.java:
##########
@@ -132,8 +128,8 @@ protected void processOutput(Exchange exchange, Output 
output) throws Exception
         // Reconstruct DJL Audio (mono, 1 channel)
         Audio audio = new Audio(audioData, samplingRate, 1);
 
+        // the body is the DJL Audio, not encoded audio bytes, so no 
Content-Type is set
         exchange.getMessage().setBody(audio);

Review Comment:
   Right, thanks. The predictor now removes `Exchange.CONTENT_TYPE` from the 
message, so a `text/plain` request no longer leaves its content type on the 
`Audio` result. The test now sends a request with `Content-Type: text/plain` 
and checks that the result has no `Content-Type`. The component page and the 
upgrade guide say that the header is removed.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



##########
components/camel-ai/camel-huggingface/src/main/java/org/apache/camel/component/huggingface/tasks/AbstractTaskPredictor.java:
##########
@@ -169,6 +177,34 @@ public void predict(Exchange exchange) throws Exception {
         }
     }
 
+    /**
+     * Fails when the output of the Python handler is its error payload: a 
JSON object with the single field
+     * {@code error}. Any other output is a result, also a text or a JSON that 
has the word error in it.
+     */
+    protected static void failOnPythonError(String output) {
+        String text = output != null ? output.strip() : "";

Review Comment:
   Thanks, that turned a failure into a silent success. `failOnPythonError` now 
throws `RuntimeCamelException` with "Python inference returned no output" for a 
null output, so text-generation, summarization, question-answering and 
zero-shot fail again, with a clear message. text-to-image fails the same way 
now; it used to fail with a `NullPointerException`. New tests run each of them 
with an `Output` that has no `data` entry. The JSON tasks already fail on a 
missing output, as Jackson rejects null content.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



##########
components/camel-ai/camel-huggingface/src/main/java/org/apache/camel/component/huggingface/tasks/ChatPredictor.java:
##########
@@ -100,10 +103,15 @@ public class ChatPredictor extends AbstractTaskPredictor {
     private final ObjectMapper objectMapper = new ObjectMapper();
 
     // In-memory chat history (key = memoryId, value = JSON string of list of 
dicts)
-    private final Map<String, String> chatHistories = new 
ConcurrentHashMap<>();
+    private final Map<String, String> chatHistories;
 
     public ChatPredictor(HuggingFaceEndpoint endpoint) {
         super(endpoint);
+        // keeps the most recently used histories only, so the map does not 
grow without bound
+        int maxChatHistories = config.getMaxChatHistories();
+        this.chatHistories = maxChatHistories > 0
+                ? LRUCacheFactory.newLRUCache(16, maxChatHistories, false)

Review Comment:
   Good point: with the default `SimpleLRUCache` only a write moves an entry, 
so the history that was updated least recently goes first. As every successful 
turn writes its history, the two are close, so I kept the cache as it is and 
reworded the option description, the component page and the upgrade guide to 
say that the history updated least recently is evicted first.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



##########
components/camel-ai/camel-huggingface/src/main/docs/huggingface-component.adoc:
##########
@@ -111,6 +111,13 @@ The component supports the following tasks:
 | custom | TBD | TBD | predictorBean (required), revision, device | Any HF 
model compatible with the custom predictor bean (e.g., 
Helsinki-NLP/opus-mt-en-fr for translation, distilgpt2 for custom generation)
 |===
 
+A task fails with a `RuntimeCamelException` when its Python handler reports an 
error, which the handler does by

Review Comment:
   Thanks. The page now says that a custom predictor (`predictorBean`) fails on 
the error payload only when its `processOutput` calls `failOnPythonError`, and 
the custom task example shows that call. The chat row of the task table now 
lists `maxChatHistories` and `maxHistoryMessages`.
   
   _Claude Code on behalf of Andrea Cosentino (oscerd)_



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to