> ## 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.

# Structured Outputs

> Use structured outputs to get typed responses from LLMs with Freeplay.

### 1. Structred outputs

This shows how to use structured outputs with Freeplay's SDK

## Examples

<CodeGroup>
  ```python Python theme={null}
  import os
  import time
  from typing import List
  import pydantic
  from openai import OpenAI
  from freeplay import Freeplay, RecordPayload, CallInfo
  from freeplay.resources.recordings import UsageTokens

  # Define structured output classes with Pydantic
  class COTStep(pydantic.BaseModel):
      thinking: str
      result: str

  class COTResponse(pydantic.BaseModel):
      response: str
      steps: List[COTStep]

  # Initialize clients
  fp_client = Freeplay(
      freeplay_api_key=os.environ["FREEPLAY_API_KEY"],
      api_base=f"{os.environ['FREEPLAY_API_URL']}/api",
  )
  client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

  input_variables = {"question": "why is the sky blue?"}
  project_id = os.environ["FREEPLAY_PROJECT_ID"]

  # Fetch formatted prompt with output schema
  formatted_prompt = fp_client.prompts.get_formatted(
      project_id=project_id,
      template_name="my-chat-template",
      environment="latest",
      variables=input_variables,
  )

  print(f"Tool schema: {formatted_prompt.tool_schema}")
  print(f"Output schema: {formatted_prompt.formatted_output_schema}")

  start = time.time()

  # Build the completion parameters
  completion_params = {
      **formatted_prompt.prompt_info.model_parameters,
  }

  # Add tools if present
  if formatted_prompt.tool_schema:
      completion_params["tools"] = formatted_prompt.tool_schema

  # Use the output schema from the prompt template
  completion = client.chat.completions.create(
      messages=formatted_prompt.llm_prompt,
      model=formatted_prompt.prompt_info.model,
      response_format={
          "type": "json_schema",
          "json_schema": {
              "strict": True,
              "schema": COTResponse.model_json_schema(), # OR you can use formatted_prompt.formatted_output_schema,
              "name": "COTReasoning",
          },
      }
      if formatted_prompt.formatted_output_schema
      else openai.NotGiven(),
  )

  end = time.time()
  print("Completion with prompt schema: %s" % completion)

  # Record to Freeplay
  session = fp_client.sessions.create()
  messages = formatted_prompt.all_messages(completion.choices[0].message)
  call_info = CallInfo.from_prompt_info(
      formatted_prompt.prompt_info,
      start,
      end,
      UsageTokens(completion.usage.prompt_tokens, completion.usage.completion_tokens),
      api_style="batch",
  )

  record_response = 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=call_info,
          tool_schema=formatted_prompt.tool_schema,
          output_schema=COTResponse.model_json_schema()  # OR you can use formatted_prompt.formatted_output_schema
      )
  )
  ```

  ```javascript JavaScript theme={null}
  import OpenAI from "openai";
  import { z } from "zod";
  import Freeplay, { getSessionInfo } from "@freeplay/freeplay";

  // Define structured output schemas using Zod
  const COTStepSchema = z.object({
      thinking: z.string(),
      result: z.string(),
  });

  const COTResponseSchema = z.object({
      response: z.string(),
      steps: z.array(COTStepSchema),
  });

  async function main() {
      const fpClient = new Freeplay({
          freeplayApiKey: process.env["FREEPLAY_API_KEY"],
          baseUrl: `${process.env["FREEPLAY_API_URL"]}/api`,
      });

      const openaiClient = new OpenAI({
          apiKey: process.env["OPENAI_API_KEY"],
      });

      const inputVariables = { question: "why is the sky blue?" };
      const projectId = process.env["FREEPLAY_PROJECT_ID"];

      // Fetch formatted prompt with output schema
      const formattedPrompt = await fpClient.prompts.getFormatted({
          projectId,
          templateName: "my-chat-template",
          environment: "latest",
          variables: inputVariables,
      });

      console.log("Tool schema:", formattedPrompt.toolSchema);
      console.log("Output schema:", formattedPrompt.outputSchema);

      const start = new Date();

      // Build the completion parameters
      const completionParams: OpenAI.ChatCompletionCreateParams = {
          messages: (formattedPrompt.llmPrompt || []) as OpenAI.ChatCompletionMessageParam[],
          model: formattedPrompt.promptInfo.model,
          ...formattedPrompt.promptInfo.modelParameters,
      };

      // Add tools if present
      if (formattedPrompt.toolSchema) {
          completionParams.tools = formattedPrompt.toolSchema;
      }

      // Use output schema from prompt template, or fall back to Zod schema
      let completion: OpenAI.ChatCompletion;
      let jsonSchema: any = undefined;

      if (formattedPrompt.outputSchema) {
          // Use schema from prompt template
          completion = await openaiClient.chat.completions.create({
              ...completionParams,
              response_format: {
                  type: "json_schema",
                  json_schema: {
                      strict: true,
                      schema: formattedPrompt.outputSchema,
                      name: "COTReasoning",
                  },
              },
          });
          console.log("Completion with prompt schema:", completion);
      } else {
          // Alternatively, use a Zod schema directly with Zod 4's native toJSONSchema()
          jsonSchema = z.toJSONSchema(COTResponseSchema);

          completion = await openaiClient.chat.completions.create({
              ...completionParams,
              response_format: {
                  type: "json_schema",
                  json_schema: {
                      strict: true,
                      schema: jsonSchema,
                      name: "COTReasoning",
                  },
              },
          });
          console.log("Completion with Zod schema:", completion);
      }

      const end = new Date();

      // Record to Freeplay
      const session = fpClient.sessions.create();
      const messages = formattedPrompt.allMessages(completion.choices[0].message);

      await fpClient.recordings.create({
          projectId,
          allMessages: messages,
          sessionInfo: getSessionInfo(session),
          inputs: inputVariables,
          promptVersionInfo: formattedPrompt.promptInfo,
          callInfo: {
              provider: formattedPrompt.promptInfo.provider,
              model: formattedPrompt.promptInfo.model,
              startTime: start,
              endTime: end,
              modelParameters: formattedPrompt.promptInfo.modelParameters,
              usage: completion.usage
                  ? {
                      promptTokens: completion.usage.prompt_tokens,
                      completionTokens: completion.usage.completion_tokens,
                  }
                  : undefined,
          },
          toolSchema: formattedPrompt.toolSchema,
          outputSchema: formattedPrompt.outputSchema || jsonSchema
      });

      console.log("Recording created successfully");
  }

  main().catch(console.error);
  ```
</CodeGroup>
