> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freeplay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Record Traces

> Record agent traces containing multiple LLM completions to Freeplay for observability.

### 1. Create client

### 2. Define a Call and Record Helper

### 3. Pass through Trace Info to Record

Pass trace info through on the record call to freeplay

### 4. Loop over questions and record to traces

### 5. Create a Trace Object

Create a Trace Object, including a user display input message

### 6. Close and Record the Trace

Record the Trace with the final display output to close the trace

## Examples

<CodeGroup>
  ```python Python theme={null}
  import os
  import random
  import time
  from typing import Optional

  from anthropic import Anthropic, NotGiven

  from freeplay import Freeplay, RecordPayload, CallInfo, SessionInfo, TraceInfo

  fp_client = Freeplay(
      freeplay_api_key=os.environ['FREEPLAY_API_KEY'],
      api_base=f"{os.environ['FREEPLAY_API_URL']}/api"
  )
  project_id = os.environ['FREEPLAY_PROJECT_ID']

  client = Anthropic(
      api_key=os.environ.get("ANTHROPIC_API_KEY")
  )


  def call_and_record(
          project_id: str,
          template_name: str,
          env: str,
          input_variables: dict,
          session_info: SessionInfo,
          trace_info: Optional[TraceInfo] = None
  ) -> dict:
      formatted_prompt = fp_client.prompts.get_formatted(
          project_id=project_id,
          template_name=template_name,
          environment=env,
          variables=input_variables
      )

      print(f"Ready for LLM: {formatted_prompt.llm_prompt}")

      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,
          **formatted_prompt.prompt_info.model_parameters
      )
      end = time.time()

      llm_response = completion.content[0].text
      print("Completion: %s" % llm_response)

      all_messages = formatted_prompt.all_messages(
          new_message={'role': 'assistant', 'content': llm_response}
      )
      call_info = CallInfo.from_prompt_info(formatted_prompt.prompt_info, start, end)
      record_response = fp_client.recordings.create(
        RecordPayload(
  						project_id=project_id,
              all_messages=all_messages,
              session_info=session_info,
              inputs=input_variables,
              prompt_version_info=formatted_prompt.prompt_info,
              call_info=call_info,
              trace_info=trace_info
          )
      )

      return {'completion_id': record_response.completion_id, 'llm_response': llm_response}


  # send 3 questions to the model encapsulated into a trace
  user_questions = ["answer life's most existential questions", "what is sand?", "how tall are lions?"]

  session = fp_client.sessions.create()
  for question in user_questions:
      trace_info = session.create_trace(input=question)
      bot_response = call_and_record(
          project_id=project_id,
          template_name='my-anthropic-prompt',
          env='latest',
          input_variables={'question': question},
          session_info=session.session_info,
          trace_info=trace_info
      )
      categorization_result = call_and_record(
          project_id=project_id,
          template_name='question-classifier',
          env='latest',
          input_variables={'question': question},
          session_info=session.session_info,
          trace_info=trace_info
      )

      trace_info.record_output(project_id, bot_response['llm_response'])
      print(f"Trace info id: {trace_info.trace_id}")
  ```

  ```javascript Node theme={null}
  import Freeplay, { getCallInfo, getSessionInfo } from "freeplay/thin";
  import Anthropic from "@anthropic-ai/sdk";

  const projectId = process.env["FREEPLAY_PROJECT_ID"];
  const environment = "latest";
  const templateName = "my-prompt-anthropic";

  const fpClient = new Freeplay({
    freeplayApiKey: process.env["FREEPLAY_API_KEY"],
    baseUrl: `${process.env["FREEPLAY_API_URL"]}/api`,
  });
  const anthropicClient = new Anthropic({
    apiKey: process.env["ANTHROPIC_API_KEY"],
  });

  async function call(
    projectId,
    templateName,
    environment,
    input_variables,
    session_info,
    trace_info
  ) {
    let formattedPrompt = await fpClient.prompts.getFormatted({
      projectId,
      templateName,
      environment,
      variables: input_variables,
    });

    let start = new Date();
    const llmResponse = await anthropicClient.messages.create({
      model: formattedPrompt.promptInfo.model,
      messages: formattedPrompt.llmPrompt,
      system: formattedPrompt.systemContent,
      ...formattedPrompt.promptInfo.modelParameters,
    });
    let end = new Date();

    const llmResponseText = llmResponse.content[0].text;

    let messages = formattedPrompt.allMessages({
      content: llmResponseText,
      role: "Assistant",
    });

    const completionResponse = await fpClient.recordings.create({
      projectId,
      allMessages: messages,
      inputs: input_variables,
      sessionInfo: session_info,
      promptVersionInfo: formattedPrompt.promptInfo,
      callInfo: getCallInfo(formattedPrompt.promptInfo, start, end),
      traceInfo: trace_info,
    });

    return {
      completionId: completionResponse.completionId,
      llmResponseText: llmResponseText,
    };
  }

  const userQuestion = "answer life's most existential questions";

  const session = await fpClient.sessions.create({
    customMetadata: { some_custom_metadata: 42 },
  });
  const traceInfo = await session.createTrace(userQuestion);
  const botResponse = await call(
    projectId,
    templateName,
    environment,
    { question: userQuestion },
    getSessionInfo(session),
    traceInfo
  );

  const categorizationResponse = await call(
    projectId,
    templateName,
    environment,
    { question: `categorize this question ${userQuestion}` },
    getSessionInfo(session),
    traceInfo
  );

  await traceInfo.recordOutput(projectId, botResponse.llmResponseText);
  console.log(
    `Trace recorded with Id ${traceInfo.traceId} and input "${traceInfo.input}" and output "${botResponse.llmResponseText}"`
  );
  ```

  ```java Java theme={null}
  package ai.freeplay.example.java;

  import ai.freeplay.client.thin.Freeplay;
  import ai.freeplay.client.thin.resources.prompts.ChatMessage;
  import ai.freeplay.client.thin.resources.prompts.FormattedPrompt;
  import ai.freeplay.client.thin.resources.recordings.CallInfo;
  import ai.freeplay.client.thin.resources.recordings.RecordInfo;
  import ai.freeplay.client.thin.resources.sessions.Session;
  import ai.freeplay.client.thin.resources.sessions.TraceInfo;
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;

  import java.net.http.HttpResponse;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.ExecutionException;

  import static ai.freeplay.client.thin.Freeplay.Config;
  import static ai.freeplay.example.java.ThinExampleUtils.callAnthropic;
  import static java.lang.String.format;

  public class ThinTrace {
      static String freeplayApiKey = System.getenv("FREEPLAY_API_KEY");
      static String projectId = System.getenv("FREEPLAY_PROJECT_ID");
      static String customerDomain = System.getenv("FREEPLAY_CUSTOMER_NAME");
      static String anthropicApiKey = System.getenv("ANTHROPIC_API_KEY");

      private static final ObjectMapper objectMapper = new ObjectMapper();

      static Freeplay fpClient = new Freeplay(Config()
              .freeplayAPIKey(freeplayApiKey)
              .customerDomain(customerDomain)
      );

      public static String call(
              String projectId,
              String templateName,
              String environment,
              Map<String, Object> variables,
              Session session,
              TraceInfo traceInfo
      ) {
          return fpClient.prompts()
                  .<List<ChatMessage>>getFormatted(
                          projectId,
                          templateName,
                          environment,
                          variables,
                          null
                  ).thenCompose((FormattedPrompt<List<ChatMessage>> formattedPrompt) -> {
                              long startTime = System.currentTimeMillis();
                              return callAnthropic(
                                      objectMapper,
                                      anthropicApiKey,
                                      formattedPrompt.getPromptInfo().getModel(),
                                      formattedPrompt.getPromptInfo().getModelParameters(),
                                      formattedPrompt.getFormattedPrompt(),
                                      formattedPrompt.getSystemContent().orElse(null)
                              ).thenApply((HttpResponse<String> response) ->
                                      new ThinExampleUtils.Tuple3<>(formattedPrompt, response, startTime)
                              );
                          }
                  ).thenCompose((ThinExampleUtils.Tuple3<FormattedPrompt<List<ChatMessage>>, HttpResponse<String>, Long> promptAndResponse) -> {
                              FormattedPrompt<List<ChatMessage>> formattedPrompt = promptAndResponse.first;
                              HttpResponse<String> response = promptAndResponse.second;
                              long startTime = promptAndResponse.third;

                              JsonNode bodyNode;
                              try {
                                  bodyNode = objectMapper.readTree(response.body());
                              } catch (JsonProcessingException e) {
                                  throw new RuntimeException("Unable to parse response body.", e);
                              }

                              List<ChatMessage> allMessages = formattedPrompt.allMessages(
                                      new ChatMessage("assistant", bodyNode.path("content").get(0).path("text").asText())
                              );

                              CallInfo callInfo = CallInfo.from(
                                      formattedPrompt.getPromptInfo(),
                                      startTime,
                                      System.currentTimeMillis()
                              );
                              String output = bodyNode.path("content").get(0).path("text").asText();
                              System.out.println("Completion: " + output);

                              RecordInfo recordInfo = new RecordInfo(
                                              projectId,
                                              allMessages
                                      ).inputs(variables)
                                              .sessionInfo(sessionInfo)
                                              .promptVersionInfo(formattedPrompt.getPromptInfo())
                                              .callInfo(callInfo)
                                              .traceInfo(traceInfo));
                              recordInfo.traceInfo(traceInfo);

                              fpClient.recordings().create(recordInfo);

                              return CompletableFuture.completedFuture(output);
                          }
                  )
                  .exceptionally(exception -> {
                      System.out.println("Got exception: " + exception.getMessage());
                      return null;
                  })
                  .join();
      }
      public static void main(String[] args) throws ExecutionException, InterruptedException{
          String input = "What is the meaning of life?";
          Map<String, Object> inputVars = Map.of("question", input);

          Session session = fpClient.sessions().create();
          TraceInfo traceInfo = session.createTrace(input);

          String response = call(
                  projectId,
                  "my-anthropic-prompt",
                  "latest",
                  inputVars,
                  session,
                  traceInfo
          );
          System.out.println("First Completion: " + response);

          Map<String, Object> inputVars2 = Map.of("question", format("categorize the following question: %s", input));
          String category = call(
                  projectId,
                  "my-anthropic-prompt",
                  "latest",
                  inputVars2,
                  session,
                  traceInfo
          );
          traceInfo.recordOutput(projectId, response);
          System.out.println("Second Completion: " + category);

          System.out.println("Recorded Trace " + traceInfo.traceId + " to session " + traceInfo.sessionId + " with input " + traceInfo.input + " and output " + response);
      }
  }
  ```
</CodeGroup>
