Regular Expressions
Use re.search, findall, and groups. Pull a URL or a small blob from messy model text. Prefer json.loads when you already have JSON.
A regular expression (regex) is a pattern that finds pieces of text. Import the standard library module re. Models often wrap a useful bit in extra words. “Sure! Here is https://example.com/doc thanks.” Regex can pull the URL. It is also easy to get wrong.
If you already have a clean JSON string, use json.loads. Do not invent a regex for full JSON. JSON has nested braces, escaped quotes, and strings that contain }. A greedy .* will grab too much or too little. The JSON parser already knows the rules.
The whole match is the line. Group 1 is the digits in parentheses. Prefer json.loads when you already have JSON.
Groups keep the piecesRegex is a scalpel for small pieces: a URL, a step number, an id with a known shape. It is not a second programming language you should use for policy.
search and findall
| Call | Result |
|---|---|
re.search(pattern, text) | First match, or None |
re.findall(pattern, text) | A list of all matches |
match.group() | The matched text |
match.group(1) | The first group in ( ). |
re.match(pattern, text) | Match only at the start |
If search finds nothing, it returns None. Check before you call .group(). None.group() is an AttributeError. That crash is common.
import re
text = class="tok-s">"step 3 of 12"
print(re.findall(class="tok-s">"[0-9]+", text))
m = re.search(class="tok-s">"step ([0-9]+)", text)
if m:
print(m.group(1))[0-9]+ means “one or more digits.” + means one or more. * means zero or more. ? means optional. You do not need all of regex. You need “digits,” “not a space,” and “this literal prefix.”
findall with a group returns the group contents, not always the whole match. Print and look. Do not assume.
Groups
Parentheses mark a group. You keep the whole match with group(0) or group(). You keep the piece in parentheses with group(1).
Use one or two groups. A pattern with many groups is hard to trust. Name the piece you needed in a variable: step_no = m.group(1).
If the pattern has no match, skip. Do not invent a default step number from thin air unless you log that you guessed.
Extract a URL
Model text is messy. Look for https:// then take characters until a space.
import re
text = class="tok-s">"Read https://example.com/doc and stop"
m = re.search(class="tok-s">"https://[^ ]+", text)
if m:
print(m.group())[^ ]+ means “one or more characters that are not a space.” URLs can still contain trailing punctuation like . or ). Strip those if you need a clean href. This pattern is a start, not a full URL parser.
findall pulls several URLs. Check they start with https:// before a fetch tool runs. http:// can be rejected in the same guard you already wrote in exceptions.
Extract a JSON blob
Sometimes the model prints words, then a {...} object, then more words. You can search for a { … } blob, then json.loads.
This is fragile. Extra braces break it. Nested objects can confuse .*. Prefer a clean JSON string when you control the prompt. A later lesson strips markdown fences and finds the first { and last } with find / rfind, which is often enough without regex.
Order of attack:
- If the whole string is JSON,
json.loads(text). - If you must, pull a small URL or id with regex.
- Do not write a regex that “parses JSON.”
Regex is easy to get wrong. A pattern that works on one reply can fail on the next. Prefer json.loads when you can.
A raw string r"\d+" is how many tutorials write digits. In this course we often write "[0-9]+" to avoid backslash fights inside other languages’ strings. Both mean digits. A dot . in regex means “any character.” To match a real dot in file.json, you need a different pattern than “any char + json.” Prefer endswith(".json") for suffixes. Regex is overkill for a suffix.
re.compile(pattern) builds the pattern once. For a few searches in a tool, compiling is optional. If you search a huge log in a loop, compile. Always check for None before .group. Always log the input when there is no match. Silent empty lists from findall look like success.
Common mistakes
.group()onNone.- Greedy
.*across the whole document. - Parsing JSON with regex.
insubstring checks you thought were “regex-level” careful — they are not.- Forgetting
import re.
Run to execute this in your browser. Nothing is sent to a server.
The messy line works because there is one object. Nested braces would make .* greedy. That is why json.loads on a sliced first-brace to last-brace (next JSON-from-model lesson) is the more robust cousin.
How agents use this
Models mix prose and data. Use regex to pull a URL or a step number from that mix. If the model returned JSON, run json.loads on that string. Do not scrape a full JSON document with a clever pattern. Log the raw text when a parse fails so you can see why.
Tool routing should not be regex on the whole reply (“if the text contains search, call search”). Parse an action object. Then look at tool. Substring policy is how “I do not want to search” still fires search because the word appears.
When fence-stripping fails, regex will not save a badly specified contract. Fix the prompt to ask for a single JSON object. Use regex as a backup extractor for ids you already know the shape of. Keep the pattern next to a test string. If you cannot write two tests, you cannot afford the pattern.
A ticket id like T-[0-9]+ is a fair regex. A “parse any JSON” pattern is not. When a match fails, return None and let the loop ask the model to try again with a cleaner shape. Do not invent a default id. Invented ids look like success and route the agent to the wrong record.
Check your understanding