diff --git a/README.md b/README.md index 7bd307f..0d33aaa 100644 --- a/README.md +++ b/README.md @@ -82,65 +82,6 @@ var request = var response = openAIClient.chat().completions().create(request); ``` -### LangChain4j Instrumentation - -```java -var braintrust = Braintrust.get(); -var openTelemetry = braintrust.openTelemetryCreate(); - -// Wrap the chat model to trace LLM calls -ChatModel model = BraintrustLangchain.wrap( - openTelemetry, - OpenAiChatModel.builder() - .apiKey(System.getenv("OPENAI_API_KEY")) - .modelName("gpt-4o-mini") - .temperature(0.0)); - -var response = model.chat("What is the capital of France?"); -``` - -#### Tool Wrapping - -Use `BraintrustLangchain.wrapTools()` to automatically trace tool executions in your LangChain4j agents: - -```java -// Create your tool class -public class WeatherTools { - @Tool("Get current weather for a location") - public String getWeather(String location) { - return "The weather in " + location + " is sunny."; - } -} - -// Wrap tools to create spans for each tool execution -WeatherTools tools = new WeatherTools(); -WeatherTools instrumentedTools = BraintrustLangchain.wrapTools(openTelemetry, tools); - -// Use instrumented tools in your AI service -Assistant assistant = AiServices.builder(Assistant.class) - .chatModel(model) - .tools(instrumentedTools) - .build(); -``` - -Each tool call will automatically create an OpenTelemetry span in Braintrust with: -- Tool name and parameters -- Execution duration -- Return values -- Any exceptions thrown - -**Note:** For proper display in the Braintrust UI, ensure parent spans (conversation, turn, etc.) also set the required Braintrust attributes: -```java -var span = tracer.spanBuilder("my-span").startSpan(); -span.setAttribute("braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"my-span\"}"); -span.setAttribute("braintrust.input_json", "{\"user_message\":\"...\"}"); -// ... do work ... -span.setAttribute("braintrust.output_json", "{\"result\":\"...\"}"); -span.end(); -``` - -See [LangchainToolWrappingExample.java](./examples/src/main/java/dev/braintrust/examples/LangchainToolWrappingExample.java) for a complete example with proper span hierarchy. - ## Running Examples Example source code can be found [here](./examples/src/main/java/dev/braintrust/examples) diff --git a/build.gradle b/build.gradle index 90d9c9f..ad1b078 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,6 @@ dependencies { testImplementation "dev.langchain4j:langchain4j:${langchainVersion}" testImplementation "dev.langchain4j:langchain4j-http-client:${langchainVersion}" testImplementation "dev.langchain4j:langchain4j-open-ai:${langchainVersion}" - - implementation 'net.bytebuddy:byte-buddy:1.14.11' // ByteBuddy for LangChain4j tool wrapping } /** diff --git a/examples/build.gradle b/examples/build.gradle index 553af25..f4e9c20 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -172,17 +172,3 @@ task runLangchain(type: JavaExec) { suspend = false } } - -task runLangchainToolWrapping(type: JavaExec) { - group = 'Braintrust SDK Examples' - description = 'Run the LangChain4j tool wrapping example. NOTE: this requires OPENAI_API_KEY to be exported and will make a small call to openai, using your tokens' - classpath = sourceSets.main.runtimeClasspath - mainClass = 'dev.braintrust.examples.LangchainToolWrappingExample' - systemProperty 'org.slf4j.simpleLogger.log.dev.braintrust', braintrustLogLevel - debugOptions { - enabled = true - port = 5566 - server = true - suspend = false - } -} diff --git a/examples/src/main/java/dev/braintrust/examples/LangchainToolWrappingExample.java b/examples/src/main/java/dev/braintrust/examples/LangchainToolWrappingExample.java deleted file mode 100644 index 102880d..0000000 --- a/examples/src/main/java/dev/braintrust/examples/LangchainToolWrappingExample.java +++ /dev/null @@ -1,158 +0,0 @@ -package dev.braintrust.examples; - -import dev.braintrust.Braintrust; -import dev.braintrust.instrumentation.langchain.BraintrustLangchain; -import dev.langchain4j.agent.tool.Tool; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; -import dev.langchain4j.service.AiServices; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.OpenTelemetrySdk; - -/** - * Demonstrates how to use BraintrustLangchain.wrapTools() to automatically trace tool executions. - * - *

This example shows: - * - *

- */ -public class LangchainToolWrappingExample { - - /** Example tool class with weather-related methods */ - public static class WeatherTools { - @Tool("Get current weather for a location") - public String getWeather(String location) { - // Simulate a weather API call - return String.format("The weather in %s is sunny with 72°F temperature.", location); - } - - @Tool("Get weather forecast for next N days") - public String getForecast(String location, int days) { - // Simulate a forecast API call - return String.format( - "The %d-day forecast for %s: Mostly sunny with temperatures between 65-75°F.", - days, location); - } - } - - /** AI Service interface for the assistant */ - interface Assistant { - String chat(String userMessage); - } - - public static void main(String[] args) throws Exception { - if (null == System.getenv("OPENAI_API_KEY")) { - System.err.println( - "\nWARNING envar OPENAI_API_KEY not found. This example will likely fail.\n"); - } - - var braintrust = Braintrust.get(); - var openTelemetry = braintrust.openTelemetryCreate(); - var tracer = openTelemetry.getTracer("langchain-tool-wrapping"); - - System.out.println("\n=== LangChain4j Tool Wrapping Example ===\n"); - - // Create root span for the conversation - var conversationSpan = tracer.spanBuilder("weather-assistant-conversation").startSpan(); - conversationSpan.setAttribute( - "braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"conversation\"}"); - conversationSpan.setAttribute( - "braintrust.input_json", - "{\"description\":\"Weather assistant with tool wrapping\",\"turns\":2}"); - - try (var ignored = conversationSpan.makeCurrent()) { - // Wrap the LLM with Braintrust instrumentation - ChatModel model = - BraintrustLangchain.wrap( - openTelemetry, - OpenAiChatModel.builder() - .apiKey(System.getenv("OPENAI_API_KEY")) - .modelName("gpt-4o-mini") - .temperature(0.0)); - - // Create tools and wrap them with Braintrust instrumentation - WeatherTools tools = new WeatherTools(); - WeatherTools instrumentedTools = BraintrustLangchain.wrapTools(openTelemetry, tools); - - System.out.println("Tools wrapped with Braintrust instrumentation"); - System.out.println("Each tool call will create a span in Braintrust\n"); - - // Create AI service with the instrumented tools - Assistant assistant = - AiServices.builder(Assistant.class) - .chatModel(model) - .tools(instrumentedTools) - .build(); - - // Example 1: Single tool call - System.out.println("--- Turn 1: Single Tool Call ---"); - String query1 = "What's the weather in San Francisco?"; - System.out.println("User: " + query1); - Span turn1 = tracer.spanBuilder("turn_1").startSpan(); - turn1.setAttribute( - "braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"turn_1\"}"); - turn1.setAttribute("braintrust.input_json", "{\"user_message\":\"" + query1 + "\"}"); - String response1; - try (Scope scope = turn1.makeCurrent()) { - response1 = assistant.chat(query1); - System.out.println("Assistant: " + response1); - turn1.setAttribute( - "braintrust.output_json", - "{\"assistant_message\":\"" + response1.replace("\"", "\\\"") + "\"}"); - } finally { - turn1.end(); - } - System.out.println(); - - // Example 2: Tool with multiple parameters - System.out.println("--- Turn 2: Multiple Parameters ---"); - String query2 = "What's the 5-day forecast for Tokyo?"; - System.out.println("User: " + query2); - Span turn2 = tracer.spanBuilder("turn_2").startSpan(); - turn2.setAttribute( - "braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"turn_2\"}"); - turn2.setAttribute("braintrust.input_json", "{\"user_message\":\"" + query2 + "\"}"); - String response2; - try (Scope scope = turn2.makeCurrent()) { - response2 = assistant.chat(query2); - System.out.println("Assistant: " + response2); - turn2.setAttribute( - "braintrust.output_json", - "{\"assistant_message\":\"" + response2.replace("\"", "\\\"") + "\"}"); - } finally { - turn2.end(); - } - System.out.println(); - - } finally { - conversationSpan.setAttribute( - "braintrust.output_json", "{\"status\":\"completed\",\"turns\":2}"); - conversationSpan.end(); - } - - // Flush traces before exit - if (openTelemetry instanceof OpenTelemetrySdk) { - ((OpenTelemetrySdk) openTelemetry).close(); - } - - var url = - braintrust.projectUri() - + "/logs?r=%s&s=%s" - .formatted( - conversationSpan.getSpanContext().getTraceId(), - conversationSpan.getSpanContext().getSpanId()); - - System.out.println("Example complete!"); - System.out.println("\nIn Braintrust, you'll see:"); - System.out.println(" • Root conversation span"); - System.out.println(" • Nested turn spans for each user interaction"); - System.out.println(" • LLM call spans (from BraintrustLangchain.wrap())"); - System.out.println(" • Tool execution spans (from BraintrustLangchain.wrapTools())"); - System.out.println("\n View your traces: " + url + "\n"); - } -} diff --git a/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java b/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java index 9645f88..a3aafa8 100644 --- a/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java +++ b/src/main/java/dev/braintrust/instrumentation/langchain/BraintrustLangchain.java @@ -49,28 +49,6 @@ public static OpenAiStreamingChatModel wrap( } } - /** - * Wrap a tools object to instrument @Tool method executions with Braintrust traces. Returns a - * proxy that intercepts all @Tool annotated methods and creates OpenTelemetry spans. - * - *

Usage: StoryTools tools = new StoryTools(); StoryTools instrumented = - * BraintrustLangchain.wrapTools(openTelemetry, tools); - * AiServices.builder(Assistant.class).chatModel(model).tools(instrumented).build() - * - * @param otel OpenTelemetry instance from braintrust.openTelemetryCreate() - * @param tools Tool object with @Tool annotated methods - * @return Proxied tool object that creates spans for each @Tool method call - */ - @SuppressWarnings("unchecked") - public static T wrapTools(OpenTelemetry otel, T tools) { - try { - return (T) new ByteBuddyToolWrapper(otel).wrap(tools); - } catch (Exception e) { - log.warn("Failed to wrap tools with instrumentation, returning original", e); - return tools; - } - } - private static HttpClientBuilder wrap( OpenTelemetry otel, HttpClientBuilder builder, Options options) { return new WrappedHttpClientBuilder(otel, builder, options); diff --git a/src/main/java/dev/braintrust/instrumentation/langchain/ByteBuddyToolWrapper.java b/src/main/java/dev/braintrust/instrumentation/langchain/ByteBuddyToolWrapper.java deleted file mode 100644 index 0c4ba25..0000000 --- a/src/main/java/dev/braintrust/instrumentation/langchain/ByteBuddyToolWrapper.java +++ /dev/null @@ -1,119 +0,0 @@ -package dev.braintrust.instrumentation.langchain; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.langchain4j.agent.tool.Tool; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Scope; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.implementation.MethodDelegation; -import net.bytebuddy.implementation.bind.annotation.*; -import net.bytebuddy.matcher.ElementMatchers; - -/** - * Uses ByteBuddy to create runtime subclass proxies that intercept @Tool methods and add - * OpenTelemetry spans with Braintrust attributes. - */ -@Slf4j -class ByteBuddyToolWrapper { - private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); - private final Tracer tracer; - - ByteBuddyToolWrapper(OpenTelemetry otel) { - this.tracer = otel.getTracer("braintrust"); - } - - @SuppressWarnings("unchecked") - public T wrap(T originalTools) throws Exception { - Class toolClass = originalTools.getClass(); - - // Create subclass with interceptor, preserving all annotations - Class proxyClass = - new ByteBuddy() - .subclass(toolClass) - .method(ElementMatchers.isAnnotatedWith(Tool.class)) - .intercept( - MethodDelegation.to( - new ToolMethodInterceptor(tracer, originalTools))) - .attribute( - net.bytebuddy.implementation.attribute.MethodAttributeAppender - .ForInstrumentedMethod.INCLUDING_RECEIVER) - .make() - .load(toolClass.getClassLoader()) - .getLoaded(); - - // Create instance - ByteBuddy subclass will delegate to original - return (T) proxyClass.getDeclaredConstructor().newInstance(); - } - - /** Interceptor that wraps @Tool method calls with OpenTelemetry spans */ - public static class ToolMethodInterceptor { - private final Tracer tracer; - private final Object originalTools; - - public ToolMethodInterceptor(Tracer tracer, Object originalTools) { - this.tracer = tracer; - this.originalTools = originalTools; - } - - @RuntimeType - public Object intercept( - @Origin Method method, @AllArguments Object[] args, @SuperCall Callable zuper) - throws Exception { - - String toolName = method.getName(); - - // Build input map from parameters - Map inputMap = new HashMap<>(); - var parameters = method.getParameters(); - for (int i = 0; i < parameters.length && i < args.length; i++) { - inputMap.put(parameters[i].getName(), args[i]); - } - - Span span = tracer.spanBuilder(toolName).startSpan(); - try (Scope scope = span.makeCurrent()) { - // Set Braintrust span attributes - span.setAttribute( - "braintrust.span_attributes", - json(Map.of("type", "tool", "name", toolName))); - - // Set input - span.setAttribute("braintrust.input_json", json(inputMap)); - - // Execute method and measure time - long startTime = System.nanoTime(); - Object result = zuper.call(); // Call original method - long endTime = System.nanoTime(); - - // Set output - span.setAttribute("braintrust.output_json", json(result)); - - // Set metrics - double executionTime = (endTime - startTime) / 1_000_000_000.0; - span.setAttribute( - "braintrust.metrics", json(Map.of("execution_time", executionTime))); - - return result; - } catch (Throwable t) { - span.setStatus(StatusCode.ERROR, t.getMessage()); - span.recordException(t); - throw t; - } finally { - span.end(); - } - } - - @SneakyThrows - private static String json(Object o) { - return JSON_MAPPER.writeValueAsString(o); - } - } -} diff --git a/src/test/java/dev/braintrust/instrumentation/langchain/BraintrustLangchainTest.java b/src/test/java/dev/braintrust/instrumentation/langchain/BraintrustLangchainTest.java index 67d0927..e2555bd 100644 --- a/src/test/java/dev/braintrust/instrumentation/langchain/BraintrustLangchainTest.java +++ b/src/test/java/dev/braintrust/instrumentation/langchain/BraintrustLangchainTest.java @@ -13,8 +13,6 @@ import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.StatusCode; -import java.util.List; import java.util.concurrent.CompletableFuture; import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; @@ -217,218 +215,4 @@ public void onError(Throwable error) { "Output should contain the complete streamed response"); assertNotNull(choice.get("finish_reason"), "Output should have finish_reason"); } - - @Test - @SneakyThrows - void testToolWrapping() { - // Create and wrap tools - TestTools tools = new TestTools(); - TestTools wrappedTools = BraintrustLangchain.wrapTools(testHarness.openTelemetry(), tools); - - // Call wrapped tool method directly - String result = wrappedTools.getWeather("Paris"); - assertNotNull(result); - assertTrue(result.contains("Paris")); - - // Verify span was created - var spans = testHarness.awaitExportedSpans(); - assertEquals(1, spans.size(), "Expected one span for tool execution"); - var span = spans.get(0); - - // Verify span name - assertEquals("getWeather", span.getName()); - - // Verify span type - var attributes = span.getAttributes(); - String spanAttrsJson = attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); - JsonNode spanAttrs = JSON_MAPPER.readTree(spanAttrsJson); - assertEquals("tool", spanAttrs.get("type").asText()); - assertEquals("getWeather", spanAttrs.get("name").asText()); - - // Verify input (parameter names may be arg0, arg1, etc without -parameters flag) - String inputJson = attributes.get(AttributeKey.stringKey("braintrust.input_json")); - assertNotNull(inputJson); - JsonNode input = JSON_MAPPER.readTree(inputJson); - assertTrue(input.isObject(), "Input should be an object"); - assertTrue(input.size() > 0, "Input should have at least one parameter"); - // Check if parameter value is present (either as "location" or "arg0") - String paramValue = - input.has("location") - ? input.get("location").asText() - : input.elements().next().asText(); - assertEquals("Paris", paramValue); - - // Verify output - String outputJson = attributes.get(AttributeKey.stringKey("braintrust.output_json")); - assertNotNull(outputJson); - JsonNode output = JSON_MAPPER.readTree(outputJson); - assertTrue(output.asText().contains("Paris")); - assertTrue(output.asText().contains("72")); - - // Verify metrics - String metricsJson = attributes.get(AttributeKey.stringKey("braintrust.metrics")); - JsonNode metrics = JSON_MAPPER.readTree(metricsJson); - assertTrue(metrics.has("execution_time")); - assertTrue(metrics.get("execution_time").asDouble() >= 0); - } - - @Test - @SneakyThrows - void testToolWrappingWithException() { - TestTools tools = new TestTools(); - TestTools wrappedTools = BraintrustLangchain.wrapTools(testHarness.openTelemetry(), tools); - - // Execute and expect exception - assertThrows( - RuntimeException.class, - () -> { - wrappedTools.throwError(); - }); - - // Verify span with error status - var spans = testHarness.awaitExportedSpans(); - assertEquals(1, spans.size()); - var span = spans.get(0); - - assertEquals(StatusCode.ERROR, span.getStatus().getStatusCode()); - assertTrue( - span.getEvents().stream().anyMatch(e -> e.getName().equals("exception")), - "Span should have exception event"); - } - - @Test - @SneakyThrows - void testToolWrappingWithMultipleCalls() { - TestTools tools = new TestTools(); - TestTools wrappedTools = BraintrustLangchain.wrapTools(testHarness.openTelemetry(), tools); - - // Call multiple tool methods - wrappedTools.getWeather("Tokyo"); - int sum = wrappedTools.calculateSum(5, 7); - assertEquals(12, sum); - - // Verify two spans created - var spans = testHarness.awaitExportedSpans(); - assertEquals(2, spans.size(), "Expected two spans"); - - // Verify first span (getWeather) - var weatherSpan = spans.get(0); - assertEquals("getWeather", weatherSpan.getName()); - - // Verify second span (calculateSum) - var sumSpan = spans.get(1); - assertEquals("calculateSum", sumSpan.getName()); - String sumOutput = - sumSpan.getAttributes().get(AttributeKey.stringKey("braintrust.output_json")); - assertEquals("12", sumOutput); - } - - @Test - @SneakyThrows - void testToolWrappingAllBraintrustAttributesPresent() { - // This test verifies ALL required Braintrust attributes are present - // to catch issues that would cause UI problems - TestTools tools = new TestTools(); - TestTools wrappedTools = BraintrustLangchain.wrapTools(testHarness.openTelemetry(), tools); - - wrappedTools.getWeather("London"); - - var spans = testHarness.awaitExportedSpans(); - var span = spans.get(0); - var attributes = span.getAttributes(); - - // CRITICAL: These attributes MUST be present for Braintrust UI to display properly - assertNotNull( - attributes.get(AttributeKey.stringKey("braintrust.span_attributes")), - "braintrust.span_attributes is required for UI"); - assertNotNull( - attributes.get(AttributeKey.stringKey("braintrust.input_json")), - "braintrust.input_json is required for UI to show inputs"); - assertNotNull( - attributes.get(AttributeKey.stringKey("braintrust.output_json")), - "braintrust.output_json is required for UI to show outputs"); - assertNotNull( - attributes.get(AttributeKey.stringKey("braintrust.metrics")), - "braintrust.metrics is required for UI to show metrics"); - - // Verify span_attributes has correct structure - String spanAttrsJson = attributes.get(AttributeKey.stringKey("braintrust.span_attributes")); - JsonNode spanAttrs = JSON_MAPPER.readTree(spanAttrsJson); - assertTrue(spanAttrs.has("type"), "span_attributes must have 'type' field"); - assertTrue(spanAttrs.has("name"), "span_attributes must have 'name' field"); - assertEquals("tool", spanAttrs.get("type").asText(), "Tool spans must have type='tool'"); - } - - @Test - @SneakyThrows - void testToolWrappingIntegrationWithConversationHierarchy() { - // This test simulates a realistic usage pattern like the example - // and verifies ALL spans in the hierarchy have input/output for UI - var tracer = testHarness.openTelemetry().getTracer("test"); - TestTools tools = new TestTools(); - TestTools wrappedTools = BraintrustLangchain.wrapTools(testHarness.openTelemetry(), tools); - - // Create conversation span (like example does) - var conversationSpan = tracer.spanBuilder("conversation").startSpan(); - conversationSpan.setAttribute( - "braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"conversation\"}"); - conversationSpan.setAttribute( - "braintrust.input_json", "{\"description\":\"test conversation\"}"); - - try (var ignored = conversationSpan.makeCurrent()) { - // Create turn span (like example does) - var turnSpan = tracer.spanBuilder("turn_1").startSpan(); - turnSpan.setAttribute( - "braintrust.span_attributes", "{\"type\":\"task\",\"name\":\"turn_1\"}"); - turnSpan.setAttribute("braintrust.input_json", "{\"user_message\":\"test query\"}"); - - try (var turnScope = turnSpan.makeCurrent()) { - // Call tool within turn (tool wrapper should create tool span) - String result = wrappedTools.getWeather("Paris"); - turnSpan.setAttribute( - "braintrust.output_json", "{\"assistant_message\":\"" + result + "\"}"); - } finally { - turnSpan.end(); - } - } finally { - conversationSpan.setAttribute("braintrust.output_json", "{\"status\":\"completed\"}"); - conversationSpan.end(); - } - - // Verify all 3 spans exist and have required attributes - var spans = testHarness.awaitExportedSpans(); - assertEquals(3, spans.size(), "Expected 3 spans: conversation, turn, and tool"); - - // Find each span type - var toolSpan = - spans.stream().filter(s -> s.getName().equals("getWeather")).findFirst().get(); - var turnSpanData = - spans.stream().filter(s -> s.getName().equals("turn_1")).findFirst().get(); - var convSpan = - spans.stream().filter(s -> s.getName().equals("conversation")).findFirst().get(); - - // CRITICAL: Every span must have input/output for UI to display properly - for (var span : List.of(toolSpan, turnSpanData, convSpan)) { - var attrs = span.getAttributes(); - assertNotNull( - attrs.get(AttributeKey.stringKey("braintrust.span_attributes")), - span.getName() + " missing braintrust.span_attributes"); - assertNotNull( - attrs.get(AttributeKey.stringKey("braintrust.input_json")), - span.getName() + " missing braintrust.input_json - UI won't show input!"); - assertNotNull( - attrs.get(AttributeKey.stringKey("braintrust.output_json")), - span.getName() + " missing braintrust.output_json - UI won't show output!"); - } - - // Verify span hierarchy (parent-child relationships) - assertEquals( - convSpan.getSpanId(), - turnSpanData.getParentSpanId(), - "Turn should be child of conversation"); - assertEquals( - turnSpanData.getSpanId(), - toolSpan.getParentSpanId(), - "Tool should be child of turn"); - } } diff --git a/src/test/java/dev/braintrust/instrumentation/langchain/TestTools.java b/src/test/java/dev/braintrust/instrumentation/langchain/TestTools.java deleted file mode 100644 index b289611..0000000 --- a/src/test/java/dev/braintrust/instrumentation/langchain/TestTools.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.braintrust.instrumentation.langchain; - -import dev.langchain4j.agent.tool.Tool; - -public class TestTools { - - @Tool("Get weather for a location") - public String getWeather(String location) { - return String.format( - "{\"location\":\"%s\",\"temperature\":72,\"condition\":\"sunny\"}", location); - } - - @Tool("Calculate sum") - public int calculateSum(int a, int b) { - return a + b; - } - - @Tool("Tool that throws exception") - public String throwError() { - throw new RuntimeException("Intentional error for testing"); - } -}