gnodet-bot commented on code in PR #29501: URL: https://github.com/apache/camel/pull/29501#discussion_r4515206531
########## dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ChaosExplore.java: ########## @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.camel.dsl.jbang.core.commands.ai.ChaosExplorePlaybook; +import org.apache.camel.dsl.jbang.core.commands.process.ListChaos; +import org.apache.camel.dsl.jbang.core.common.ActionClient; +import org.apache.camel.dsl.jbang.core.common.ActionClient.ActionResult; +import org.apache.camel.dsl.jbang.core.common.ChaosExperimentRun; +import org.apache.camel.dsl.jbang.core.common.CliExitCode; +import org.apache.camel.dsl.jbang.core.common.ProcessFinder; +import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess; +import org.apache.camel.dsl.jbang.core.common.RuntimeHelper; +import org.apache.camel.util.TimeUtils; +import org.apache.camel.util.json.JsonObject; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +/** + * Let there be chaos (CAMEL-26787): the AI of camel ask explores chaos on a running integration by itself, for the + * budget of the user, and reports at the end. + */ +@Command(name = "chaos-explore", + description = "Let there be chaos: an AI explores by itself how a running integration copes with faults, for a" + + " time budget, and reports what held and what broke", + sortOptions = false, showDefaultValues = true, + footer = { + "%nThis command starts a chaos exploration with the budget (camel cmd chaos-explore-start), and hands", + "it to the AI of camel ask with a playbook: read the suggestions, run one experiment per call", + "(camel cmd chaos-experiment), read the verdicts, and go on while there is time. The AI asks", + "nothing on the way: starting this command is the consent to the exploration, and camel-chaos", + "removes every rule at the deadline. The AI never changes the routes. When it is done, the", + "exploration is finished, and its report and the journal of the experiments are printed. The exit", + "code is 1 when an experiment failed.", + "%nThe integration needs camel-chaos switched on (camel run --chaos), and remote endpoints are best", + "stubbed (camel run --stub=remote), so the experiments do not reach real systems.", + "%nExamples:", + " camel cmd chaos-explore", + " camel cmd chaos-explore myApp --budget=30m --focus=refunds", + " camel cmd chaos-explore --api-type=anthropic --budget=1h" }) +public class ChaosExplore extends CamelCommand { + + @Parameters(description = "Name or pid of the running Camel integration (must match one); without it, the only one" + + " running, or the only one with chaos switched on", + arity = "0..1") + String name; + + @Option(names = { "--budget" }, defaultValue = "10m", + description = "How long the AI explores, between 10s and 2h") + String budget; + + @Option(names = { "--focus" }, + description = "What to explore, such as a route id (default: all routes)") + String focus; + + @Option(names = { "--url" }, + description = "LLM API endpoint URL, as for camel ask. Auto-detected if not specified") + String url; + + @Option(names = { "--api-type" }, + description = "API type: 'ollama', 'openai', 'anthropic', or 'watsonx'") + LlmClient.ApiType apiType; + + @Option(names = { "--api-key" }, + description = "API key. Also reads ANTHROPIC_API_KEY, OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars") + String apiKey; + + @Option(names = { "--model" }, + description = "Model to use. Default: the provider's default; with Ollama the installed model best at" + + " tool calling") + String model; + + @Option(names = { "--timeout" }, defaultValue = "300", + description = "Timeout in seconds for one answer of the LLM (an experiment can take minutes)") + int timeout = 300; + + @Option(names = { "--max-iterations" }, defaultValue = "100", + description = "Maximum number of tool-calling rounds") + int maxIterations = 100; + + @Option(names = { "--tools" }, defaultValue = "auto", + description = "The tools the model gets: core (a small set, for a local model), full, or auto") + Ask.ToolSet tools = Ask.ToolSet.auto; + + @Option(names = { "--yes" }, + description = "Let the AI send messages to endpoints the integration does not have, and run the other calls" + + " that camel ask asks for first, without asking") + boolean yes; + + @Option(names = { "--show-tools" }, + description = "Show the tool calls and results as they happen") + boolean showTools; + + @Option(names = { "--verbose" }, + description = "Print debug information of the LLM calls") + boolean verbose; + + public ChaosExplore(CamelJBangMain main) { + super(main); + } + + @Override + public Integer doCall() throws Exception { + CamelProcess process = ProcessFinder.requireOneForChaos(name); + // the exploration has the budget of the user, not one the AI picks + ActionResult started = ActionClient.execute(process, "chaos", root -> { + root.put("command", "explore-start"); + root.put("budget", budget); + if (focus != null && !focus.isBlank()) { + root.put("focus", focus); + } + }, 10000); + if (!started.isOk()) { + printer().printErr(started.error()); + return started.exitCode(); + } + printer().println("Let there be chaos: " + process.name() + " (pid " + process.pid() + "), " + + TimeUtils.printDuration(started.reply().getLongOrDefault("budget", 0)) + + (focus != null && !focus.isBlank() ? ", focus: " + focus : "")); + int answer; + try { + answer = ask(process).doCall(); + } finally { + // the AI may have finished it already; else its rules go now + ActionClient.execute(process, "chaos", root -> root.put("command", "explore-finish"), 10000); + } Review Comment: When `ask(process).doCall()` throws — LLM unreachable, timeout, bad API key — the `finally` block correctly finishes the exploration, but the journal and report printing below (lines 146–153) is skipped because the exception propagates past the `try/finally`. The command's stated behaviour is to always print the report and the journal, but on an LLM failure the user gets nothing (the exploration data is on the server and retrievable via `camel get chaos --report`, but that's a poor UX for a failure case). Adding a `catch` keeps the exception path consistent with the success path: ```suggestion int answer; try { answer = ask(process).doCall(); } catch (Exception e) { answer = 1; printer().printErr(e.getMessage()); } finally { // the AI may have finished it already; else its rules go now ActionClient.execute(process, "chaos", root -> root.put("command", "explore-finish"), 10000); } ``` _This review was generated by an AI agent, Hermès on behalf of @gnodet._ <!-- reviewer: gnodet-bot --> -- 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]
