1. Setup clients
Initialize Freeplay and Anthropic client SDKs.2. Fetch prompt from Freeplay
Pull in the formatted prompt with Freeplay. The prompt contains tools schema that’ll we’ll pass down below.3. Call Anthropic with the tools
When creating a new completion, pass in the tools schema from the prompt we fetched.4. Handle tool call
When LLM responds back with a tool call, call the external function in your service. As an example here, we are callingget_temperature function
5. Record tool call and schema
Pass in the schema and completion response to capture the tool call and its associated schema.Examples
import os
import time
import json
from anthropic import Anthropic, NotGiven
from anthropic.types import ToolUseBlock
from freeplay import Freeplay, RecordPayload, CallInfo
# A mock function that gets temperature for a location
def get_temperature(location: str) -> float:
return 72.5
project_id=os.environ['FREEPLAY_PROJECT_ID']
fp_client = Freeplay(
freeplay_api_key=os.environ['FREEPLAY_API_KEY'],
api_base=f"{os.environ['FREEPLAY_API_URL']}/api"
)
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
input_variables = {'location': "Boulder, CO"}
formatted_prompt = fp_client.prompts.get_formatted(
project_id=project_id,
template_name='my-anthropic-prompt',
environment='latest',
variables=input_variables
)
start = time.time()
completion = client.messages.create(
system=formatted_prompt.system_content or NotGiven(),
messages=formatted_prompt.llm_prompt,
model=formatted_prompt.prompt_info.model,
tools=formatted_prompt.tool_schema,
**formatted_prompt.prompt_info.model_parameters
)
end = time.time()
# Get all messages including the completion
messages = formatted_prompt.all_messages({
'content': completion.content,
'role': completion.role,
})
# Handle tool calls if present
if isinstance(completion.content, list):
for block in completion.content:
if isinstance(block, ToolUseBlock) and block.name == "weather_of_location":
temperature = get_temperature(block.input["location"])
tool_response_message = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(temperature),
}
]
}
messages.append(tool_response_message)
session = fp_client.sessions.create()
fp_client.recordings.create(
RecordPayload(
project_id=project_id,
all_messages=messages,
session_info=session.session_info,
inputs=input_variables,
prompt_version_info=formatted_prompt.prompt_info,
call_info=CallInfo.from_prompt_info(formatted_prompt.prompt_info, start, end),
tool_schema=formatted_prompt.tool_schema
)
)
import Anthropic from "@anthropic-ai/sdk";
import Freeplay, { getSessionInfo, getCallInfo } from "freeplay";
// A mock function that gets temperature for a location
function getTemperature(location) {
return 72.5;
}
const fp_client = new Freeplay({
freeplayApiKey: process.env.FREEPLAY_API_KEY,
baseUrl: `${process.env.FREEPLAY_API_URL}/api`,
});
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const inputVariables = { location: "Boulder, CO" };
const formattedPrompt = await fp_client.prompts.getFormatted({
projectId: process.env.FREEPLAY_PROJECT_ID,
templateName: "my-anthropic-prompt",
environment: "latest",
variables: inputVariables,
});
const start = new Date();
const completion = await client.messages.create({
system: formattedPrompt.systemContent || undefined,
messages: formattedPrompt.llmPrompt,
model: formattedPrompt.promptInfo.model,
tools: formattedPrompt.toolSchema,
...formattedPrompt.promptInfo.modelParameters,
});
const end = new Date();
// Get all messages including the completion
const messages = formattedPrompt.allMessages({
content: completion.content,
role: completion.role,
});
// Handle tool calls if present
if (Array.isArray(completion.content)) {
for (const block of completion.content) {
if (block.type === "tool_use" && block.name === "weather_of_location") {
const temperature = getTemperature(block.input.location);
const toolResponseMessage = {
role: "user",
content: [
{
type: "tool_result",
tool_use_id: block.id,
content: temperature.toString(),
},
],
};
messages.push(toolResponseMessage);
}
}
}
const session = fp_client.sessions.create();
await fp_client.recordings.create({
allMessages: messages,
sessionInfo: getSessionInfo(session),
inputs: inputVariables,
promptInfo: formattedPrompt.promptInfo,
callInfo: getCallInfo(formattedPrompt.promptInfo, start, end),
toolSchema: formattedPrompt.toolSchema
});
package ai.freeplay.example.kotlin
import ai.freeplay.client.thin.Freeplay
import ai.freeplay.client.thin.resources.prompts.ChatMessage
import ai.freeplay.client.thin.resources.recordings.*
import com.fasterxml.jackson.databind.ObjectMapper
import ai.freeplay.example.java.ThinExampleUtils.callAnthropic
object AnthropicToolsExample {
private val objectMapper = ObjectMapper()
// Mock weather function
private fun getTemperature(location: String): Double = 72.5
@JvmStatic
fun main(args: Array<String>) {
val freeplayApiKey = System.getenv("FREEPLAY_API_KEY")
val projectId = System.getenv("FREEPLAY_PROJECT_ID")
val apiRoot = System.getenv("FREEPLAY_API_URL")
val baseUrl = "${apiRoot}/api"
val anthropicApiKey = System.getenv("ANTHROPIC_API_KEY")
val fpClient = Freeplay(
Freeplay.Config()
.freeplayAPIKey(freeplayApiKey)
.baseUrl(baseUrl)
)
val variables = mapOf("location" to "Boulder, CO")
fpClient.prompts()
.getFormatted<List<ChatMessage>>(
projectId,
"my-anthropic-prompt",
"latest",
variables,
null
).thenCompose { formattedPrompt ->
val startTime = System.currentTimeMillis()
callAnthropic(
objectMapper,
anthropicApiKey,
formattedPrompt.promptInfo.model,
formattedPrompt.promptInfo.modelParameters,
formattedPrompt.formattedPrompt,
formattedPrompt.systemContent.orElse(null),
formattedPrompt.toolSchema
).thenApply { Triple(formattedPrompt, it, startTime) }
}.thenCompose { (formattedPrompt, response, startTime) ->
try {
val bodyNode = objectMapper.readTree(response.body())
val contentNode = bodyNode.get("content")
// Create initial message list
val allMessages = formattedPrompt.allMessages(
ChatMessage("assistant", objectMapper.convertValue(contentNode, List::class.java))
).toMutableList()
// Handle tool calls
if (contentNode.isArray) {
contentNode.forEach { block ->
if (block["type"]?.asText() == "tool_use" &&
block["name"]?.asText() == "weather_of_location") {
val input = block["input"]
val temperature = getTemperature(input["location"].asText())
val toolResponse = mapOf(
"role" to "user",
"content" to listOf(
mapOf(
"type" to "tool_result",
"content" to temperature.toString(),
"tool_use_id" to block["id"].asText()
)
)
)
allMessages.add(ChatMessage(toolResponse))
}
}
}
val callInfo = CallInfo.from(
formattedPrompt.promptInfo,
startTime,
System.currentTimeMillis()
)
val sessionInfo = fpClient.sessions().create().sessionInfo
// Print the completion for debugging
if (contentNode.isArray && contentNode.size() > 0) {
contentNode[0]?.get("text")?.asText()?.let { text ->
println("Completion: $text")
}
}
val record = RecordInfo(
projectId,
allMessages
).inputs(variables)
.sessionInfo(session.sessionInfo)
.promptVersionInfo(prompt.promptInfo)
.callInfo(callInfo)
.traceInfo(trace)
.toolSchema(formattedPrompt.toolSchema)
)
fpClient.recordings().create(record)
} catch (e: Exception) {
throw RuntimeException("Failed to process JSON response", e)
}
}
.exceptionally { exception ->
System.err.println("Error: ${exception.message}")
exception.printStackTrace()
RecordResponse(null)
}
.join()
}
}

