# How do you build a simple agent loop with the OpenAI API?

Date: 2023-05-03 · Author: Robert Scales · https://guidetoartificialintelligence.com/en/learn/build-simple-agent-loop-openai-api/

**Answer:** An agent loop asks the model to write a thought and one action in a fixed text format, your program reads that action, runs the matching tool, and sends the result back as the next message. You repeat think, act and observe until the model writes a final answer, with a hard limit on steps and on tokens so it can never run away.

Projects like Auto-GPT look complicated, but their core is a short loop you can write yourself in about forty lines of Python. Building one is the best way to see what an agent really is: not a new kind of model, but ordinary chat requests wired together by your own code, with your code deciding what actually happens in the world.

The pattern comes from a 2022 research paper called ReAct, short for "reasoning and acting." Its authors had a language model alternate between writing out its reasoning and taking an action, such as a lookup through a simple Wikipedia tool, and then reading what came back. Checking an outside source this way reduced made-up facts compared with reasoning alone.

## The three moves: think, act, observe

Every pass through the loop has three parts.

1. **Think.** The model writes a short line of reasoning about what to do next.
2. **Act.** It names one tool and its input, in a format your program can read.
3. **Observe.** Your program runs the tool and sends the result back as a new message.

The model never runs anything itself. The ChatGPT API only returns text. Today it has no built-in way to call your functions, so the "action" is just a line of text that your code searches for with a regular expression (a pattern-matching rule for text) and then decides whether to obey.

## The chat format you build on

Since March 1, OpenAI's API offers gpt-3.5-turbo, the model behind ChatGPT, through a chat completions endpoint. Instead of one block of text, you send a list of messages, each with a role (system, user or assistant) and its content. The reply comes back with the model's message, a finish reason and a usage count of tokens, the small pieces of text the API bills by. At $0.002 per 1,000 tokens, it is cheap enough to experiment with.

One practical note from OpenAI's own guide: the current gpt-3.5-turbo snapshot doesn't pay much attention to the system message, so important instructions are better placed in a user message. The example below does exactly that.

## A worked example in Python

This agent answers a small budgeting question using two toy tools: a price lookup and an adder. The prices are made up for illustration. It uses the `openai` Python library (version 0.27) and reads your API key from an environment variable.

```python
import os, re, openai
openai.api_key = os.getenv("OPENAI_API_KEY")

PRICES = {"train": 89, "hotel": 145, "museum": 22}   # illustrative prices

def lookup(item):
    return str(PRICES.get(item.strip().lower(), "unknown item"))

def add(numbers):
    try:
        return str(sum(float(n) for n in numbers.split(",")))
    except ValueError:
        return "Error: add needs numbers separated by commas"

TOOLS = {"lookup": lookup, "add": add}

INSTRUCTIONS = """Solve the task one step at a time. Tools:
lookup[item]   returns the price of one item
add[n1, n2]    returns the sum of the numbers
Reply in exactly this format, one action per reply:
THOUGHT: your reasoning
ACTION: tool[input]
When you know the answer, reply:
THOUGHT: your reasoning
FINAL: your answer"""

MAX_STEPS = 6          # hard limit on loop passes
TOKEN_BUDGET = 6000    # hard limit on total tokens billed

def run(task):
    messages = [{"role": "user", "content": INSTRUCTIONS + "\n\nTask: " + task}]
    used = 0
    for step in range(1, MAX_STEPS + 1):
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=messages,
            temperature=0,
            max_tokens=200,
            stop=["OBSERVATION:"],
        )
        used += response["usage"]["total_tokens"]
        reply = response["choices"][0]["message"]["content"]
        print(f"--- step {step} ---\n{reply}")
        messages.append({"role": "assistant", "content": reply})

        if "FINAL:" in reply:
            return reply.split("FINAL:", 1)[1].strip()

        match = re.search(r"ACTION:\s*(\w+)\[(.*?)\]", reply)
        if match and match.group(1) in TOOLS:
            observation = TOOLS[match.group(1)](match.group(2))
        else:
            observation = "Error: reply with one listed tool as tool[input]."
        messages.append({"role": "user", "content": "OBSERVATION: " + observation})

        if used > TOKEN_BUDGET:
            return f"Stopped: token budget used after {step} steps."
    return "Stopped: step limit reached without a final answer."

print(run("What do a train ticket, one hotel night and a museum pass cost together?"))
```

A typical run, shown here as an illustration, looks up the three prices one at a time, calls `add[89, 145, 22]`, gets 256.0 back and replies with a final answer of $256. That is five model calls for one question.

A few lines do quiet but important work. `temperature=0` makes replies more predictable, which helps the format hold. The `stop` parameter cuts the reply off if the model starts writing its own "OBSERVATION:", so it cannot invent a tool result. And unknown or malformed actions get an error message back instead of crashing the program, which gives the model a chance to correct itself.

> **Try it:** Run the script, then add a third tool, such as `convert[amount]` that turns dollars into euros at a fixed rate you choose. Update the instructions, ask a question that needs it, and read the printed steps to see whether the model uses the new tool correctly.

## How do you stop it from looping forever?

Use several brakes, because each one catches a different failure. The step limit (`MAX_STEPS`) is the simplest: no matter what the model writes, the `for` loop ends. The token budget catches the less obvious cost problem. Every call resends the whole conversation, so each step costs more than the one before; counting `usage` after each call keeps the total under a ceiling you set. On top of that, set a monthly spending limit in your OpenAI account settings so that a bug in your own code can't run up a surprise bill.

Then add checks for the model going in circles. If it requests the same action with the same input twice in a row, you can stop or reply with an observation such as "You already tried that; choose a different step." If it keeps breaking the format, end the run after two or three errors rather than letting it keep trying.

Finally, keep a person in the loop for anything with real consequences. Read-only tools like a price lookup are safe to automate. A tool that sends email, deletes files or spends money should pause and ask you to approve the action first, the same caution Auto-GPT applies by default. The loop is yours, so the rules are too.

## Sources

- How to format inputs to ChatGPT models: https://github.com/openai/openai-cookbook/blob/8cd6b0f53b9482f03dca3ec7988a4b2d639850fd/examples/How_to_format_inputs_to_ChatGPT_models.ipynb
- ReAct: Synergizing Reasoning and Acting in Language Models: https://arxiv.org/abs/2210.03629v1
- Introducing ChatGPT and Whisper APIs: https://openai.com/blog/introducing-chatgpt-and-whisper-apis
