From c0d56c765f9b2e2f1d3f2056d9a2243589d52bc9 Mon Sep 17 00:00:00 2001 From: Rod Johnson Date: Mon, 25 Aug 2025 19:26:42 +1000 Subject: [PATCH 1/3] Agent creator --- .embabel/coding-style.md | 34 +++++ pom.xml | 18 ++- .../com/embabel/{template => }/DemoShell.java | 2 +- .../com/embabel/ProjectNameApplication.java | 3 + src/main/java/com/embabel/coding/Tyrell.java | 87 +++++++++++ .../com/embabel/joke/agent/JokeAgent.java | 144 ++++++++++++++++++ src/main/resources/application.properties | 1 + src/main/resources/prompts/.gitkeep | 0 .../resources/prompts/coding/creator.jinja | 28 ++++ .../com/embabel/joke/agent/JokeAgentTest.java | 130 ++++++++++++++++ .../WriteAndReviewAgentIntegrationTest.java | 46 ------ .../agent/WriteAndReviewAgentTest.java | 4 +- 12 files changed, 441 insertions(+), 56 deletions(-) create mode 100644 .embabel/coding-style.md rename src/main/java/com/embabel/{template => }/DemoShell.java (92%) create mode 100644 src/main/java/com/embabel/coding/Tyrell.java create mode 100644 src/main/java/com/embabel/joke/agent/JokeAgent.java create mode 100644 src/main/resources/prompts/.gitkeep create mode 100644 src/main/resources/prompts/coding/creator.jinja create mode 100644 src/test/java/com/embabel/joke/agent/JokeAgentTest.java delete mode 100644 src/test/java/com/embabel/template/agent/WriteAndReviewAgentIntegrationTest.java diff --git a/.embabel/coding-style.md b/.embabel/coding-style.md new file mode 100644 index 0000000..45ae8b5 --- /dev/null +++ b/.embabel/coding-style.md @@ -0,0 +1,34 @@ +# Coding style + +## General + +The project uses Maven, Kotlin and Spring Boot. + +Follow the style of the code you read. Favor clarity. + +Don't bother putting in licence headers, as build will do that. + +Don't comment obvious things, inline or in type headers. +Comment only things that may be non-obvious. LLMs offer comment +more than humans; don't do that. + +Use consistent naming in the Spring idiom. + +Use the Spring idiom where possible. + +Favor immutability. + +Use the `Schema` and related annotations to add information to types passed over the wire in the REST application. +This will improve Swagger/OpenAPI documentation. + +Unless there is a specific reason not to, use the latest GA version of all dependencies. + +Use @Nested classes in tests. Use `test complicated thing` instead of @DisplayName for test cases. + +In log statements, use placeholders for efficiency at all logging levels. +E.g. logger.info("{} {}", a, b) instead of logger.info("computed string"). + +## Java + +- Use modern Java features like var, records, and enhanced switch expressions. +- Use multiline strings rather than concatenation. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0ddfee2..d17a992 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 21 - 0.1.1 + 0.1.2-SNAPSHOT @@ -30,11 +30,16 @@ ${embabel-agent.version} + + com.embabel.agent + embabel-agent-code + ${embabel-agent.version} + + com.embabel.agent embabel-agent-test - - 0.1.2-SNAPSHOT + ${embabel-agent.version} test @@ -61,6 +66,9 @@ embabel-releases https://repo.embabel.com/artifactory/libs-release + + true + false @@ -68,13 +76,11 @@ embabel-snapshots https://repo.embabel.com/artifactory/libs-snapshot - - false - true + diff --git a/src/main/java/com/embabel/template/DemoShell.java b/src/main/java/com/embabel/DemoShell.java similarity index 92% rename from src/main/java/com/embabel/template/DemoShell.java rename to src/main/java/com/embabel/DemoShell.java index c5976e0..23264e3 100644 --- a/src/main/java/com/embabel/template/DemoShell.java +++ b/src/main/java/com/embabel/DemoShell.java @@ -1,4 +1,4 @@ -package com.embabel.template; +package com.embabel; import com.embabel.template.injected.InjectedDemo; import org.springframework.shell.standard.ShellComponent; diff --git a/src/main/java/com/embabel/ProjectNameApplication.java b/src/main/java/com/embabel/ProjectNameApplication.java index 9f89218..3a68dfb 100644 --- a/src/main/java/com/embabel/ProjectNameApplication.java +++ b/src/main/java/com/embabel/ProjectNameApplication.java @@ -18,12 +18,15 @@ import com.embabel.agent.config.annotation.EnableAgentShell; import com.embabel.agent.config.annotation.EnableAgents; import com.embabel.agent.config.annotation.LoggingThemes; +import com.embabel.coding.Tyrell; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @EnableAgentShell +@EnableConfigurationProperties(Tyrell.Config.class) @EnableAgents(loggingTheme = LoggingThemes.STAR_WARS) class ProjectNameApplication { public static void main(String[] args) { diff --git a/src/main/java/com/embabel/coding/Tyrell.java b/src/main/java/com/embabel/coding/Tyrell.java new file mode 100644 index 0000000..c02e466 --- /dev/null +++ b/src/main/java/com/embabel/coding/Tyrell.java @@ -0,0 +1,87 @@ +package com.embabel.coding; + +import com.embabel.agent.api.annotation.*; +import com.embabel.agent.api.common.OperationContext; +import com.embabel.agent.api.common.ToolObject; +import com.embabel.agent.domain.library.code.SoftwareProject; +import com.embabel.agent.tools.common.LlmReference; +import com.embabel.coding.tools.api.ApiReference; +import com.embabel.coding.tools.git.RepositoryReferenceProvider; +import com.embabel.coding.tools.jvm.ClassGraphApiReferenceExtractor; +import com.embabel.common.ai.model.LlmOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +@Agent(description = "Agent that creates other agents") +public class Tyrell { + + private final Config config; + + private final List references = new LinkedList<>(); + + private final SoftwareProject softwareProject = new SoftwareProject(System.getProperty("user.dir")); + + public Tyrell(Config config) { + this.config = config; + var embabelApiReference = new ApiReference( + new ClassGraphApiReferenceExtractor().fromProjectClasspath( + "embabel-agent", + Set.of("com.embabel.agent"), + Set.of()), + 100); + var examplesReference = RepositoryReferenceProvider.create() + .cloneRepository("https://github.com/embabel/embabel-agent-examples.git"); + references.add(embabelApiReference); + references.add(examplesReference); + } + + + @ConfigurationProperties(prefix = "embabel.tyrell") + public record Config(LlmOptions codingLlm) { + } + + public record AgentCreationRequest(String pkg, String purpose) { + } + + /** + * The agent will have been created to fulfill this summary. + * + * @param summary + */ + public record AgentCreationResult(String summary) { + } + + @Action + AgentCreationRequest requestAgentDetails() { + return WaitFor.formSubmission( + "Please enter the package and purpose of the new agent", + AgentCreationRequest.class); + } + + @Action + @AchievesGoal( + description = "New agent has been created", + export = @Export(remote = true, startingInputTypes = {AgentCreationRequest.class})) + public AgentCreationResult createAgent(AgentCreationRequest request, OperationContext embabel) { + return embabel.ai() + .withLlm(config.codingLlm) + .withPromptElements(references) + // TODO need withToolObjects method in API + .withToolObject(new ToolObject(references.get(0)).withNamingStrategy(n -> "api_ref_" + n)) + .withToolObject(new ToolObject(references.get(1)).withNamingStrategy(n -> "examples_repo_" + n)) + .withToolObject(new ToolObject(softwareProject).withNamingStrategy(n -> "this_project_" + n)) + .withPromptElements(softwareProject) + .withTemplate("coding/creator") + .createObject( + AgentCreationResult.class, + Map.of( + "package", request.pkg(), + "purpose", request.purpose() + )); + } +} diff --git a/src/main/java/com/embabel/joke/agent/JokeAgent.java b/src/main/java/com/embabel/joke/agent/JokeAgent.java new file mode 100644 index 0000000..c4dd7ba --- /dev/null +++ b/src/main/java/com/embabel/joke/agent/JokeAgent.java @@ -0,0 +1,144 @@ +package com.embabel.joke.agent; + +import com.embabel.agent.api.annotation.AchievesGoal; +import com.embabel.agent.api.annotation.Action; +import com.embabel.agent.api.annotation.Agent; +import com.embabel.agent.api.annotation.Export; +import com.embabel.agent.api.common.OperationContext; +import com.embabel.agent.domain.io.UserInput; +import com.embabel.agent.prompt.persona.Persona; +import com.embabel.agent.prompt.persona.RoleGoalBackstory; +import com.embabel.common.ai.model.LlmOptions; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; + +import java.util.List; + +abstract class JokePersonas { + static final RoleGoalBackstory JOKE_WRITER = RoleGoalBackstory + .withRole("Professional Comedian") + .andGoal("Write funny and clever jokes that make people laugh") + .andBackstory("Spent 20 years doing stand-up comedy in clubs around the world"); + + static final Persona JOKE_CRITIC = Persona.create( + "Comedy Critic", + "Late Night Comedy Show Writer", + "Sharp, witty, and constructive", + "Evaluate jokes for humor, delivery, and audience appeal" + ); +} + +record Joke(String setup, String punchline) { +} + +record JokeIdea(String topic, String style) { +} + +record RefinedJoke( + Joke originalJoke, + Joke improvedJoke, + String critique +) { +} + +@Agent(description = "Generate jokes based on user input and refine them") +@Profile("!test") +class JokeAgent { + + private final int maxJokeLength; + + JokeAgent(@Value("${maxJokeLength:150}") int maxJokeLength) { + this.maxJokeLength = maxJokeLength; + } + + @AchievesGoal( + description = "A joke has been generated and refined based on feedback", + export = @Export(remote = true, name = "generateAndRefineJoke")) + @Action + RefinedJoke refineJoke(UserInput userInput, Joke joke, OperationContext context) { + var critique = context + .ai() + .withAutoLlm() + .withPromptContributor(JokePersonas.JOKE_CRITIC) + .generateText(String.format(""" + Evaluate this joke. Provide constructive feedback on: + - How funny it is + - Whether the punchline lands well + - If it's appropriate for the intended audience + - Suggestions for improvement + + Keep your critique brief and actionable. + + # The Joke + Setup: %s + Punchline: %s + + # Original user request + %s + """, + joke.setup(), + joke.punchline(), + userInput.getContent() + ).trim()); + + var improvedJoke = context + .ai() + .withLlm(LlmOptions.withAutoLlm().withTemperature(.8)) + .withPromptContributor(JokePersonas.JOKE_WRITER) + .createObject(String.format(""" + Based on this critique, create an improved version of the joke. + Keep the total length under %d characters. + + # Original Joke + Setup: %s + Punchline: %s + + # Critique + %s + """, + maxJokeLength, + joke.setup(), + joke.punchline(), + critique + ).trim(), Joke.class); + + return new RefinedJoke(joke, improvedJoke, critique); + } + + @Action + Joke writeJoke(JokeIdea jokeIdea, OperationContext context) { + return context.ai() + .withLlm(LlmOptions.withAutoLlm().withTemperature(.9)) + .withPromptContributor(JokePersonas.JOKE_WRITER) + .createObject(String.format(""" + Write a joke about: %s + Style: %s + + The joke should have a clear setup and punchline. + Keep the total length under %d characters. + Make it clever and funny. + """, + jokeIdea.topic(), + jokeIdea.style(), + maxJokeLength + ).trim(), Joke.class); + } + + @Action + JokeIdea brainstormJoke(UserInput userInput, OperationContext context) { + return context.ai() + .withAutoLlm() + .createObject(String.format(""" + Based on the user's input, identify: + 1. A topic for a joke (be specific) + 2. A comedy style (e.g., pun, observational, one-liner, knock-knock, etc.) + + If the user didn't specify preferences, choose something appropriate and funny. + + # User input + %s + """, + userInput.getContent() + ).trim(), JokeIdea.class); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 59bee9a..d8f5660 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,3 +9,4 @@ #embabel.models.embeddingServices.cheapest=nomic-embed-text:latest # #embabel.agent-platform.ranking.llm=llama3.1:8b +embabel.tyrell.coding-llm.model=claude-opus-4-20250514 diff --git a/src/main/resources/prompts/.gitkeep b/src/main/resources/prompts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/prompts/coding/creator.jinja b/src/main/resources/prompts/coding/creator.jinja new file mode 100644 index 0000000..f7e46ac --- /dev/null +++ b/src/main/resources/prompts/coding/creator.jinja @@ -0,0 +1,28 @@ +Create a new Embabel agent in Java within this project. +This project uses normal Maven conventions. + +The package should be {{ pgk }} + +The purpose of the agent is {{ purpose }}. + +Choose an appropriate class name. Refer to API documentation and examples for how to build an agent. + +Start by considering the data flow between the agent's @Action methods. +These types should be captured in records. + +Ue modern Java with vars and records. + +Use the build tool to check that your build works and fix it if it doesn't. + +You should also create the following artifacts: + +1. Unit test for the new agent. Use WriteAndReviewAgentTest in this repository as a guide + +{#2. Integration test for the new agent. Use#} + +DO NOT MODIFY ANY FILES BESIDES THE NEW FILES YOU'VE CREATED + +DO NOT MODIFY THE pom.xml + +Tools provided will give you access to reference repositories. +The tool names beginning with "this_project_" will give you access to this repository \ No newline at end of file diff --git a/src/test/java/com/embabel/joke/agent/JokeAgentTest.java b/src/test/java/com/embabel/joke/agent/JokeAgentTest.java new file mode 100644 index 0000000..f7258eb --- /dev/null +++ b/src/test/java/com/embabel/joke/agent/JokeAgentTest.java @@ -0,0 +1,130 @@ +package com.embabel.joke.agent; + +import com.embabel.agent.domain.io.UserInput; +import com.embabel.agent.testing.unit.FakeOperationContext; +import com.embabel.agent.testing.unit.FakePromptRunner; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.*; + +class JokeAgentTest { + + @Nested + class BrainstormJokeTests { + @Test + void testBrainstormJokeWithSpecificTopic() { + var context = FakeOperationContext.create(); + var expectedIdea = new JokeIdea("cats", "pun"); + context.expectResponse(expectedIdea); + + var agent = new JokeAgent(150); + var result = agent.brainstormJoke(new UserInput("Tell me a joke about cats", Instant.now()), context); + + assertEquals("cats", result.topic()); + assertEquals("pun", result.style()); + + var llmInvocation = context.getLlmInvocations().getFirst(); + assertTrue(llmInvocation.getPrompt().contains("cats"), "Expected prompt to contain 'cats'"); + } + + @Test + void testBrainstormJokeWithNoSpecificTopic() { + var context = FakeOperationContext.create(); + context.expectResponse(new JokeIdea("coffee", "observational")); + + var agent = new JokeAgent(150); + var result = agent.brainstormJoke(new UserInput("Tell me a funny joke", Instant.now()), context); + + assertNotNull(result.topic()); + assertNotNull(result.style()); + } + } + + @Nested + class WriteJokeTests { + @Test + void testWriteJokeFromIdea() { + var context = FakeOperationContext.create(); + var promptRunner = (FakePromptRunner) context.promptRunner(); + var expectedJoke = new Joke("Why don't cats play poker?", "Too many cheetahs!"); + context.expectResponse(expectedJoke); + + var agent = new JokeAgent(150); + var jokeIdea = new JokeIdea("cats", "pun"); + var result = agent.writeJoke(jokeIdea, context); + + assertNotNull(result.setup()); + assertNotNull(result.punchline()); + assertEquals("Why don't cats play poker?", result.setup()); + assertEquals("Too many cheetahs!", result.punchline()); + + var prompt = promptRunner.getLlmInvocations().getFirst().getPrompt(); + assertTrue(prompt.contains("cats"), "Expected prompt to contain topic"); + assertTrue(prompt.contains("pun"), "Expected prompt to contain style"); + } + } + + @Nested + class RefineJokeTests { + @Test + void testRefineJokeWithCritique() { + var agent = new JokeAgent(150); + var userInput = new UserInput("Tell me a joke about programming", Instant.now()); + var originalJoke = new Joke("Why do programmers prefer dark mode?", "Because light attracts bugs!"); + var context = FakeOperationContext.create(); + + // First expectation for critique + context.expectResponse("Good pun but could be more clever. Consider a twist on the setup."); + + // Second expectation for improved joke + var improvedJoke = new Joke("Why do programmers hate nature?", "It has too many bugs!"); + context.expectResponse(improvedJoke); + + var result = agent.refineJoke(userInput, originalJoke, context); + + assertNotNull(result.critique()); + assertNotNull(result.improvedJoke()); + assertEquals(originalJoke, result.originalJoke()); + assertTrue(result.critique().contains("pun"), "Expected critique to mention pun"); + + var llmInvocations = context.getLlmInvocations(); + assertEquals(2, llmInvocations.size()); + + // Check critique prompt + var critiquePrompt = llmInvocations.get(0).getPrompt(); + assertTrue(critiquePrompt.contains("Evaluate"), "Expected critique prompt to contain 'Evaluate'"); + assertTrue(critiquePrompt.contains(originalJoke.setup()), "Expected critique prompt to contain original setup"); + + // Check improvement prompt + var improvementPrompt = llmInvocations.get(1).getPrompt(); + assertTrue(improvementPrompt.contains("improved version"), "Expected improvement prompt to contain 'improved version'"); + } + } + + @Test + void testFullJokeGenerationFlow() { + var agent = new JokeAgent(200); + var userInput = new UserInput("I need a knock-knock joke", Instant.now()); + var context = FakeOperationContext.create(); + + // Brainstorm + context.expectResponse(new JokeIdea("doors", "knock-knock")); + var jokeIdea = agent.brainstormJoke(userInput, context); + + // Write joke + var joke = new Joke("Knock knock. Who's there? Interrupting cow.", "Interrupting cow w-- MOO!"); + context.expectResponse(joke); + var writtenJoke = agent.writeJoke(jokeIdea, context); + + // Refine joke + context.expectResponse("Classic format but timing could be better emphasized."); + context.expectResponse(new Joke("Knock knock. Who's there? Interrupting cow.", "Interrupt-- MOOOOO!")); + var refinedJoke = agent.refineJoke(userInput, writtenJoke, context); + + assertNotNull(refinedJoke.improvedJoke()); + assertEquals(4, context.getLlmInvocations().size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/embabel/template/agent/WriteAndReviewAgentIntegrationTest.java b/src/test/java/com/embabel/template/agent/WriteAndReviewAgentIntegrationTest.java deleted file mode 100644 index a3ece1e..0000000 --- a/src/test/java/com/embabel/template/agent/WriteAndReviewAgentIntegrationTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.embabel.template.agent; - -import com.embabel.agent.api.common.autonomy.AgentInvocation; -import com.embabel.agent.domain.io.UserInput; -import com.embabel.agent.testing.integration.EmbabelMockitoIntegrationTest; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.contains; - -/** - * Use framework superclass to test the complete workflow of writing and reviewing a story. - * This will run under Spring Boot against an AgentPlatform instance - * that has loaded all our agents. - */ -class WriteAndReviewAgentIntegrationTest extends EmbabelMockitoIntegrationTest { - - @Test - void shouldExecuteCompleteWorkflow() { - var input = new UserInput("Write about artificial intelligence"); - - var story = new Story("AI will transform our world..."); - var reviewedStory = new ReviewedStory(story, "Excellent exploration of AI themes.", Personas.REVIEWER); - - whenCreateObject(contains("Craft a short story"), Story.class) - .thenReturn(story); - - // The second call uses generateText - whenGenerateText(contains("You will be given a short story to review")) - .thenReturn(reviewedStory.review()); - - var invocation = AgentInvocation.create(agentPlatform, ReviewedStory.class); - var reviewedStoryResult = invocation.invoke(input); - - assertNotNull(reviewedStoryResult); - assertTrue(reviewedStoryResult.getContent().contains(story.text()), - "Expected story content to be present: " + reviewedStoryResult.getContent()); - assertEquals(reviewedStory, reviewedStoryResult, - "Expected review to match: " + reviewedStoryResult); - - verifyCreateObjectMatching(prompt -> prompt.contains("Craft a short story"), Story.class, - llm -> llm.getLlm().getTemperature() == 0.7 && llm.getToolGroups().isEmpty()); - verifyGenerateTextMatching(prompt -> prompt.contains("You will be given a short story to review")); - verifyNoMoreInteractions(); - } -} diff --git a/src/test/java/com/embabel/template/agent/WriteAndReviewAgentTest.java b/src/test/java/com/embabel/template/agent/WriteAndReviewAgentTest.java index b37485a..3b5c825 100644 --- a/src/test/java/com/embabel/template/agent/WriteAndReviewAgentTest.java +++ b/src/test/java/com/embabel/template/agent/WriteAndReviewAgentTest.java @@ -3,16 +3,14 @@ import com.embabel.agent.domain.io.UserInput; import com.embabel.agent.testing.unit.FakeOperationContext; import com.embabel.agent.testing.unit.FakePromptRunner; -import com.embabel.agent.testing.unit.UnitTestUtils; import org.junit.jupiter.api.Test; import java.time.Instant; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class WriteAndReviewAgentTest { - + @Test void testWriteAndReviewAgent() { var context = FakeOperationContext.create(); From 1c18276c0940da8c965f5d1f5940a0b24420e851 Mon Sep 17 00:00:00 2001 From: Rod Johnson Date: Mon, 25 Aug 2025 19:34:50 +1000 Subject: [PATCH 2/3] Tool group selection --- src/main/java/com/embabel/coding/Tyrell.java | 12 ++++++++++-- src/main/resources/prompts/coding/creator.jinja | 13 ++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/embabel/coding/Tyrell.java b/src/main/java/com/embabel/coding/Tyrell.java index c02e466..27955e8 100644 --- a/src/main/java/com/embabel/coding/Tyrell.java +++ b/src/main/java/com/embabel/coding/Tyrell.java @@ -4,6 +4,7 @@ import com.embabel.agent.api.common.OperationContext; import com.embabel.agent.api.common.ToolObject; import com.embabel.agent.domain.library.code.SoftwareProject; +import com.embabel.agent.spi.ToolGroupResolver; import com.embabel.agent.tools.common.LlmReference; import com.embabel.coding.tools.api.ApiReference; import com.embabel.coding.tools.git.RepositoryReferenceProvider; @@ -21,13 +22,16 @@ public class Tyrell { private final Config config; + private final ToolGroupResolver toolGroupResolver; private final List references = new LinkedList<>(); private final SoftwareProject softwareProject = new SoftwareProject(System.getProperty("user.dir")); - public Tyrell(Config config) { + public Tyrell(Config config, ToolGroupResolver toolGroupResolver) { this.config = config; + this.toolGroupResolver = toolGroupResolver; + var embabelApiReference = new ApiReference( new ClassGraphApiReferenceExtractor().fromProjectClasspath( "embabel-agent", @@ -56,6 +60,9 @@ public record AgentCreationRequest(String pkg, String purpose) { public record AgentCreationResult(String summary) { } + public record ToolGroups(List toolGroups) { + } + @Action AgentCreationRequest requestAgentDetails() { return WaitFor.formSubmission( @@ -81,7 +88,8 @@ public AgentCreationResult createAgent(AgentCreationRequest request, OperationCo AgentCreationResult.class, Map.of( "package", request.pkg(), - "purpose", request.purpose() + "purpose", request.purpose(), + "toolGroups", toolGroupResolver.availableToolGroups() )); } } diff --git a/src/main/resources/prompts/coding/creator.jinja b/src/main/resources/prompts/coding/creator.jinja index f7e46ac..3d122d4 100644 --- a/src/main/resources/prompts/coding/creator.jinja +++ b/src/main/resources/prompts/coding/creator.jinja @@ -10,6 +10,14 @@ Choose an appropriate class name. Refer to API documentation and examples for ho Start by considering the data flow between the agent's @Action methods. These types should be captured in records. +For each @Action method, consider what tool groups may be required. +Be parsimonious in your choice of tool groups, including only what's necessary. +The available tool groups are: + +{% for tool_group in tool_groups %} + - {{ tool_group.role }}: {{ tool_group.description }} +{% endfor %} + Ue modern Java with vars and records. Use the build tool to check that your build works and fix it if it doesn't. @@ -25,4 +33,7 @@ DO NOT MODIFY ANY FILES BESIDES THE NEW FILES YOU'VE CREATED DO NOT MODIFY THE pom.xml Tools provided will give you access to reference repositories. -The tool names beginning with "this_project_" will give you access to this repository \ No newline at end of file +The tool names beginning with "this_project_" will give you access to this repository + +Create the simplest possible implementation that meets the requirements. +Do not invent extra steps. \ No newline at end of file From 079e1b7c47b8425346e0f6fadfe7ff29784cb1eb Mon Sep 17 00:00:00 2001 From: Rod Johnson Date: Mon, 25 Aug 2025 19:40:45 +1000 Subject: [PATCH 3/3] Tool group selection --- pom.xml | 6 + .../coding/ReferenceConfiguration.java | 18 +++ src/main/java/com/embabel/coding/Tyrell.java | 12 +- .../com/embabel/joke/agent/JokeAgent.java | 144 ------------------ src/main/resources/application.properties | 3 +- .../resources/prompts/coding/creator.jinja | 37 +++-- 6 files changed, 53 insertions(+), 167 deletions(-) create mode 100644 src/main/java/com/embabel/coding/ReferenceConfiguration.java delete mode 100644 src/main/java/com/embabel/joke/agent/JokeAgent.java diff --git a/pom.xml b/pom.xml index d17a992..79d9f7b 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,12 @@ ${embabel-agent.version} + + com.embabel.agent + embabel-agent-rag-lucene + ${embabel-agent.version} + + com.embabel.agent embabel-agent-test diff --git a/src/main/java/com/embabel/coding/ReferenceConfiguration.java b/src/main/java/com/embabel/coding/ReferenceConfiguration.java new file mode 100644 index 0000000..f36e79c --- /dev/null +++ b/src/main/java/com/embabel/coding/ReferenceConfiguration.java @@ -0,0 +1,18 @@ +package com.embabel.coding; + +import com.embabel.agent.rag.RagService; +import com.embabel.agent.rag.lucene.LuceneRagService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ReferenceConfiguration { + + @Bean + public RagService ragService() { + return new LuceneRagService( + "code ref", + "code description" + ); + } +} diff --git a/src/main/java/com/embabel/coding/Tyrell.java b/src/main/java/com/embabel/coding/Tyrell.java index 27955e8..7d5d6da 100644 --- a/src/main/java/com/embabel/coding/Tyrell.java +++ b/src/main/java/com/embabel/coding/Tyrell.java @@ -1,11 +1,10 @@ package com.embabel.coding; import com.embabel.agent.api.annotation.*; +import com.embabel.agent.api.common.LlmReference; import com.embabel.agent.api.common.OperationContext; -import com.embabel.agent.api.common.ToolObject; import com.embabel.agent.domain.library.code.SoftwareProject; import com.embabel.agent.spi.ToolGroupResolver; -import com.embabel.agent.tools.common.LlmReference; import com.embabel.coding.tools.api.ApiReference; import com.embabel.coding.tools.git.RepositoryReferenceProvider; import com.embabel.coding.tools.jvm.ClassGraphApiReferenceExtractor; @@ -42,6 +41,7 @@ public Tyrell(Config config, ToolGroupResolver toolGroupResolver) { .cloneRepository("https://github.com/embabel/embabel-agent-examples.git"); references.add(embabelApiReference); references.add(examplesReference); + references.add(softwareProject); } @@ -77,16 +77,12 @@ AgentCreationRequest requestAgentDetails() { public AgentCreationResult createAgent(AgentCreationRequest request, OperationContext embabel) { return embabel.ai() .withLlm(config.codingLlm) - .withPromptElements(references) - // TODO need withToolObjects method in API - .withToolObject(new ToolObject(references.get(0)).withNamingStrategy(n -> "api_ref_" + n)) - .withToolObject(new ToolObject(references.get(1)).withNamingStrategy(n -> "examples_repo_" + n)) - .withToolObject(new ToolObject(softwareProject).withNamingStrategy(n -> "this_project_" + n)) - .withPromptElements(softwareProject) + .withReferences(references) .withTemplate("coding/creator") .createObject( AgentCreationResult.class, Map.of( + "thisProject", softwareProject.getName().replace("-", "_"), "package", request.pkg(), "purpose", request.purpose(), "toolGroups", toolGroupResolver.availableToolGroups() diff --git a/src/main/java/com/embabel/joke/agent/JokeAgent.java b/src/main/java/com/embabel/joke/agent/JokeAgent.java deleted file mode 100644 index c4dd7ba..0000000 --- a/src/main/java/com/embabel/joke/agent/JokeAgent.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.embabel.joke.agent; - -import com.embabel.agent.api.annotation.AchievesGoal; -import com.embabel.agent.api.annotation.Action; -import com.embabel.agent.api.annotation.Agent; -import com.embabel.agent.api.annotation.Export; -import com.embabel.agent.api.common.OperationContext; -import com.embabel.agent.domain.io.UserInput; -import com.embabel.agent.prompt.persona.Persona; -import com.embabel.agent.prompt.persona.RoleGoalBackstory; -import com.embabel.common.ai.model.LlmOptions; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; - -import java.util.List; - -abstract class JokePersonas { - static final RoleGoalBackstory JOKE_WRITER = RoleGoalBackstory - .withRole("Professional Comedian") - .andGoal("Write funny and clever jokes that make people laugh") - .andBackstory("Spent 20 years doing stand-up comedy in clubs around the world"); - - static final Persona JOKE_CRITIC = Persona.create( - "Comedy Critic", - "Late Night Comedy Show Writer", - "Sharp, witty, and constructive", - "Evaluate jokes for humor, delivery, and audience appeal" - ); -} - -record Joke(String setup, String punchline) { -} - -record JokeIdea(String topic, String style) { -} - -record RefinedJoke( - Joke originalJoke, - Joke improvedJoke, - String critique -) { -} - -@Agent(description = "Generate jokes based on user input and refine them") -@Profile("!test") -class JokeAgent { - - private final int maxJokeLength; - - JokeAgent(@Value("${maxJokeLength:150}") int maxJokeLength) { - this.maxJokeLength = maxJokeLength; - } - - @AchievesGoal( - description = "A joke has been generated and refined based on feedback", - export = @Export(remote = true, name = "generateAndRefineJoke")) - @Action - RefinedJoke refineJoke(UserInput userInput, Joke joke, OperationContext context) { - var critique = context - .ai() - .withAutoLlm() - .withPromptContributor(JokePersonas.JOKE_CRITIC) - .generateText(String.format(""" - Evaluate this joke. Provide constructive feedback on: - - How funny it is - - Whether the punchline lands well - - If it's appropriate for the intended audience - - Suggestions for improvement - - Keep your critique brief and actionable. - - # The Joke - Setup: %s - Punchline: %s - - # Original user request - %s - """, - joke.setup(), - joke.punchline(), - userInput.getContent() - ).trim()); - - var improvedJoke = context - .ai() - .withLlm(LlmOptions.withAutoLlm().withTemperature(.8)) - .withPromptContributor(JokePersonas.JOKE_WRITER) - .createObject(String.format(""" - Based on this critique, create an improved version of the joke. - Keep the total length under %d characters. - - # Original Joke - Setup: %s - Punchline: %s - - # Critique - %s - """, - maxJokeLength, - joke.setup(), - joke.punchline(), - critique - ).trim(), Joke.class); - - return new RefinedJoke(joke, improvedJoke, critique); - } - - @Action - Joke writeJoke(JokeIdea jokeIdea, OperationContext context) { - return context.ai() - .withLlm(LlmOptions.withAutoLlm().withTemperature(.9)) - .withPromptContributor(JokePersonas.JOKE_WRITER) - .createObject(String.format(""" - Write a joke about: %s - Style: %s - - The joke should have a clear setup and punchline. - Keep the total length under %d characters. - Make it clever and funny. - """, - jokeIdea.topic(), - jokeIdea.style(), - maxJokeLength - ).trim(), Joke.class); - } - - @Action - JokeIdea brainstormJoke(UserInput userInput, OperationContext context) { - return context.ai() - .withAutoLlm() - .createObject(String.format(""" - Based on the user's input, identify: - 1. A topic for a joke (be specific) - 2. A comedy style (e.g., pun, observational, one-liner, knock-knock, etc.) - - If the user didn't specify preferences, choose something appropriate and funny. - - # User input - %s - """, - userInput.getContent() - ).trim(), JokeIdea.class); - } -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d8f5660..80e7e60 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,5 @@ #embabel.models.embeddingServices.cheapest=nomic-embed-text:latest # #embabel.agent-platform.ranking.llm=llama3.1:8b -embabel.tyrell.coding-llm.model=claude-opus-4-20250514 +embabel.tyrell.coding-llm.model=claude-3-7-sonnet-latest +#embabel.tyrell.coding-llm.model=claude-3-5-haiku-latest diff --git a/src/main/resources/prompts/coding/creator.jinja b/src/main/resources/prompts/coding/creator.jinja index 3d122d4..67e5fed 100644 --- a/src/main/resources/prompts/coding/creator.jinja +++ b/src/main/resources/prompts/coding/creator.jinja @@ -1,30 +1,28 @@ Create a new Embabel agent in Java within this project. This project uses normal Maven conventions. -The package should be {{ pgk }} - -The purpose of the agent is {{ purpose }}. - -Choose an appropriate class name. Refer to API documentation and examples for how to build an agent. +Choose an appropriate class name within the package. +Refer to API documentation and examples for how to build an agent. Start by considering the data flow between the agent's @Action methods. These types should be captured in records. For each @Action method, consider what tool groups may be required. Be parsimonious in your choice of tool groups, including only what's necessary. -The available tool groups are: - -{% for tool_group in tool_groups %} - - {{ tool_group.role }}: {{ tool_group.description }} +The available tool groups are the following (use the role string in code for withToolGroup): +Role | Description +---- | ----------- +{% for group in toolGroups %} + {{ group.role }} | {{ group.description }} {% endfor %} Ue modern Java with vars and records. Use the build tool to check that your build works and fix it if it doesn't. -You should also create the following artifacts: - -1. Unit test for the new agent. Use WriteAndReviewAgentTest in this repository as a guide +{#You should also create the following artifacts:#} +{##} +{#1. Unit test for the new agent. Use WriteAndReviewAgentTest in this repository as a guide#} {#2. Integration test for the new agent. Use#} @@ -33,7 +31,18 @@ DO NOT MODIFY ANY FILES BESIDES THE NEW FILES YOU'VE CREATED DO NOT MODIFY THE pom.xml Tools provided will give you access to reference repositories. -The tool names beginning with "this_project_" will give you access to this repository +The tool names beginning with "{{ thisProject }}" will give you access to this repository + +This project uses normal Maven project structure, you will add source code under src/main/java and tests under src/test/java. +Note that the examples repo has a different structure. Create the simplest possible implementation that meets the requirements. -Do not invent extra steps. \ No newline at end of file +Do not invent extra steps or actions. + +# PACKAGE + +The package should be {{ package }} + +# PURPOSE OF THE AGENT + +{{ purpose }} \ No newline at end of file