From 10b0bd08e7bd14711578c23497c940659155ab7b Mon Sep 17 00:00:00 2001 From: tschuehly Date: Tue, 20 Oct 2026 15:39:31 +0200 Subject: [PATCH] Map example calls to Claude Haiku 5.5 and Sonnet 5.5 with explicit effort The default model and cheapest role become Claude Haiku 5.5 at low effort. The Researcher critic uses Haiku 5.5 at medium effort, and merging uses its own mergeModelName. Fact-checker extraction and consolidation use Sonnet 5.5 in both languages. Effort is set per call through Thinking budgets, because the default model path carries no hyperparameters. Needs embabel-agent#2172 and #2173. Signed-off-by: tschuehly --- .../com/embabel/example/common/Effort.kt | 36 +++++++++++++++++++ .../example/factchecker/FactChecker.java | 8 +++-- .../example/horoscope/NewsFeedService.java | 3 +- .../example/horoscope/StarNewsFinder.java | 3 +- .../example/injection/InjectedComponent.java | 5 +-- .../injection/travel/ActivitySummarizer.java | 4 ++- .../example/repeatuntil/GoatWriterConfig.java | 4 ++- .../secured/MarketIntelligenceAgent.java | 14 ++++---- .../example/secured/NewsDigestAgent.java | 8 +++-- .../src/main/resources/application.properties | 11 +++--- .../injection/InjectedComponentTest.java | 3 ++ .../travel/ActivitySummarizerTest.java | 12 ++++--- .../example/factchecker/factChecker.kt | 11 ++++-- .../example/horoscope/StarNewsFinder.kt | 3 +- .../embabel/example/researcher/Researcher.kt | 12 ++++--- .../secured/MarketIntelligenceAgent.kt | 14 ++++---- .../example/secured/NewsDigestAgent.kt | 8 +++-- .../src/main/resources/application.properties | 5 +-- 18 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 examples-common/src/main/kotlin/com/embabel/example/common/Effort.kt diff --git a/examples-common/src/main/kotlin/com/embabel/example/common/Effort.kt b/examples-common/src/main/kotlin/com/embabel/example/common/Effort.kt new file mode 100644 index 0000000..5a20d89 --- /dev/null +++ b/examples-common/src/main/kotlin/com/embabel/example/common/Effort.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024-2026 Embabel Pty Ltd. + * + * Licensed 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 com.embabel.example.common + +import com.embabel.common.ai.model.Thinking + +/** + * Reasoning effort for the examples' calls. + * + * Claude 4.7 and later models think adaptively, and Embabel maps a thinking token budget to their + * effort level: under 1024 is low, under 4096 is medium. On those models the budget is not a token cap. + * Older Claude models receive it as a literal thinking budget instead. + * + * Effort is set per call because the configured default model carries no effort of its own. + */ +object Effort { + + @JvmField + val LOW: Thinking = Thinking.withTokenBudget(512) + + @JvmField + val MEDIUM: Thinking = Thinking.withTokenBudget(1024) +} diff --git a/examples-java/src/main/java/com/embabel/example/factchecker/FactChecker.java b/examples-java/src/main/java/com/embabel/example/factchecker/FactChecker.java index 01312fe..8110597 100644 --- a/examples-java/src/main/java/com/embabel/example/factchecker/FactChecker.java +++ b/examples-java/src/main/java/com/embabel/example/factchecker/FactChecker.java @@ -30,6 +30,7 @@ import com.embabel.agent.domain.library.InternetResource; import com.embabel.common.ai.model.LlmOptions; import com.embabel.common.ai.prompt.PromptContributor; +import com.embabel.example.common.Effort; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.google.common.base.Supplier; import org.slf4j.LoggerFactory; @@ -75,12 +76,13 @@ record FactCheckerProperties( List trustedSources, List untrustedSources, List models, + List extractionModels, String deduplicationModel, int maxConcurrency ) { LlmOptions deduplicationLlm() { - return LlmOptions.withModel(deduplicationModel); + return LlmOptions.withModel(deduplicationModel).withThinking(Effort.LOW); } /** @@ -172,7 +174,7 @@ private Stream> factCheckWithSingleLlm( } private Stream> factualAssertionExtractors(UserInput userInput, ActionContext actionContext) { - return properties.models().stream() + return properties.extractionModels().stream() .map(model -> () -> extractFactualAssertionsWithModel(userInput, actionContext, model)); } @@ -181,7 +183,7 @@ private Stream> factualAssertionExtractors(U */ private DistinctFactualAssertions extractFactualAssertionsWithModel(UserInput userInput, ActionContext context, String model) { return context.ai() - .withLlm(model) + .withLlm(LlmOptions.withModel(model).withThinking(Effort.LOW)) .createObject( """ Extract distinct factual assertions from the following text. diff --git a/examples-java/src/main/java/com/embabel/example/horoscope/NewsFeedService.java b/examples-java/src/main/java/com/embabel/example/horoscope/NewsFeedService.java index 367cc3b..5f43f53 100644 --- a/examples-java/src/main/java/com/embabel/example/horoscope/NewsFeedService.java +++ b/examples-java/src/main/java/com/embabel/example/horoscope/NewsFeedService.java @@ -21,6 +21,7 @@ import com.embabel.agent.api.tool.ToolCallContext; import com.embabel.agent.spi.ToolGroupResolver; import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import tools.jackson.databind.json.JsonMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,7 +100,7 @@ public Result call(String input, ToolCallContext context) { return result; } }; - ai.withLlm(LlmOptions.withModel(defaultLlm).withTimeout(Duration.ofMinutes(3))) + ai.withLlm(LlmOptions.withModel(defaultLlm).withThinking(Effort.LOW).withTimeout(Duration.ofMinutes(3))) .withId("fetch_news_feed") .withTool(directFetch) .generateText("Call %s once with url=%s%s. Do not summarize or search." diff --git a/examples-java/src/main/java/com/embabel/example/horoscope/StarNewsFinder.java b/examples-java/src/main/java/com/embabel/example/horoscope/StarNewsFinder.java index 6813aa0..b264088 100644 --- a/examples-java/src/main/java/com/embabel/example/horoscope/StarNewsFinder.java +++ b/examples-java/src/main/java/com/embabel/example/horoscope/StarNewsFinder.java @@ -25,6 +25,7 @@ import com.embabel.agent.domain.library.PersonImpl; import com.embabel.agent.domain.library.RelevantNewsStories; import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import org.springframework.beans.factory.annotation.Value; import java.time.Duration; @@ -112,7 +113,7 @@ public RelevantNewsStories findNewsStories( .mapToObj(i -> i + ": " + stories.get(i).getTitle() + " — " + stories.get(i).getSummary()) .collect(Collectors.joining("\n")); var selection = ai - .withLlm(LlmOptions.withModel(defaultLlm).withTimeout(Duration.ofMinutes(3))) + .withLlm(LlmOptions.withModel(defaultLlm).withThinking(Effort.LOW).withTimeout(Duration.ofMinutes(3))) .withId("select_news_stories") .createObject(""" Select between 1 and %d news stories relevant to %s's horoscope. diff --git a/examples-java/src/main/java/com/embabel/example/injection/InjectedComponent.java b/examples-java/src/main/java/com/embabel/example/injection/InjectedComponent.java index aeac126..02090d8 100644 --- a/examples-java/src/main/java/com/embabel/example/injection/InjectedComponent.java +++ b/examples-java/src/main/java/com/embabel/example/injection/InjectedComponent.java @@ -17,6 +17,7 @@ import com.embabel.agent.api.common.Ai; import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import org.springframework.stereotype.Component; @@ -47,7 +48,7 @@ public record Joke(String leadup, String punchline) { public String tellJokeAbout(String topic) { return ai - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .generateText(""" Tell me a short joke. Reply with the joke only. %s @@ -57,7 +58,7 @@ public String tellJokeAbout(String topic) { public Joke createJokeObjectAbout(String topic1, String topic2, String voice) { return ai - .withLlm(LlmOptions.withDefaultLlm().withTemperature(.8)) + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject(""" Tell me a joke about the two topics below, in the voice below. The joke should have a leadup and a punchline. diff --git a/examples-java/src/main/java/com/embabel/example/injection/travel/ActivitySummarizer.java b/examples-java/src/main/java/com/embabel/example/injection/travel/ActivitySummarizer.java index 84f390a..be1990a 100644 --- a/examples-java/src/main/java/com/embabel/example/injection/travel/ActivitySummarizer.java +++ b/examples-java/src/main/java/com/embabel/example/injection/travel/ActivitySummarizer.java @@ -16,6 +16,8 @@ package com.embabel.example.injection.travel; import com.embabel.agent.api.common.Ai; +import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.lang.Nullable; @@ -64,7 +66,7 @@ public TravelerActivityReport summarizeActivity(Long customerId) { // We can rely on the record's generated toString in this // case to expose the right data, as it will include all fields var report = ai - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .withToolObject(activity) .generateText(""" Your job is to help staff of Antechinus Travel understand their customers. diff --git a/examples-java/src/main/java/com/embabel/example/repeatuntil/GoatWriterConfig.java b/examples-java/src/main/java/com/embabel/example/repeatuntil/GoatWriterConfig.java index 78fdeee..afe2794 100644 --- a/examples-java/src/main/java/com/embabel/example/repeatuntil/GoatWriterConfig.java +++ b/examples-java/src/main/java/com/embabel/example/repeatuntil/GoatWriterConfig.java @@ -19,6 +19,8 @@ import com.embabel.agent.api.common.workflow.loop.TextFeedback; import com.embabel.agent.api.models.OpenAiModels; import com.embabel.agent.core.Agent; +import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -50,7 +52,7 @@ public Agent goatWriter() { Goat.class); }) .withEvaluator(context -> - context.ai().withDefaultLlm().createObject( + context.ai().withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)).createObject( """ Evaluate the goat description for creativity and detail. The goat must have a name. diff --git a/examples-java/src/main/java/com/embabel/example/secured/MarketIntelligenceAgent.java b/examples-java/src/main/java/com/embabel/example/secured/MarketIntelligenceAgent.java index e95efa3..74dc911 100644 --- a/examples-java/src/main/java/com/embabel/example/secured/MarketIntelligenceAgent.java +++ b/examples-java/src/main/java/com/embabel/example/secured/MarketIntelligenceAgent.java @@ -23,6 +23,8 @@ import com.embabel.agent.core.CoreToolGroups; import com.embabel.agent.domain.io.UserInput; import com.embabel.agent.mcpserver.security.SecureAgentTool; +import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import java.util.stream.Collectors; @@ -66,7 +68,7 @@ public class MarketIntelligenceAgent { @Action public AnalysisSubject parseSubject(UserInput userInput, OperationContext context) { return context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Extract the subject of market analysis from the following user input. @@ -93,7 +95,7 @@ public AnalysisSubject parseSubject(UserInput userInput, OperationContext contex @Action public String gatherIntelligence(AnalysisSubject subject, OperationContext context) { return context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .withToolGroup(CoreToolGroups.WEB) .createObject( """ @@ -147,7 +149,7 @@ public MarketIntelligenceReport synthesiseReport( OperationContext context ) { SwotEntryList swotList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, produce a SWOT analysis for %s. @@ -161,7 +163,7 @@ Each entry must have a category (Strength, Weakness, Opportunity, or Threat) ); CompetitorInsightList competitorList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, identify the top 4 competitors @@ -175,7 +177,7 @@ Each entry must have a category (Strength, Weakness, Opportunity, or Threat) ); KeyTrendList trendList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, extract the 5 most significant @@ -189,7 +191,7 @@ Each entry must have a category (Strength, Weakness, Opportunity, or Threat) ); String summary = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Write a 4-sentence executive summary for a market intelligence report on diff --git a/examples-java/src/main/java/com/embabel/example/secured/NewsDigestAgent.java b/examples-java/src/main/java/com/embabel/example/secured/NewsDigestAgent.java index 14afabc..ce4b1a7 100644 --- a/examples-java/src/main/java/com/embabel/example/secured/NewsDigestAgent.java +++ b/examples-java/src/main/java/com/embabel/example/secured/NewsDigestAgent.java @@ -23,6 +23,8 @@ import com.embabel.agent.core.CoreToolGroups; import com.embabel.agent.domain.io.UserInput; import com.embabel.agent.mcpserver.security.SecureAgentTool; +import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import java.util.stream.Collectors; @@ -61,7 +63,7 @@ public class NewsDigestAgent { @Action public NewsTopic extractTopic(UserInput userInput, OperationContext context) { return context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Extract a research topic from the following user input. @@ -106,7 +108,7 @@ public NewsDigest produceDigest(NewsTopic topic, OperationContext context) { : ""; DigestItemList digestItemList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .withToolGroup(CoreToolGroups.WEB) .createObject( """ @@ -118,7 +120,7 @@ public NewsDigest produceDigest(NewsTopic topic, OperationContext context) { ); String narrative = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Write a 3-sentence editorial summary of these news items about %s. diff --git a/examples-java/src/main/resources/application.properties b/examples-java/src/main/resources/application.properties index 696c333..7be88ce 100644 --- a/examples-java/src/main/resources/application.properties +++ b/examples-java/src/main/resources/application.properties @@ -1,7 +1,8 @@ embabel.agent.application.name=embabel-examples -embabel.models.default-llm=gpt-4.1-mini -embabel.models.llms.cheapest=gpt-6-luna +# Calls on the default model and the cheapest role ask for low effort in code (see Effort) +embabel.models.default-llm=claude-haiku-5-5 +embabel.models.llms.cheapest=claude-haiku-5-5 embabel.models.llms.best=gpt-6-astra @@ -20,10 +21,12 @@ embabel.agent-platform.ranking.llm=gpt-4.1-mini embabel.fact-checker.models=gpt-4.1-mini,gpt-6-luna,gpt-6.1-sol +# Smaller models extract opinions as facts +embabel.fact-checker.extraction-models=claude-sonnet-5-5 embabel.fact-checker.max-concurrency=8 -# Use a good LLM for this -embabel.fact-checker.deduplication-model=gpt-4.1 +# Use a good LLM for this: smaller models merge an assertion with its negation +embabel.fact-checker.deduplication-model=claude-sonnet-5-5 embabel.fact-checker.reasoning-word-count=50 embabel.fact-checker.trusted-sources=Wikipedia,Wikidata,Britannica,BBC,Reuters,NY Times,ABC Australia embabel.fact-checker.untrusted-sources=reddit,4chan,twitter diff --git a/examples-java/src/test/java/com/embabel/example/injection/InjectedComponentTest.java b/examples-java/src/test/java/com/embabel/example/injection/InjectedComponentTest.java index 2d18a67..34696fc 100644 --- a/examples-java/src/test/java/com/embabel/example/injection/InjectedComponentTest.java +++ b/examples-java/src/test/java/com/embabel/example/injection/InjectedComponentTest.java @@ -16,9 +16,11 @@ package com.embabel.example.injection; import com.embabel.agent.test.unit.FakeOperationContext; +import com.embabel.example.common.Effort; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -40,6 +42,7 @@ void tellJokeAboutReturnsTheJoke() { assertEquals(terribleJoke, joke); assertTrue(promptOf(context).contains("Topic: frogs")); + assertSame(Effort.LOW, context.getLlmInvocations().getFirst().getInteraction().getLlm().getThinking()); } @Test diff --git a/examples-java/src/test/java/com/embabel/example/injection/travel/ActivitySummarizerTest.java b/examples-java/src/test/java/com/embabel/example/injection/travel/ActivitySummarizerTest.java index f96f772..455825d 100644 --- a/examples-java/src/test/java/com/embabel/example/injection/travel/ActivitySummarizerTest.java +++ b/examples-java/src/test/java/com/embabel/example/injection/travel/ActivitySummarizerTest.java @@ -18,6 +18,8 @@ import com.embabel.agent.api.common.Ai; import com.embabel.agent.api.common.PromptRunner; import com.embabel.agent.test.unit.FakeOperationContext; +import com.embabel.common.ai.model.LlmOptions; +import com.embabel.example.common.Effort; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -25,7 +27,9 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; class ActivitySummarizerTest { @@ -55,7 +59,7 @@ void testSummarizeActivityLlmThrowsException() { var travellerActivity = createSampleTravellerActivity(); when(mockReportingService.report(1L)).thenReturn(travellerActivity); - when(mockAi.withDefaultLlm()).thenReturn(mockPromptRunner); + when(mockAi.withLlm(any(LlmOptions.class))).thenReturn(mockPromptRunner); when(mockPromptRunner.withToolObject(travellerActivity)).thenReturn(mockPromptRunner); when(mockPromptRunner.generateText(anyString())).thenThrow(new RuntimeException("LLM service unavailable")); @@ -64,7 +68,7 @@ void testSummarizeActivityLlmThrowsException() { assertThrows(RuntimeException.class, () -> activitySummarizer.summarizeActivity(1L)); Mockito.verify(mockReportingService).report(1L); - Mockito.verify(mockAi).withDefaultLlm(); + Mockito.verify(mockAi).withLlm(argThat((LlmOptions llm) -> llm.getThinking() == Effort.LOW)); Mockito.verify(mockPromptRunner).withToolObject(travellerActivity); } @@ -79,7 +83,7 @@ void testSummarizeActivityValidReturn() { var expectedSummary = "John Doe is a frequent traveler who has taken 2 trips to Paris and Tokyo, spending $3000 total. As a high spender, he enjoys premium destinations."; when(mockReportingService.report(1L)).thenReturn(travellerActivity); - when(mockAi.withDefaultLlm()).thenReturn(mockPromptRunner); + when(mockAi.withLlm(any(LlmOptions.class))).thenReturn(mockPromptRunner); when(mockPromptRunner.withToolObject(travellerActivity)).thenReturn(mockPromptRunner); when(mockPromptRunner.generateText(anyString())).thenReturn(expectedSummary); @@ -90,7 +94,7 @@ void testSummarizeActivityValidReturn() { assertEquals(travellerActivity, result.activity()); Mockito.verify(mockReportingService).report(1L); - Mockito.verify(mockAi).withDefaultLlm(); + Mockito.verify(mockAi).withLlm(argThat((LlmOptions llm) -> llm.getThinking() == Effort.LOW)); Mockito.verify(mockPromptRunner).withToolObject(travellerActivity); Mockito.verify(mockPromptRunner).generateText(anyString()); } diff --git a/examples-kotlin/src/main/kotlin/com/embabel/example/factchecker/factChecker.kt b/examples-kotlin/src/main/kotlin/com/embabel/example/factchecker/factChecker.kt index 85ef24e..f697b52 100644 --- a/examples-kotlin/src/main/kotlin/com/embabel/example/factchecker/factChecker.kt +++ b/examples-kotlin/src/main/kotlin/com/embabel/example/factchecker/factChecker.kt @@ -27,6 +27,7 @@ import com.embabel.agent.domain.library.InternetResource import com.embabel.agent.domain.library.InternetResources import com.embabel.common.ai.model.LlmOptions import com.embabel.common.core.types.ZeroToOne +import com.embabel.example.common.Effort import com.fasterxml.jackson.annotation.JsonPropertyDescription import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean @@ -82,8 +83,10 @@ class FactCheckerAgentConfiguration { fun factChecker(factCheckerProperties: FactCheckerProperties): Agent { return factCheckerAgent( llms = listOf( - LlmOptions(AnthropicModels.CLAUDE_HAIKU_4_5).withTemperature(.3), - LlmOptions(AnthropicModels.CLAUDE_HAIKU_4_5).withTemperature(.0), + // Sonnet, because smaller models extract opinions as facts. + // Two effort levels give the aggregate two different readings. + LlmOptions(AnthropicModels.CLAUDE_SONNET_5_5).withThinking(Effort.LOW), + LlmOptions(AnthropicModels.CLAUDE_SONNET_5_5).withThinking(Effort.MEDIUM), ), properties = factCheckerProperties, ) @@ -121,7 +124,9 @@ fun factCheckerAgent( } }, merge = { list, context -> - context.ai().withDefaultLlm().createObject( + // Sonnet, because smaller models merge an assertion with its negation. + context.ai().withLlm(LlmOptions(AnthropicModels.CLAUDE_SONNET_5_5).withThinking(Effort.LOW)) + .createObject( """ Given the following factual assertions, merge them into a single list if any are the same. Condense into one assertion if one assertion negates another. diff --git a/examples-kotlin/src/main/kotlin/com/embabel/example/horoscope/StarNewsFinder.kt b/examples-kotlin/src/main/kotlin/com/embabel/example/horoscope/StarNewsFinder.kt index 7acd0a4..02ff063 100644 --- a/examples-kotlin/src/main/kotlin/com/embabel/example/horoscope/StarNewsFinder.kt +++ b/examples-kotlin/src/main/kotlin/com/embabel/example/horoscope/StarNewsFinder.kt @@ -30,6 +30,7 @@ import com.embabel.agent.domain.library.HasContent import com.embabel.agent.domain.library.Person import com.embabel.agent.domain.library.RelevantNewsStories import com.embabel.common.ai.model.LlmOptions +import com.embabel.example.common.Effort import com.embabel.ux.form.Text import com.fasterxml.jackson.annotation.JsonClassDescription import com.fasterxml.jackson.annotation.JsonPropertyDescription @@ -120,7 +121,7 @@ class StarNewsFinder( @Action fun extractPerson(userInput: UserInput, context: OperationContext): Person? = // All prompts are typesafe - context.ai().withDefaultLlm().createObjectIfPossible( + context.ai().withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)).createObjectIfPossible( """ Create a person from this user input, extracting their name: ${userInput.content} diff --git a/examples-kotlin/src/main/kotlin/com/embabel/example/researcher/Researcher.kt b/examples-kotlin/src/main/kotlin/com/embabel/example/researcher/Researcher.kt index 7738ac1..157d0f1 100644 --- a/examples-kotlin/src/main/kotlin/com/embabel/example/researcher/Researcher.kt +++ b/examples-kotlin/src/main/kotlin/com/embabel/example/researcher/Researcher.kt @@ -30,6 +30,7 @@ import com.embabel.common.ai.model.ModelProvider.Companion.CHEAPEST_ROLE import com.embabel.common.ai.prompt.PromptContributor import com.embabel.common.ai.prompt.PromptContributorConsumer import com.embabel.common.core.types.Timestamped +import com.embabel.example.common.Effort import com.embabel.example.common.prompt.PromptUtils import org.slf4j.LoggerFactory import org.springframework.boot.context.properties.ConfigurationProperties @@ -54,8 +55,9 @@ class ResearcherProperties( val maxWordCount: Int = 300, val claudeModelName: String = AnthropicModels.CLAUDE_HAIKU_4_5, val openAiModelName: String = OpenAiModels.GPT_41_MINI, - val criticModeName: String = OpenAiModels.GPT_41, - val mergeModelName: String = OpenAiModels.GPT_41_MINI, + // TODO use AnthropicModels.CLAUDE_HAIKU_5_5 once embabel-agent#2173 is released + val criticModeName: String = "claude-haiku-5-5", + val mergeModelName: String = OpenAiModels.GPT_41, personaName: String = "Sherlock", personaDescription: String = "A resourceful researcher agent that can perform deep web research on a topic. Nothing escapes Sherlock", personaVoice: String = "Your voice is dry and in the style of Sherlock Holmes. Occasionally you address the user as Watson", @@ -123,7 +125,7 @@ class Researcher( userInput: UserInput, context: OperationContext, ): Categorization = context.ai() - .withLlmByRole(CHEAPEST_ROLE) + .withLlm(LlmOptions.withLlmForRole(CHEAPEST_ROLE).withThinking(Effort.LOW)) .create( """ Categorize the following user input: @@ -365,7 +367,7 @@ class Researcher( userInput: UserInput, @RequireNameMatch mergedReport: ResearchReport, context: OperationContext, - ): Critique = context.ai().withLlm(properties.criticModeName) + ): Critique = context.ai().withLlm(LlmOptions(properties.criticModeName).withThinking(Effort.MEDIUM)) .create( """ Is this research report satisfactory? Consider the following question: @@ -403,7 +405,7 @@ class Researcher( claudeReport, ) return context.promptRunner( - llm = LlmOptions(properties.criticModeName), + llm = LlmOptions(properties.mergeModelName), promptContributors = properties.promptContributors, ).create( """ diff --git a/examples-kotlin/src/main/kotlin/com/embabel/example/secured/MarketIntelligenceAgent.kt b/examples-kotlin/src/main/kotlin/com/embabel/example/secured/MarketIntelligenceAgent.kt index 46bb795..8f22817 100644 --- a/examples-kotlin/src/main/kotlin/com/embabel/example/secured/MarketIntelligenceAgent.kt +++ b/examples-kotlin/src/main/kotlin/com/embabel/example/secured/MarketIntelligenceAgent.kt @@ -24,6 +24,8 @@ import com.embabel.agent.api.common.createObject import com.embabel.agent.core.CoreToolGroups import com.embabel.agent.domain.io.UserInput import com.embabel.agent.mcpserver.security.SecureAgentTool +import com.embabel.common.ai.model.LlmOptions +import com.embabel.example.common.Effort /** * Agent that produces a structured market intelligence report for a given company or sector. @@ -64,7 +66,7 @@ class MarketIntelligenceAgent { userInput: UserInput, context: OperationContext, ): AnalysisSubject = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Extract the subject of market analysis from the following user input. @@ -91,7 +93,7 @@ class MarketIntelligenceAgent { subject: AnalysisSubject, context: OperationContext, ): String = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .withToolGroup(CoreToolGroups.WEB) .createObject( """ @@ -141,7 +143,7 @@ class MarketIntelligenceAgent { context: OperationContext, ): MarketIntelligenceReport { val swotList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, produce a SWOT analysis for ${subject.subject}. @@ -154,7 +156,7 @@ class MarketIntelligenceAgent { ) val competitorList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, identify the top 4 competitors @@ -167,7 +169,7 @@ class MarketIntelligenceAgent { ) val trendList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Based on the following market intelligence, extract the 5 most significant @@ -180,7 +182,7 @@ class MarketIntelligenceAgent { ) val summary = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Write a 4-sentence executive summary for a market intelligence report on diff --git a/examples-kotlin/src/main/kotlin/com/embabel/example/secured/NewsDigestAgent.kt b/examples-kotlin/src/main/kotlin/com/embabel/example/secured/NewsDigestAgent.kt index 04d1b4e..da664b1 100644 --- a/examples-kotlin/src/main/kotlin/com/embabel/example/secured/NewsDigestAgent.kt +++ b/examples-kotlin/src/main/kotlin/com/embabel/example/secured/NewsDigestAgent.kt @@ -24,6 +24,8 @@ import com.embabel.agent.api.common.createObject import com.embabel.agent.core.CoreToolGroups import com.embabel.agent.domain.io.UserInput import com.embabel.agent.mcpserver.security.SecureAgentTool +import com.embabel.common.ai.model.LlmOptions +import com.embabel.example.common.Effort /** * Agent that researches a topic via web search and returns a curated news digest @@ -61,7 +63,7 @@ class NewsDigestAgent { userInput: UserInput, context: OperationContext, ): NewsTopic = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Extract a research topic from the following user input. @@ -105,7 +107,7 @@ class NewsDigestAgent { else "" val digestItemList = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .withToolGroup(CoreToolGroups.WEB) .createObject( """ @@ -116,7 +118,7 @@ class NewsDigestAgent { ) val narrative = context.ai() - .withDefaultLlm() + .withLlm(LlmOptions.withDefaultLlm().withThinking(Effort.LOW)) .createObject( """ Write a 3-sentence editorial summary of these news items about ${topic.topic}. diff --git a/examples-kotlin/src/main/resources/application.properties b/examples-kotlin/src/main/resources/application.properties index 316b0e6..f94a4c2 100644 --- a/examples-kotlin/src/main/resources/application.properties +++ b/examples-kotlin/src/main/resources/application.properties @@ -1,4 +1,5 @@ embabel.agent.application.name=embabel-examples -embabel.models.default-llm=gpt-4.1-mini -embabel.models.llms.cheapest=gpt-6-luna \ No newline at end of file +# Calls on the default model and the cheapest role ask for low effort in code (see Effort) +embabel.models.default-llm=claude-haiku-5-5 +embabel.models.llms.cheapest=claude-haiku-5-5 \ No newline at end of file