> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://humanloop.com/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://humanloop.com/docs/_mcp/server.

> Use Humanloop to add logging to an AI project.

This quickstart takes a chat agent and adds Humanloop logging to it so you can observe and reason about its behavior.

## Prerequisites

#### Account setup

Create a Humanloop Account

If you haven't already, [create an account](https://app.humanloop.com/signup) or [log in](https://app.humanloop.com/login) to Humanloop

Add an OpenAI API Key

If you're the first person in your organization, you'll need to add an API key to a model provider.

1. Go to OpenAI and [grab an API key](https://platform.openai.com/api-keys).
2. In Humanloop [Organization Settings](https://app.humanloop.com/account/api-keys) set up OpenAI as a model provider.

> **Info**
>
> Using the Prompt Editor will use your OpenAI credits in the same way that the
> OpenAI playground does. Keep your API keys for Humanloop and the model
> providers private.

#### Install dependencies

```python
pip install humanloop openai
```

```typescript
npm install humanloop openai
```

## Create the chat agent

To demonstrate how to add logging, we will start with a chat agent that answers math and science questions.

Create a script and add the following:

```python maxLines=30
import json

from humanloop import Humanloop
from openai import OpenAI

openai = OpenAI(api_key="<YOUR_OPENAI_KEY>")
humanloop = Humanloop(api_key="<YOUR_HUMANLOOP_KEY>")


def calculator(operation: str, num1: int, num2: int) -> str:
    """Do arithmetic operations on two numbers."""
    if operation == "add":
        return num1 + num2
    elif operation == "subtract":
        return num1 - num2
    elif operation == "multiply":
        return num1 * num2
    elif operation == "divide":
        return num1 / num2
    else:
        return "Invalid operation"


def call_model(messages: list[str]) -> str:
    output = openai.chat.completions.create(
        messages=messages,
        model="gpt-4o-mini",
        tools=[{
            "type": "function",
            "function": {
                'name': 'calculator',
                'description': 'Do arithmetic operations on two numbers.',
                'parameters': {
                    'type': 'object',
                    'required': ['operation', 'num1', 'num2'],
                    'properties': {
                        'operation': {'type': 'string'},
                        'num1': {'type': 'integer'},
                        'num2': {'type': 'integer'}
                    },
                    'additionalProperties': False
                }, 
            },
        }],
        temperature=0.7,
    )

    # Check if model asked for a tool call
    if output.choices[0].message.tool_calls:
        for tool_call in output.choices[0].message.tool_calls:
            arguments = json.loads(tool_call.function.arguments)
            if tool_call.function.name == "calculator":
                result = calculator(**arguments)
                return f"[TOOL CALL] {result}"

    # Otherwise, return the LLM response
    return output.choices[0].message.content


def conversation():
    messages = [
        {
            "role": "system",
            "content": "You are a a groovy 80s surfer dude "
            "helping with math and science."
        },
    ]
    while True:
        user_input = input("You: ")
        if user_input == "exit":
            break
        messages.append({"role": "user", "content": user_input})
        response = call_model(messages=messages)
        messages.append({"role": "assistant", "content": response})
        print(f"Agent: {response}")


if __name__ == "__main__":
    conversation()
```

```typescript maxLines=30
import * as readline from "readline/promises";

import { HumanloopClient } from "humanloop";
import OpenAI from "openai";

type MessageType = {
  content: string;
  role: "system" | "user" | "assistant";
};

const humanloop = new HumanloopClient({apiKey: "<YOUR_HUMANLOOP_KEY>"});
const openAIClient = new OpenAI({apiKey: "<YOUR_OPENAI_KEY>"});

// Passed to the LLM to enable function calling
const CALCULATOR_JSON_SCHEMA = {
  name: "calculator",
  description: "Perform arithmetic operations on two numbers",
  strict: true,
  parameters: {
    type: "object",
    properties: {
      operation: {
        type: "string",
        description: "The operation to perform",
        enum: ["add", "subtract", "multiply", "divide"],
      },
      num1: {
        type: "number",
        description: "The first number",
      },
      num2: {
        type: "number",
        description: "The second number",
      },
    },
    required: ["operation", "num1", "num2"],
    additionalProperties: false,
  },
};

const calculator = ({
  operation, num1, num2
}: {
  operation: string;
  num1: number;
  num2: number;
}) => {
  switch (operation) {
    case "add":
      return num1 + num2;
    case "subtract":
      return num1 - num2;
    case "multiply":
      return num1 * num2;
    case "divide":
      if (num2 === 0) {
        throw new Error("Cannot divide by zero");
      }
      return num1 / num2;
    default:
      throw new Error("Invalid operation");
  }
};

const callModel = async (traceId: string, messages: MessageType[]) => {
  const output = await openAIClient.chat.completions.create({
    messages: messages,
    model: "gpt-4o-mini",
    temperature: 0.8,
    tools: [
      {
        type: "function",
        function: CALCULATOR_JSON_SCHEMA,
      } as OpenAI.ChatCompletionTool,
    ],
  });

  let llmResponse = "";

  // Check if the agent made a tool call
  if (output.choices[0].message.tool_calls) {
    for (const toolCall of output.choices[0].message.tool_calls) {
      const toolCallArgs = JSON.parse(toolCall.function.arguments);
      const result = calculator(toolCallArgs);
      // Log the tool call
      humanloop.tools.log({
        path: "Chat Agent/Calculator",
        inputs: toolCallArgs,
        output: JSON.stringify(result),
        traceParentId: traceId,
      });
      llmResponse = `[${toolCall.function.name}] ${result}`;
    }
  } else {
    llmResponse = output.choices[0].message.content || "";
  }

  // Log the model call
  await humanloop.prompts.log({
    path: "Chat Agent/Call Model",
    prompt: {
      model: "gpt-4o",
      temperature: 0.8,
      tools: [CALCULATOR_JSON_SCHEMA],
    },
    traceParentId: traceId,
    messages: [...messages, { role: "assistant", content: llmResponse }],
  });

  return llmResponse;
};

const conversation = async () => {
  const messages: MessageType[] = [
    {
      role: "system",
      content:
        "You are a groovy 80s surfer dude helping with math and science.",
    },
  ];
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  // Create the Flow trace
  // Each conversation will have a unique trace
  const traceId = (
    await humanloop.flows.log({
      path: "Chat Agent/Conversation",
      startTime: new Date(),
    })
  ).id;
  while (true) {
    let userInput = await rl.question("You: ");
    if (userInput === "exit") {
      rl.close();
      break;
    }
    messages.push({ role: "user", content: userInput });

    const response = await callModel(traceId, messages);
    console.log("Assistant:", response);

    messages.push({
      role: "assistant",
      content: response,
    });
  }
  //   Close the Flow trace when the conversation is done
  await humanloop.flows.updateLog(traceId, {
    traceStatus: "complete",
    output: JSON.stringify(messages),
  });
};

await conversation();
```

## Log to Humanloop

> **Info**
>
> If you use a programming language not supported by the SDK, or want more control, see our guide on [logging through the API](/docs/v5/guides/observability/logging-through-api) for an alternative to decorators.

Use the SDK decorators to enable logging. At runtime, every call to a decorated function will create a [Log](/docs/v5/explanations/logs) on Humanloop.

```python maxLines=50 highlight={1,5,10}
@humanloop.tool(path="Logging Quickstart/Calculator")
def calculator(operation: str, num1: int, num2: int) -> str:
    ...

@humanloop.prompt(path="Logging Quickstart/QA Prompt")
def call_model(messages: list[str]) -> str:
    ...


@humanloop.flow(path="Logging Quickstart/QA Agent")
def conversation():
    ...

if __name__ == "__main__":
    conversation()
```

```typescript maxLines=50 highlight={1-12, 17-20, 25-28}
const calculator = humanloop.tool({
  path: "Chat Agent/Calculator",
  version: {function: CALCULATOR_JSON_SCHEMA},    
  callable: ({
    operation,
    num1,
    num2,
  }: {
    operation: string;
    num1: number;
    num2: number;
  }) => {
    ...
  },
});

const callModel = (messages: MessageType[]) =>
  humanloop.prompt({
    path: "Chat Agent/Call Model",
    callable: async (inputs: any, messages: MessageType[]) => {
      ...
    },
  })(undefined, messages);

const conversation = () =>
  humanloop.flow({
    path: "Chat Agent/Conversation",
    callable: async () => {
      ...
    },
})(undefined, undefined);
```

## Run the code

Have a conversation with the agent. When you're done, type `exit` to close the program.

```python
> python main.py
You: Hi dude!
Agent: Tubular! I am here to help with math and science, what is groovin?
You: How does flying work?
Agent: ...
You: What is 5678 * 456?
Agent: [TOOL CALL] 2587968
You: exit
```

```typescript
> npx tsx index.ts
You: Hi dude!
Agent: Tubular! I am here to help with math and science, what is groovin?
You: How does flying work?
Agent: ...
You: What is 5678 * 456?
Agent: [TOOL CALL] 2587968
You: exit
```

## Check your workspace

Navigate to [your workspace](https://app.humanloop.com) to see the logged conversation.

Inside the **Logging Quickstart** directory on the left, click the **QA Agent** [Flow](/docs/v5/explanation/flows). Select the **Logs** tab from the top of the page and click the Log inside the table.

You will see the conversation's trace, containing Logs corresponding to the [Tool](/docs/v5/explanation/tools) and the [Prompt](/docs/v5/explanation/prompts).

![](/docs/_fern-img/5cda056d966bab0861d9d8d5a22f524426023a24052b8ee419da0a0c6fa6c035.webp)

## Change the agent and rerun

Modify the `call_model` function to use a different model and temperature.

```python maxLines=30 highlight={6-11}
@humanloop.prompt(path="Logging Quickstart/QA Prompt")
def call_model(messages: list[str]) -> str:
    output = openai.chat.completions.create(
        messages=messages,
        model="gpt-4o-mini",
        tools=[
            # The @tool utility adds a .json_schema attribute
            # to avoid redefining the schema
            calculator.json_schema
        ],
        temperature=0.2,
    )

    # Check if model asked for a tool call
    if output.choices[0].message.tool_calls:
        for tool_call in output.choices[0].message.tool_calls:
            arguments = json.loads(tool_call.function.arguments)
            if tool_call.function.name == "calculator":
                result = calculator(**arguments)
                return f"[TOOL CALL] {result}"

    # Otherwise, return the LLM response
    return output.choices[0].message.content
```

```typescript maxLines=30 highlight={8-14}
const callModel = (messages: MessageType[]) =>
  humanloop.prompt({
    path: "Chat Agent/Call Model",
    callable: async (inputs: any, messages: MessageType[]) => {
      const output = await openAIClient.chat.completions.create({
        messages: messages,
        model: "gpt-4o-mini",
        tools: [
            // The tool utility adds a .jsonSchema attribute
            // to avoid redefining the schema
            {
                type: "function",
            function: calculator.jsonSchema,
          } as OpenAI.ChatCompletionTool,
        ],
        temperature: 0.2,
      });

      let llmResponse = "";

      // Check if the agent made a tool call
      if (output.choices[0].message.tool_calls) {
        for (const toolCall of output.choices[0].message.tool_calls) {
          const toolCallArgs = JSON.parse(toolCall.function.arguments);
          const result = await calculator(toolCallArgs);
          llmResponse = `[${toolCall.function.name}] ${result}`;
        }
      } else {
        llmResponse = output.choices[0].message.content || "";
      }

      return llmResponse;
    },
  })(undefined, messages);
```

Run the agent again, then head back to your workspace.

Click the **QA Prompt** [Prompt](/docs/v5/explanation/prompts), select the **Dashboard** tab from the top of the page
and you should find a new version of the Prompt at the top of the list.

By changing the hyperparameters of the OpenAI call, you have tagged a new version of the Prompt.

![](/docs/_fern-img/2293bc119e4fc55e04565a772bdcee9b47a2471003f72bf0f69fae4db064052d.webp)

## Next steps

Logging is the first step to observing your AI product. Follow up with these guides on monitoring and evals:

* Add [monitoring Evaluators](/docs/v5/guides/observability/monitoring) to evaluate Logs as they're made against a File.

* Explore evals to improve the performance of your AI feature in our [guide on running an Evaluation](/docs/v5/guides/evals/run-evaluation).

* See logging in action on a complex example in our [tutorial on evaluating an agent](/docs/v5/tutorials/agent-evaluation).