From e020cc02ccbd7c4a93293cf04a06c806ef760299 Mon Sep 17 00:00:00 2001 From: Eric Dong Date: Mon, 17 Nov 2025 09:41:36 -0500 Subject: [PATCH] added prompt example --- langsmith-java-example/README.md | 57 ++- langsmith-java-example/build.gradle.kts | 64 +-- .../example/{ => otel}/OtelJaegerExample.java | 2 +- .../{ => otel}/OtelLangSmithExample.java | 2 +- .../example/{ => otel}/OtelOpenAIExample.java | 2 +- .../SpringBootLangSmithExample.java | 2 +- .../{ => otel}/config/OtelConfiguration.java | 2 +- .../{ => otel}/config/OtelShutdownHook.java | 2 +- .../{ => otel}/controller/ChatController.java | 4 +- .../{ => otel}/service/LlmService.java | 2 +- .../prompt/PromptManagementExample.java | 376 ++++++++++++++++++ 11 files changed, 470 insertions(+), 45 deletions(-) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/OtelJaegerExample.java (99%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/OtelLangSmithExample.java (99%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/OtelOpenAIExample.java (99%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/SpringBootLangSmithExample.java (98%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/config/OtelConfiguration.java (96%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/config/OtelShutdownHook.java (94%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/controller/ChatController.java (96%) rename langsmith-java-example/src/main/java/com/langchain/smith/example/{ => otel}/service/LlmService.java (98%) create mode 100644 langsmith-java-example/src/main/java/com/langchain/smith/example/prompt/PromptManagementExample.java diff --git a/langsmith-java-example/README.md b/langsmith-java-example/README.md index 8f86794a..11f072d2 100644 --- a/langsmith-java-example/README.md +++ b/langsmith-java-example/README.md @@ -1,6 +1,23 @@ # LangSmith Java Examples -## 1. Jaeger (Local) +This module contains runnable examples organized by feature: +- **`otel/`** - OpenTelemetry tracing examples +- **`prompt/`** - Prompt management examples + +## Prerequisites + +All examples require: +```bash +export LANGSMITH_API_KEY=your_api_key +``` + +The `LANGCHAIN_BASE_URL` environment variable is optional and defaults to `https://api.smith.langchain.com` if not set. + +## OpenTelemetry Tracing Examples + +Located in `src/main/java/com/langchain/smith/example/otel/` + +### Jaeger (Local) Send traces to local Jaeger instance. @@ -15,13 +32,12 @@ docker run -d --name jaeger -p 4318:4318 -p 16686:16686 jaegertracing/all-in-one open http://localhost:16686 ``` -## 2. OpenAI + LangSmith (Real API Calls) +### OpenAI + LangSmith (Real API Calls) Make actual OpenAI API calls with automatic tracing to LangSmith. ```bash export OPENAI_API_KEY=your_openai_key -export LANGSMITH_API_KEY=your_langsmith_key export LANGSMITH_PROJECT=my-project # optional, defaults to "default" ./gradlew :langsmith-java-example:run -Pexample=OtelOpenAI @@ -29,12 +45,11 @@ export LANGSMITH_PROJECT=my-project # optional, defaults to "default" View traces at https://smith.langchain.com -## 3. LangSmith (Standalone) +### LangSmith (Standalone) Send mock traces to LangSmith without external API calls. ```bash -export LANGSMITH_API_KEY=your_api_key export LANGSMITH_PROJECT=my-project # optional, defaults to "default" ./gradlew :langsmith-java-example:run -Pexample=OtelLangSmith @@ -42,12 +57,11 @@ export LANGSMITH_PROJECT=my-project # optional, defaults to "default" View traces at https://smith.langchain.com -## 4. Spring Boot API to Langsmith +### Spring Boot API to LangSmith REST API with OpenTelemetry traces sent to LangSmith. ```bash -export LANGSMITH_API_KEY=your_api_key export LANGSMITH_PROJECT=my-project # optional # Start server @@ -60,3 +74,32 @@ curl -X POST http://localhost:8080/api/chat \ curl "http://localhost:8080/api/analyze?text=This%20is%20great" ``` + +## Prompt Management Examples + +Located in `src/main/java/com/langchain/smith/example/prompt/` + +### Prompt Management (Getting Started) + +**RECOMMENDED** - Clean, simple example following the same pattern as the Dataset example. + +```bash +./gradlew :langsmith-java-example:run -Pexample=PromptManagement +``` + +**Features demonstrated:** +1. Create prompt repositories using `client.repos().create()` +2. Add prompt content with variables using `client.commits().update()` +3. List and filter prompts using `client.repos().list()` +4. Retrieve prompt content using `client.commits().retrieve()` +5. View prompts in the LangSmith UI + +This example follows the [LangSmith Prompt Management docs](https://docs.langchain.com/langsmith/manage-prompts-programmatically) and uses the SDK directly. + +``` + +**Additional features:** +- Use prompts with OpenAI to generate responses +- Update prompt metadata and descriptions +- Pull specific commit versions +- System + user message prompts diff --git a/langsmith-java-example/build.gradle.kts b/langsmith-java-example/build.gradle.kts index af04b80d..799e8a78 100644 --- a/langsmith-java-example/build.gradle.kts +++ b/langsmith-java-example/build.gradle.kts @@ -15,20 +15,11 @@ dependencies { // Jackson for JSON handling in examples implementation("com.fasterxml.jackson.core:jackson-databind:2.13.4") - // OpenAI SDK for RunExperimentExample - implementation("com.openai:openai-java:4.6.1") - - // SLF4J logging backend for examples - runtimeOnly("org.slf4j:slf4j-simple:2.0.17") // Spring Boot dependencies (optional - only needed for Spring Boot example) implementation(platform("org.springframework.boot:spring-boot-dependencies:2.7.18")) - implementation("org.springframework.boot:spring-boot-starter-web") { - exclude(group = "ch.qos.logback", module = "logback-classic") - } - implementation("org.springframework.boot:spring-boot-starter") { - exclude(group = "ch.qos.logback", module = "logback-classic") - } + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter") } tasks.withType().configureEach { @@ -45,29 +36,44 @@ tasks.withType().configureEach application { // Use `./gradlew :langsmith-java-example:run` to run `Main` // Use `./gradlew :langsmith-java-example:run -Pexample=Something` to run `SomethingExample` - mainClass = "com.langchain.smith.example.${ - if (project.hasProperty("example")) { - val exampleName = project.property("example") as String - val baseName = if (exampleName.endsWith("Example")) exampleName else "${exampleName}Example" - - // Check if it's a Java or Kotlin file - val javaFile = file("src/main/java/com/langchain/smith/example/${baseName}.java") - val kotlinFile = file("src/main/kotlin/com/langchain/smith/example/${baseName}.kt") + mainClass = if (project.hasProperty("example")) { + val exampleName = project.property("example") as String + val baseName = if (exampleName.endsWith("Example")) exampleName else "${exampleName}Example" + + // Search in multiple subdirectories: root, otel, prompt + val searchPaths = listOf( + "" to "com.langchain.smith.example", + "otel/" to "com.langchain.smith.example.otel", + "prompt/" to "com.langchain.smith.example.prompt" + ) + + var foundPackage = "" + var isKotlin = false + + for ((subdir, packageName) in searchPaths) { + val javaFile = file("src/main/java/com/langchain/smith/example/${subdir}${baseName}.java") + val kotlinFile = file("src/main/kotlin/com/langchain/smith/example/${subdir}${baseName}.kt") if (javaFile.exists()) { - // Java file - no Kt suffix - baseName + foundPackage = packageName + isKotlin = false + break } else if (kotlinFile.exists()) { - // Kotlin file - add Kt suffix for main function - "${baseName}Kt" - } else { - // Default: assume Kotlin for backwards compatibility - "${baseName}Kt" + foundPackage = packageName + isKotlin = true + break } - } else { - "Main" } - }" + + if (foundPackage.isNotEmpty()) { + "${foundPackage}.${baseName}${if (isKotlin) "Kt" else ""}" + } else { + // Default: assume Kotlin in root for backwards compatibility + "com.langchain.smith.example.${baseName}Kt" + } + } else { + "Main" + } } // Export stdin to examples for readln() diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelJaegerExample.java similarity index 99% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelJaegerExample.java index 087c7a4e..2c0ed0bc 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelJaegerExample.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example; +package com.langchain.smith.example.otel; import com.langchain.smith.otel.OtelSpanCreator; import com.langchain.smith.otel.OtelTraceExporter; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelLangSmithExample.java similarity index 99% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelLangSmithExample.java index 0f88e83e..f385a52e 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelLangSmithExample.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example; +package com.langchain.smith.example.otel; import com.langchain.smith.otel.OtelConfig; import com.langchain.smith.otel.OtelSpanCreator; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelOpenAIExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelOpenAIExample.java similarity index 99% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/OtelOpenAIExample.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelOpenAIExample.java index dab9d012..056e7e4b 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelOpenAIExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/OtelOpenAIExample.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example; +package com.langchain.smith.example.otel; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/SpringBootLangSmithExample.java similarity index 98% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/SpringBootLangSmithExample.java index 39707de8..a77ccae9 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/SpringBootLangSmithExample.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example; +package com.langchain.smith.example.otel; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelConfiguration.java similarity index 96% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelConfiguration.java index e850feb7..2c3371d3 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelConfiguration.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example.config; +package com.langchain.smith.example.otel.config; import com.langchain.smith.otel.OtelConfig; import com.langchain.smith.otel.OtelTraceExporter; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelShutdownHook.java similarity index 94% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelShutdownHook.java index 0186eeed..30a43826 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/config/OtelShutdownHook.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example.config; +package com.langchain.smith.example.otel.config; import com.langchain.smith.otel.OtelTraceExporter; import javax.annotation.PreDestroy; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/controller/ChatController.java similarity index 96% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/controller/ChatController.java index 11288209..29058f6f 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/controller/ChatController.java @@ -1,6 +1,6 @@ -package com.langchain.smith.example.controller; +package com.langchain.smith.example.otel.controller; -import com.langchain.smith.example.service.LlmService; +import com.langchain.smith.example.otel.service.LlmService; import com.langchain.smith.otel.OtelSpanCreator; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/service/LlmService.java similarity index 98% rename from langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java rename to langsmith-java-example/src/main/java/com/langchain/smith/example/otel/service/LlmService.java index a0d357c2..db9bb261 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/otel/service/LlmService.java @@ -1,4 +1,4 @@ -package com.langchain.smith.example.service; +package com.langchain.smith.example.otel.service; import com.langchain.smith.otel.OtelSpanCreator; import io.opentelemetry.api.trace.Span; diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/prompt/PromptManagementExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/prompt/PromptManagementExample.java new file mode 100644 index 00000000..38456cb6 --- /dev/null +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/prompt/PromptManagementExample.java @@ -0,0 +1,376 @@ +package com.langchain.smith.example.prompt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.langchain.smith.client.LangsmithClient; +import com.langchain.smith.client.okhttp.LangsmithOkHttpClient; +import com.langchain.smith.core.JsonValue; +import com.langchain.smith.models.commits.CommitListParams; +import com.langchain.smith.models.commits.CommitListResponse; +import com.langchain.smith.models.commits.CommitManifestResponse; +import com.langchain.smith.models.commits.CommitRetrieveParams; +import com.langchain.smith.models.commits.CommitUpdateParams; +import com.langchain.smith.models.repos.RepoCreateParams; +import com.langchain.smith.models.repos.RepoListParams; +import com.langchain.smith.models.repos.RepoListResponse; +import com.langchain.smith.models.repos.RepoWithLookups; +import java.util.HashMap; +import java.util.Map; + +/** + * Demonstrates how to manage prompts programmatically using the LangSmith Java + * SDK. + * + *

+ * This example shows: + * - Creating prompt repositories (repos) + * - Adding prompt content as commits (with variables) + * - Listing prompts with filters + * - Retrieving prompt content + * - Pulling specific versions + * + *

+ * Prerequisites: + * - `LANGSMITH_API_KEY`: Your LangSmith API key + * - `LANGCHAIN_BASE_URL`: LangSmith API URL (https://api.smith.langchain.com) + * + *

+ * Running: + * ```bash + * ./gradlew :langsmith-java-example:run -Pexample=PromptManagement + * ``` + */ +public class PromptManagementExample { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static void main(String[] args) { + System.out.println("=== LangSmith Prompt Management Example ===\n"); + + LangsmithClient client = LangsmithOkHttpClient.fromEnv(); + String owner = getOwnerFromEnv(); + String promptName = "joke-generator"; + + try { + // 1. Check if prompt already exists + System.out.println("1. Checking for existing prompt using client.repos().list()..."); + RepoWithLookups existing = findPrompt(client, promptName); + boolean promptExists = existing != null; + + if (promptExists) { + System.out.println(" ℹ Prompt '" + promptName + "' already exists"); + System.out.println(" View: https://smith.langchain.com/prompts/" + existing.fullName()); + System.out.println(" Skipping creation steps...\n"); + } else { + System.out.println(" ✓ No existing prompt found\n"); + + // 2. Create prompt repository (metadata only) + System.out.println("2. Creating prompt repository using client.repos().create()..."); + client.repos() + .create(RepoCreateParams.builder() + .repoHandle(promptName) + .description("A joke generator that accepts a topic variable") + .isPublic(false) + .build()); + System.out.println(" ✓ Created prompt repository: " + promptName + "\n"); + } + + // 3. Add prompt content as a commit (only if prompt is new or has no commits) + boolean commitCreated = false; + if (promptExists) { + String parentCommitHash = getLatestCommitHash(client, promptName, owner); + if (parentCommitHash != null) { + System.out.println("3. Prompt already has content, skipping commit creation..."); + System.out.println(" ℹ To update the prompt, delete it first or modify the content\n"); + } else { + commitCreated = createCommit(client, promptName, owner, null); + } + } else { + commitCreated = createCommit(client, promptName, owner, null); + } + + // 4. List prompts + System.out.println("4. Listing prompts using client.repos().list()..."); + RepoListResponse allPrompts = client.repos() + .list(RepoListParams.builder() + .isPublic(RepoListParams.IsPublic.FALSE) + .limit(100L) + .build()); + System.out.println(" ✓ Found " + allPrompts.repos().size() + " prompt(s) in your organization"); + + RepoListResponse jokePrompts = client.repos() + .list(RepoListParams.builder() + .query("joke") + .isPublic(RepoListParams.IsPublic.FALSE) + .limit(100L) + .build()); + System.out.println( + " ✓ Found " + jokePrompts.repos().size() + " prompt(s) matching 'joke' in your organization\n"); + + // 5. Pull prompt content (retrieve latest commit) + System.out.println("5. Pulling prompt content using client.commits().list() and retrieve()..."); + + String commitHash = getLatestCommitHash(client, promptName, owner); + if (commitHash == null) { + throw new RuntimeException("No commits found for prompt"); + } + + System.out.println(" ✓ Latest commit: " + commitHash.substring(0, 8) + "..."); + + // Retrieve the manifest + CommitManifestResponse manifestResponse = client.commits() + .retrieve( + commitHash, + CommitRetrieveParams.builder() + .owner(owner) + .repo(promptName) + .build()); + + String promptContent = extractPromptContent(manifestResponse._manifest()); + System.out.println(" ✓ Prompt content: \"" + promptContent + "\"\n"); + + // 6. Demonstrate using the prompt with a value + System.out.println("6. Using the prompt with a value..."); + String filledPrompt = promptContent.replace("{topic}", "dogs"); + System.out.println(" Template: \"" + promptContent + "\""); + System.out.println(" Filled: \"" + filledPrompt + "\"\n"); + + // Summary + System.out.println("=== Summary ==="); + if (!promptExists) { + System.out.println("✓ Created prompt repository"); + } else { + System.out.println("ℹ Prompt already existed (skipped creation)"); + } + if (commitCreated) { + System.out.println("✓ Added prompt content as commit"); + } else { + System.out.println("ℹ Prompt content unchanged (skipped commit)"); + } + System.out.println("✓ Listed and filtered prompts"); + System.out.println("✓ Retrieved prompt content"); + System.out.println("✓ Demonstrated using prompt with a value"); + System.out.println(); + System.out.println("Learn more:"); + System.out.println("https://docs.langchain.com/langsmith/manage-prompts-programmatically"); + + } catch (Exception e) { + System.err.println("\nError: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } finally { + client.close(); + } + } + + /** + * Creates a chat prompt manifest with multiple messages (system + user) compatible with LangSmith. + */ + private static Map createChatPromptWithMultipleMessages( + String systemMessage, String userMessage, String[] inputVariables) { + Map manifest = new HashMap<>(); + manifest.put("lc", 1); + manifest.put("type", "constructor"); + manifest.put("id", new String[] {"langchain", "prompts", "chat", "ChatPromptTemplate"}); + + Map kwargs = new HashMap<>(); + kwargs.put("input_variables", inputVariables); + + java.util.List> messages = new java.util.ArrayList<>(); + messages.add(createMessagePrompt("system", systemMessage)); + messages.add(createMessagePrompt("human", userMessage)); + + kwargs.put("messages", messages.toArray()); + manifest.put("kwargs", kwargs); + + return manifest; + } + + /** + * Creates a message prompt (system, human, or AI). + */ + private static Map createMessagePrompt(String messageType, String template) { + String className; + switch (messageType.toLowerCase()) { + case "system": + className = "SystemMessagePromptTemplate"; + break; + case "ai": + case "assistant": + className = "AIMessagePromptTemplate"; + break; + case "human": + case "user": + default: + className = "HumanMessagePromptTemplate"; + break; + } + + Map message = new HashMap<>(); + message.put("lc", 1); + message.put("type", "constructor"); + message.put("id", new String[] {"langchain", "core", "prompts", "chat", className}); + + Map stringPrompt = new HashMap<>(); + stringPrompt.put("lc", 1); + stringPrompt.put("type", "prompt"); + stringPrompt.put("id", new String[] {"langchain", "prompts", "prompt", "StringPromptTemplate"}); + Map stringPromptKwargs = new HashMap<>(); + stringPromptKwargs.put("template", template); + stringPrompt.put("kwargs", stringPromptKwargs); + + Map msgKwargs = new HashMap<>(); + msgKwargs.put("prompt", stringPrompt); + message.put("kwargs", msgKwargs); + + return message; + } + + /** + * Determines message type from the message object's id field. + */ + @SuppressWarnings("unchecked") + private static String getMessageType(Map message) { + try { + Object idObj = message.get("id"); + String className = null; + + if (idObj instanceof Object[]) { + Object[] idArray = (Object[]) idObj; + if (idArray.length > 0) { + className = idArray[idArray.length - 1].toString(); + } + } else if (idObj instanceof java.util.List) { + java.util.List idList = (java.util.List) idObj; + if (!idList.isEmpty()) { + className = idList.get(idList.size() - 1).toString(); + } + } + + if (className != null) { + if (className.contains("System")) { + return "system"; + } else if (className.contains("AI") || className.contains("Assistant")) { + return "assistant"; + } + } + } catch (Exception e) { + // Default to "user" if we can't determine the type + } + return "user"; + } + + /** + * Finds a prompt by exact repo handle match. + * + * @return the prompt if found, null otherwise + */ + private static RepoWithLookups findPrompt(LangsmithClient client, String promptName) { + RepoListResponse existingRepos = client.repos() + .list(RepoListParams.builder() + .query(promptName) + .isPublic(RepoListParams.IsPublic.FALSE) + .build()); + + for (RepoWithLookups repo : existingRepos.repos()) { + if (repo.repoHandle().equals(promptName)) { + return repo; + } + } + return null; + } + + /** + * Helper method to get the latest commit hash for a prompt repository. + */ + private static String getLatestCommitHash(LangsmithClient client, String promptName, String owner) { + try { + CommitListResponse commits = client.commits() + .list( + promptName, + CommitListParams.builder() + .owner(owner) + .repo(promptName) + .limit(1L) + .build()); + if (!commits.commits().isEmpty()) { + return commits.commits().get(0).commitHash(); + } + } catch (Exception e) { + // No commits found + } + return null; + } + + /** + * Creates a commit with prompt content. + * + * @return true if commit was created, false otherwise + */ + private static boolean createCommit(LangsmithClient client, String promptName, String owner, String parentCommit) { + System.out.println("3. Adding prompt content using client.commits().update()..."); + Map manifest = createChatPromptWithMultipleMessages( + "You are a helpful assistant that tells jokes.", + "Tell me a joke about {topic}", + new String[] {"topic"}); + + CommitUpdateParams.Builder builder = CommitUpdateParams.builder() + .owner(owner) + .manifest(JsonValue.from(manifest)); + if (parentCommit != null) { + builder.parentCommit(parentCommit); + } + + try { + client.commits().update(promptName, builder.build()); + System.out.println(" ✓ Added prompt content as commit\n"); + return true; + } catch (Exception e) { + throw new RuntimeException("Failed to create commit", e); + } + } + + /** + * Extracts prompt content from a manifest for display (shows all messages). + */ + @SuppressWarnings("unchecked") + private static String extractPromptContent(JsonValue manifestJson) { + try { + Map manifest = MAPPER.convertValue(manifestJson, Map.class); + Map kwargs = (Map) manifest.get("kwargs"); + Object messagesObj = kwargs.get("messages"); + + Object[] messages; + if (messagesObj instanceof Object[]) { + messages = (Object[]) messagesObj; + } else if (messagesObj instanceof java.util.List) { + java.util.List messagesList = (java.util.List) messagesObj; + messages = messagesList.toArray(new Object[0]); + } else { + return "Unable to parse prompt content"; + } + + StringBuilder result = new StringBuilder(); + for (int i = 0; i < messages.length; i++) { + Map message = (Map) messages[i]; + Map msgKwargs = (Map) message.get("kwargs"); + Map prompt = (Map) msgKwargs.get("prompt"); + Map promptKwargs = (Map) prompt.get("kwargs"); + String template = promptKwargs.get("template").toString(); + String messageType = getMessageType(message); + + if (i > 0) { + result.append(" | "); + } + result.append(messageType).append(": \"").append(template).append("\""); + } + return result.toString(); + } catch (Exception e) { + return "Unable to parse prompt content"; + } + } + + private static String getOwnerFromEnv() { + String owner = System.getenv("LANGSMITH_OWNER"); + return (owner == null || owner.isEmpty()) ? "-" : owner; + } +}