{"slug": "a-simple-python-implementation-of-the-react-pattern-for-llms", "title": "A simple Python implementation of the ReAct pattern for LLMs", "summary": "Simon Willison published a from-scratch Python implementation of the ReAct (Reason+Act) pattern on top of his tiny ChatGPT API wrapper, giving the LLM three callable actions: `wikipedia: <search term>`, `simon_blog_search: <search term>`, and `calculate: <expression>`, the last evaluated with Python's `eval()` — which Willison flagged as \"so dangerous!\" and said should use a safer mechanism. In worked examples, the model answered \"England shares borders with Wales and Scotland\" via a Wikipedia lookup and resolved \"Fifteen * twenty five\" by requesting the calculate action. Willison built the implementation himself rather than using langchain, which Matt Webb had used for his version of the loop.", "body_md": "A popular nightmare scenario for AI is giving it access to tools, so it can make API calls and execute its own code and generally break free of the constraints of its initial environment.\n\nLet's do that now!\n\nThe ReAct pattern (for Reason+Act) is [described in this paper](https://react-lm.github.io/). It's a pattern where you implement additional actions that an LLM can take - searching Wikipedia or running calculations for example - and then teach it how to request that those actions are run, then feed their results back into the LLM.\n\nSee also my post [Could you train a ChatGPT-beating model for $85,000 and run it in a browser?](https://simonwillison.net/2023/Mar/17/beat-chatgpt-in-a-browser/#react-pattern).\n\nMatt Webb wrote a great piece about that here: [The surprising ease and effectiveness of AI in a loop](https://interconnected.org/home/2023/03/16/singularity). In it Matt points out that the 1/10 price reduction on ChatGPT compared to GPT-3 makes it an ideal candidate for cheaply implementing this pattern.\n\nMatt used [langchain](https://github.com/hwchase17/langchain) for his implementation. I decided to try building it from scratch on top of my [tiny Python wrapper](https://til.simonwillison.net/gpt3/chatgpt-api) for the ChatGPT API.\n\nHere's my first attempt at a very rough implementation. It gains access to three new actions:\n\n`wikipedia: <search term>` - search Wikipedia and returns the snippet of the first result`simon_blog_search: <search term>` - search my blog and return the truncated text of the first result`calculate: <expression>` - evaluate an expression using Python's `eval()` function (so dangerous! This should absolutely use something like Here are some (very cherry-picked) examples of my `query()` function running, showing the underlying deliberations of the LLM:\n\n```\nquery(\"What does England share borders with?\")\nThought: I should list down the neighboring countries of England\nAction: wikipedia: England\nPAUSE\n -- running wikipedia England\nObservation: <span class=\"searchmatch\">England</span> is a country that is part of the United Kingdom. It shares land borders with Wales to its west and Scotland to its north. The Irish Sea lies northwest\nAnswer: England shares borders with Wales and Scotland.\nquery(\"Has Simon been to Madagascar?\")\nThought: I can try and search on Simon's blog or look up Madagascar on Wikipedia to see if Simon has mentioned it in any of his writings.\n\nAction: simon_blog_search: Madagascar\n\nPAUSE\n -- running simon_blog_search Madagascar\nObservation: Weeknotes: More releases, more museums: Lots of small releases this week.\nDatasette\nI released two bug fix releases for Datasette - 0.30.1 and 0.30.2. Changelog here. My Dogsheep personal analytics project means I&#39;m using Datasette for my own data analysis every day, which inspires me to fix small but annoying bugs much more aggressively.\nI&#39;ve also set myself a Streak goal to land a commit to Datasette every day.\nI landed a tiny new feature to master yesterday: a ?column__notin=x,y,z filter, working as an inverse of the existing ?column__in=x,y,z filter. See issue #614 for details.\nMore Niche Museums\nI&#39;ve been keeping up my streak of adding at least one new museum to www.niche-museums.com every day. This week I added the Pirates Museum in Antananarivo, Madagascar, the David Rumsey Map Center at Stanford, Galerie de Paléontologie et d’Anatomie comparée in Paris, DEVIL-ish Little Things in Vancouver, Washington, Mardi Gras World in New Orleans, Environmental Volunteers EcoCenter in Palo Alto, the Evergreen A\nObservation: According to the blog post searched on Simon's blog, he has visited the Pirates Museum in Antananarivo, Madagascar which is listed as a niche museum on www.niche-museums.com.\n\nAnswer: Yes, Simon has been to Madagascar and visited the Pirates Museum located in Antananarivo.\nquery(\"Fifteen * twenty five\")\nThought: The action required is a calculation\nAction: calculate: 15 * 25\nPAUSE\n -- running calculate 15 * 25\nObservation: 375\nAnswer: Fifteen times twenty five equals 375.\n# This code is Apache 2 licensed:\n# https://www.apache.org/licenses/LICENSE-2.0\nimport openai\nimport re\nimport httpx\n\nopenai.api_key = \"sk-...\"\n \nclass ChatBot:\n    def __init__(self, system=\"\"):\n        self.system = system\n        self.messages = []\n        if self.system:\n            self.messages.append({\"role\": \"system\", \"content\": system})\n    \n    def __call__(self, message):\n        self.messages.append({\"role\": \"user\", \"content\": message})\n        result = self.execute()\n        self.messages.append({\"role\": \"assistant\", \"content\": result})\n        return result\n    \n    def execute(self):\n        completion = openai.ChatCompletion.create(model=\"gpt-3.5-turbo\", messages=self.messages)\n        # Uncomment this to print out token usage each time, e.g.\n        # {\"completion_tokens\": 86, \"prompt_tokens\": 26, \"total_tokens\": 112}\n        # print(completion.usage)\n        return completion.choices[0].message.content\n\nprompt = \"\"\"\nYou run in a loop of Thought, Action, PAUSE, Observation.\nAt the end of the loop you output an Answer\nUse Thought to describe your thoughts about the question you have been asked.\nUse Action to run one of the actions available to you - then return PAUSE.\nObservation will be the result of running those actions.\n\nYour available actions are:\n\ncalculate:\ne.g. calculate: 4 * 7 / 3\nRuns a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary\n\nwikipedia:\ne.g. wikipedia: Django\nReturns a summary from searching Wikipedia\n\nsimon_blog_search:\ne.g. simon_blog_search: Django\nSearch Simon's blog for that term\n\nAlways look things up on Wikipedia if you have the opportunity to do so.\n\nExample session:\n\nQuestion: What is the capital of France?\nThought: I should look up France on Wikipedia\nAction: wikipedia: France\nPAUSE\n\nYou will be called again with this:\n\nObservation: France is a country. The capital is Paris.\n\nYou then output:\n\nAnswer: The capital of France is Paris\n\"\"\".strip()\n\naction_re = re.compile('^Action: (\\w+): (.*)$')\n\ndef query(question, max_turns=5):\n    i = 0\n    bot = ChatBot(prompt)\n    next_prompt = question\n    while i < max_turns:\n        i += 1\n        result = bot(next_prompt)\n        print(result)\n        actions = [action_re.match(a) for a in result.split('\\n') if action_re.match(a)]\n        if actions:\n            # There is an action to run\n            action, action_input = actions[0].groups()\n            if action not in known_actions:\n                raise Exception(\"Unknown action: {}: {}\".format(action, action_input))\n            print(\" -- running {} {}\".format(action, action_input))\n            observation = known_actions[action](action_input)\n            print(\"Observation:\", observation)\n            next_prompt = \"Observation: {}\".format(observation)\n        else:\n            return\n\ndef wikipedia(q):\n    return httpx.get(\"https://en.wikipedia.org/w/api.php\", params={\n        \"action\": \"query\",\n        \"list\": \"search\",\n        \"srsearch\": q,\n        \"format\": \"json\"\n    }).json()[\"query\"][\"search\"][0][\"snippet\"]\n\ndef simon_blog_search(q):\n    results = httpx.get(\"https://datasette.simonwillison.net/simonwillisonblog.json\", params={\n        \"sql\": \"\"\"\n        select\n          blog_entry.title || ': ' || substr(html_strip_tags(blog_entry.body), 0, 1000) as text,\n          blog_entry.created\n        from\n          blog_entry join blog_entry_fts on blog_entry.rowid = blog_entry_fts.rowid\n        where\n          blog_entry_fts match escape_fts(:q)\n        order by\n          blog_entry_fts.rank\n        limit\n          1\"\"\".strip(),\n        \"_shape\": \"array\",\n        \"q\": q,\n    }).json()\n    return results[0][\"text\"]\n\ndef calculate(what):\n    return eval(what)\n\nknown_actions = {\n    \"wikipedia\": wikipedia,\n    \"calculate\": calculate,\n    \"simon_blog_search\": simon_blog_search\n}\n```\n\nThis is not a very robust implementation at all - there's a ton of room for improvement. But I love how simple it is - it really does just take a few dozen lines of Python to make these extra capabilities available to the LLM and have it start to use them.\n\nCreated 2023-03-17T07:52:22-07:00, updated 2023-03-20T15:18:43-07:00 · [History](https://github.com/simonw/til/commits/main/llms/python-react-pattern.md) · [Edit](https://github.com/simonw/til/blob/main/llms/python-react-pattern.md)", "url": "https://wpnews.pro/news/a-simple-python-implementation-of-the-react-pattern-for-llms", "canonical_source": "https://til.simonwillison.net/llms/python-react-pattern", "published_at": "2026-11-10 22:33:34+00:00", "updated_at": "2026-11-10 22:52:23.941347+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "ai-tools", "developer-tools", "generative-ai"], "entities": ["Simon Willison", "ReAct", "ChatGPT", "OpenAI", "langchain", "Matt Webb", "Wikipedia", "Datasette"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a-simple-python-implementation-of-the-react-pattern-for-llms", "markdown": "https://wpnews.pro/news/a-simple-python-implementation-of-the-react-pattern-for-llms.md", "text": "https://wpnews.pro/news/a-simple-python-implementation-of-the-react-pattern-for-llms.txt", "jsonld": "https://wpnews.pro/news/a-simple-python-implementation-of-the-react-pattern-for-llms.jsonld"}}