chore: convert to kotlin

This commit is contained in:
Eric Dong
2025-11-17 10:56:18 -05:00
committed by stainless-app[bot]
parent 3fdd586165
commit 802f5a118c
3 changed files with 335 additions and 386 deletions
+18 -12
View File
@@ -8,10 +8,15 @@ This module contains runnable examples organized by feature:
All examples require:
```bash
./gradlew :langsmith-java-example:run -Pexample=ExampleName -Dlangchain.langsmithApiKey=your_api_key
```
Alternatively, you can use environment variables:
```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.
The `langchain.baseUrl` system property (or `LANGCHAIN_BASE_URL` environment variable) is optional and defaults to `https://api.smith.langchain.com/` if not set.
## OpenTelemetry Tracing Examples
@@ -37,10 +42,10 @@ open http://localhost:16686
Make actual OpenAI API calls with automatic tracing to LangSmith.
```bash
export OPENAI_API_KEY=your_openai_key
export LANGSMITH_PROJECT=my-project # optional, defaults to "default"
./gradlew :langsmith-java-example:run -Pexample=OtelOpenAI
./gradlew :langsmith-java-example:run -Pexample=OtelOpenAI \
-Dlangchain.langsmithApiKey=your_api_key \
-DOPENAI_API_KEY=your_openai_key \
-DLANGSMITH_PROJECT=my-project # optional, defaults to "default"
```
View traces at https://smith.langchain.com
@@ -50,9 +55,9 @@ View traces at https://smith.langchain.com
Send mock traces to LangSmith without external API calls.
```bash
export LANGSMITH_PROJECT=my-project # optional, defaults to "default"
./gradlew :langsmith-java-example:run -Pexample=OtelLangSmith
./gradlew :langsmith-java-example:run -Pexample=OtelLangSmith \
-Dlangchain.langsmithApiKey=your_api_key \
-DLANGSMITH_PROJECT=my-project # optional, defaults to "default"
```
View traces at https://smith.langchain.com
@@ -62,10 +67,10 @@ View traces at https://smith.langchain.com
REST API with OpenTelemetry traces sent to LangSmith.
```bash
export LANGSMITH_PROJECT=my-project # optional
# Start server
./gradlew :langsmith-java-example:run -Pexample=SpringBootLangSmith
./gradlew :langsmith-java-example:run -Pexample=SpringBootLangSmith \
-Dlangchain.langsmithApiKey=your_api_key \
-DLANGSMITH_PROJECT=my-project # optional
# In another terminal, test endpoints:
curl -X POST http://localhost:8080/api/chat \
@@ -84,7 +89,8 @@ Located in `src/main/java/com/langchain/smith/example/prompt/`
**RECOMMENDED** - Clean, simple example following the same pattern as the Dataset example.
```bash
./gradlew :langsmith-java-example:run -Pexample=PromptManagement
./gradlew :langsmith-java-example:run -Pexample=PromptManagement \
-Dlangchain.langsmithApiKey=your_api_key
```
**Features demonstrated:**
@@ -1,374 +0,0 @@
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.
*
* <p>
* This example shows:
* - Creating prompt repositories (repos)
* - Adding prompt content as commits (with variables)
* - Listing prompts with filters
* - Retrieving prompt content
* - Pulling specific versions
*
* <p>
* Prerequisites:
* - `LANGSMITH_API_KEY`: Your LangSmith API key
* - `LANGCHAIN_BASE_URL`: LangSmith API URL (https://api.smith.langchain.com)
*
* <p>
* 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<String, Object> createChatPromptWithMultipleMessages(
String systemMessage, String userMessage, String[] inputVariables) {
Map<String, Object> manifest = new HashMap<>();
manifest.put("lc", 1);
manifest.put("type", "constructor");
manifest.put("id", new String[] {"langchain", "prompts", "chat", "ChatPromptTemplate"});
Map<String, Object> kwargs = new HashMap<>();
kwargs.put("input_variables", inputVariables);
java.util.List<Map<String, Object>> 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<String, Object> 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<String, Object> message = new HashMap<>();
message.put("lc", 1);
message.put("type", "constructor");
message.put("id", new String[] {"langchain", "core", "prompts", "chat", className});
Map<String, Object> stringPrompt = new HashMap<>();
stringPrompt.put("lc", 1);
stringPrompt.put("type", "prompt");
stringPrompt.put("id", new String[] {"langchain", "prompts", "prompt", "StringPromptTemplate"});
Map<String, Object> stringPromptKwargs = new HashMap<>();
stringPromptKwargs.put("template", template);
stringPrompt.put("kwargs", stringPromptKwargs);
Map<String, Object> 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<String, Object> 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<Object> idList = (java.util.List<Object>) 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<String, Object> 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<String, Object> manifest = MAPPER.convertValue(manifestJson, Map.class);
Map<String, Object> kwargs = (Map<String, Object>) 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<Object> messagesList = (java.util.List<Object>) 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<String, Object> message = (Map<String, Object>) messages[i];
Map<String, Object> msgKwargs = (Map<String, Object>) message.get("kwargs");
Map<String, Object> prompt = (Map<String, Object>) msgKwargs.get("prompt");
Map<String, Object> promptKwargs = (Map<String, Object>) 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;
}
}
@@ -0,0 +1,317 @@
package com.langchain.smith.example
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.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.RepoWithLookups
/**
* 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
* ```
*/
private val MAPPER = ObjectMapper()
fun main() {
println("=== LangSmith Prompt Management Example ===\n")
val client = LangsmithOkHttpClient.fromEnv()
val owner = getOwnerFromEnv()
val promptName = "joke-generator"
try {
// 1. Check if prompt already exists
println("1. Checking for existing prompt using client.repos().list()...")
val existing = findPrompt(client, promptName)
val promptExists = existing.isPresent
if (promptExists) {
val repo = existing.get()
println("2. Prompt '$promptName' already exists")
println(" View: https://smith.langchain.com/prompts/${repo.fullName()}")
println(" Skipping creation steps...\n")
} else {
println(" ✓ No existing prompt found\n")
// 2. Create prompt repository (metadata only)
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()
)
println(" ✓ Created prompt repository: $promptName\n")
}
// 3. Add prompt content as a commit (only if prompt is new or has no commits)
val commitCreated = if (!promptExists || !hasLatestCommit(client, promptName, owner)) {
createCommit(client, promptName, owner, null)
} else {
println("3. Prompt already has content, skipping commit creation...")
println(" To update the prompt, delete it first or modify the content\n")
false
}
// 4. List prompts
println("4. Listing prompts using client.repos().list()...")
val allPrompts = client.repos()
.list(
RepoListParams.builder()
.isPublic(RepoListParams.IsPublic.FALSE)
.build()
)
println(" ✓ Found ${allPrompts.repos().size} prompt(s) in your organization")
val jokePrompts = client.repos()
.list(
RepoListParams.builder()
.query("joke")
.isPublic(RepoListParams.IsPublic.FALSE)
.build()
)
println(" ✓ Found ${jokePrompts.repos().size} prompt(s) matching 'joke' in your organization\n")
// 5. Pull prompt content (retrieve latest commit)
println("5. Pulling prompt content using client.commits().retrieve() with commit='latest'...")
// Retrieve the manifest using "latest" as the commit hash
val manifestResponse = client.commits()
.retrieve(
"latest",
CommitRetrieveParams.builder()
.owner(owner)
.repo(promptName)
.build()
)
println(" ✓ Retrieved latest commit manifest")
val promptContent = extractPromptContent(manifestResponse._manifest())
println(" ✓ Prompt content: \"$promptContent\"\n")
// 6. Demonstrate using the prompt with a value
println("6. Using the prompt with a value...")
val filledPrompt = promptContent.replace("{topic}", "dogs")
println(" Template: \"$promptContent\"")
println(" Filled: \"$filledPrompt\"\n")
// Summary
println("=== Summary ===")
if (!promptExists) {
println("✓ Created prompt repository")
} else {
println(" Prompt already existed (skipped creation)")
}
if (commitCreated) {
println("✓ Added prompt content as commit")
} else {
println(" Prompt content unchanged (skipped commit)")
}
println("✓ Listed and filtered prompts")
println("✓ Retrieved prompt content")
println("✓ Demonstrated using prompt with a value")
println()
println("Learn more:")
println("https://docs.langchain.com/langsmith/manage-prompts-programmatically")
} catch (e: Exception) {
System.err.println("\nError: ${e.message}")
e.printStackTrace()
System.exit(1)
} finally {
client.close()
}
}
/**
* Creates a chat prompt manifest with multiple messages (system + user) compatible with LangSmith.
*/
private fun createChatPromptWithMultipleMessages(
systemMessage: String,
userMessage: String,
inputVariables: Array<String>
): Map<String, Any> {
val messages = listOf(
createMessagePrompt("system", systemMessage),
createMessagePrompt("human", userMessage)
)
return mapOf(
"lc" to 1,
"type" to "constructor",
"id" to arrayOf("langchain", "prompts", "chat", "ChatPromptTemplate"),
"kwargs" to mapOf(
"input_variables" to inputVariables,
"messages" to messages.toTypedArray()
)
)
}
/**
* Creates a message prompt (system, human, or AI).
*/
private fun createMessagePrompt(messageType: String, template: String): Map<String, Any> {
val className = when (messageType.lowercase()) {
"system" -> "SystemMessagePromptTemplate"
"ai", "assistant" -> "AIMessagePromptTemplate"
"human", "user" -> "HumanMessagePromptTemplate"
else -> "HumanMessagePromptTemplate"
}
val stringPrompt = mutableMapOf<String, Any>(
"lc" to 1,
"type" to "prompt",
"id" to arrayOf("langchain", "prompts", "prompt", "StringPromptTemplate"),
"kwargs" to mapOf("template" to template)
)
return mapOf(
"lc" to 1,
"type" to "constructor",
"id" to arrayOf("langchain", "core", "prompts", "chat", className),
"kwargs" to mapOf("prompt" to stringPrompt)
)
}
/**
* Determines message type from the message object's id field.
*/
@Suppress("UNCHECKED_CAST")
private fun getMessageType(message: Map<String, Any>): String {
return runCatching {
val idObj = message["id"]
val className = when (idObj) {
is Array<*> -> idObj.lastOrNull()?.toString()
is List<*> -> idObj.lastOrNull()?.toString()
else -> null
}
when {
className?.contains("System") == true -> "system"
className?.contains("AI") == true || className?.contains("Assistant") == true -> "assistant"
else -> "user"
}
}.getOrDefault("user")
}
/**
* Finds a prompt by exact repo handle match.
*/
private fun findPrompt(client: LangsmithClient, promptName: String): java.util.Optional<RepoWithLookups> {
val existingRepos = client.repos()
.list(
RepoListParams.builder()
.query(promptName)
.isPublic(RepoListParams.IsPublic.FALSE)
.build()
)
return existingRepos.repos().stream()
.filter { it.repoHandle() == promptName }
.findFirst()
}
/**
* Checks if a prompt repository has a latest commit.
*/
private fun hasLatestCommit(client: LangsmithClient, promptName: String, owner: String): Boolean {
return runCatching {
client.commits().retrieve(
"latest",
CommitRetrieveParams.builder()
.owner(owner)
.repo(promptName)
.build()
)
}.isSuccess
}
/**
* Creates a commit with prompt content.
*
* @return true if commit was created, false otherwise
*/
private fun createCommit(
client: LangsmithClient,
promptName: String,
owner: String,
parentCommit: String?
): Boolean {
println("3. Adding prompt content using client.commits().update()...")
val manifest = createChatPromptWithMultipleMessages(
"You are a helpful assistant that tells jokes.",
"Tell me a joke about {topic}",
arrayOf("topic")
)
val params = CommitUpdateParams.builder()
.owner(owner)
.manifest(JsonValue.from(manifest))
.apply { parentCommit?.let { parentCommit(it) } }
.build()
return runCatching {
client.commits().update(promptName, params)
println(" ✓ Added prompt content as commit\n")
true
}.getOrElse { e ->
throw RuntimeException("Failed to create commit", e)
}
}
/**
* Extracts prompt content from a manifest for display (shows all messages).
*/
@Suppress("UNCHECKED_CAST")
private fun extractPromptContent(manifestJson: JsonValue): String {
return runCatching {
val manifest = MAPPER.convertValue(manifestJson, Map::class.java) as Map<String, Any>
val kwargs = manifest["kwargs"] as Map<String, Any>
val messagesObj = kwargs["messages"] ?: return "Unable to parse prompt content"
val messages = when (messagesObj) {
is Array<*> -> messagesObj
is List<*> -> messagesObj.toTypedArray()
else -> return "Unable to parse prompt content"
}
messages.mapIndexed { index, messageObj ->
val message = messageObj as Map<String, Any>
val msgKwargs = message["kwargs"] as Map<String, Any>
val prompt = msgKwargs["prompt"] as Map<String, Any>
val promptKwargs = prompt["kwargs"] as Map<String, Any>
val template = promptKwargs["template"].toString()
val messageType = getMessageType(message)
val prefix = if (index > 0) " | " else ""
"$prefix$messageType: \"$template\""
}.joinToString("")
}.getOrDefault("Unable to parse prompt content")
}
private fun getOwnerFromEnv(): String =
System.getenv("LANGSMITH_OWNER")?.takeIf { it.isNotEmpty() } ?: "-"