Aiinfox Tech
Artificial Intelligence

AI Agent Project in Python: Build an Insurance Claims Assistant for Your Portfolio

By AIInfoxTech7 min read

If you are learning to build AI agents and want something concrete to show for it, this guide walks through an AI agent project in Python that you can finish in a weekend: an insurance claims-intake assistant. It collects claim details in a chat loop, validates the required fields, calls two small tools, decides when to hand off to a person and logs every step.

The insurance setting is only a vehicle. The real subject is an agent that stays inside its limits.

Why an AI agent project in Python belongs in your portfolio

Most beginner AI portfolios contain a chatbot that answers questions and nothing else. An agent is different: it makes decisions, calls functions, keeps state across turns and knows when to stop. Each is something you can demonstrate, test and explain in an interview.

Python is the natural choice: every mainstream LLM API has a Python client and the testing tools are mature. The guide to starting an AI career shows where a project like this fits.

What the claims assistant does, and what it refuses to do

Before writing any code, write down the scope: a short list of things the agent will do and an equally short list it will not:

  • It will collect a policy number, incident date, incident type and description, look up the policy, look up an existing claim if asked, and confirm the details back.
  • It will not decide whether a claim is covered, quote an amount, discuss injuries or legal matters, or continue past a fixed number of turns. In each case it hands off to a person.

Put both lists at the top of your README; they are your acceptance criteria. The same principle holds at company scale, as the post on AI agents and automation in business explains: useful agents have clear edges.

The architecture: planner, tools, memory and guardrails

The assistant has four parts, one file each.

  • Planner. Sends the conversation to a language model and gets back one structured decision: ask, call a tool, hand off, or finish. The model proposes; your code decides.
  • Tools. Plain Python functions the planner may call: a policy lookup and a claim-status lookup, backed by in-memory dictionaries so the project runs without a database.
  • Memory. The message list plus a claim dictionary holding the fields collected so far.
  • Guardrails. Rules that live in code rather than in the prompt: required fields, a turn limit, a cap on tool calls per turn, and the hand-off triggers.

Anything that must always happen is enforced in Python, not requested in a prompt. That is what makes the agent bounded rather than merely well behaved.

Folder layout

claims-assistant/
  README.md
  requirements.txt
  agent/
    __init__.py
    planner.py
    tools.py
    guardrails.py
    logger.py
    loop.py
  tests/
    test_tools.py
  logs/

Small, flat and obvious: a reader should be able to guess what each file does from its name.

Step 1: write the tools first

Start with the tools because they are the easiest part to test and they fix what data the agent can see. Both take a string and return a dictionary, and both return {"found": False} rather than raising when a record is missing. The planner can cope with a missing record; it cannot cope with an exception.

# agent/tools.py
POLICIES = {"PC-1001": {"cover": "motor", "active": True},
            "PC-1002": {"cover": "home", "active": False}}
CLAIMS = {"CL-5001": {"policy": "PC-1001", "status": "under review"}}

def lookup_policy(policy_number: str) -> dict:
    policy = POLICIES.get(policy_number.strip().upper())
    return {"found": True, **policy} if policy else {"found": False}

def get_claim_status(claim_id: str) -> dict:
    claim = CLAIMS.get(claim_id.strip().upper())
    return {"found": True, **claim} if claim else {"found": False}

TOOLS = {"lookup_policy": lookup_policy, "get_claim_status": get_claim_status}

The TOOLS dictionary is the allow-list. If the model asks for a function that is not in it, the loop ignores the request.

Step 2: guardrails and hand-off rules

# agent/guardrails.py
REQUIRED = ["policy_number", "incident_date", "incident_type", "description"]
MAX_TURNS = 12
MAX_TOOL_CALLS = 3
HANDOFF_WORDS = ("injur", "lawyer", "complaint", "human")

def missing_fields(claim: dict) -> list:
    return [f for f in REQUIRED if not claim.get(f)]

def needs_handoff(user_text: str, claim: dict):
    if any(w in user_text.lower() for w in HANDOFF_WORDS):
        return "sensitive topic"
    if claim.get("policy_active") is False:
        return "policy not active"
    return None

The keyword list is deliberately blunt: a false hand-off costs a little time, while a missed hand-off on an injury claim is a real problem.

Step 3: the planner and call_model()

The planner turns the conversation into a decision in a fixed shape. Put the model call behind a single function so that switching providers, or using a locally hosted model, changes one file.

# agent/planner.py
import json

SYSTEM = """You are a claims-intake assistant. Collect policy_number, incident_date,
incident_type and description, one at a time. Reply ONLY with JSON:
{"action": "ask|tool|handoff|finish", "tool": "lookup_policy|get_claim_status|null",
 "argument": "...", "fields": {}, "message": "..."}"""

def call_model(system: str, messages: list) -> str:
    # Call whichever LLM API you use; return its text.
    raise NotImplementedError

def plan(messages: list) -> dict:
    try:
        decision = json.loads(call_model(SYSTEM, messages))
    except json.JSONDecodeError:
        decision = {"action": "ask", "message": "Could you say that again?"}
    return {"fields": {}, "message": "", **decision}

call_model() sends the system prompt and the messages to any LLM API and returns the text. If that text is not valid JSON, the planner asks the customer to repeat themselves rather than crashing, and missing keys get safe defaults. That fallback is itself a guardrail.

Step 4: the agent loop

Every branch below corresponds to one item on the "will" or "will not" list.

# agent/loop.py
import json
from agent.planner import plan
from agent.tools import TOOLS
from agent.guardrails import MAX_TURNS, MAX_TOOL_CALLS, missing_fields, needs_handoff
from agent.logger import log

def stop(reason, claim):
    log("handoff", reason=reason, claim=claim)
    print("Assistant: I am passing this to a colleague.")

def run():
    messages, claim = [], {}
    for turn in range(MAX_TURNS):
        user_text = input("You: ")
        messages.append({"role": "user", "content": user_text})
        if reason := needs_handoff(user_text, claim):
            return stop(reason, claim)
        decision, calls = plan(messages), 0
        claim.update(decision["fields"])
        while decision["action"] == "tool" and decision["tool"] in TOOLS and calls < MAX_TOOL_CALLS:
            result = TOOLS[decision["tool"]](decision["argument"])
            log("tool", name=decision["tool"], result=result)
            if decision["tool"] == "lookup_policy" and result["found"]:
                claim["policy_active"] = result["active"]
            messages.append({"role": "tool", "content": json.dumps(result)})
            decision, calls = plan(messages), calls + 1
            claim.update(decision["fields"])
        log("decision", turn=turn, action=decision["action"], claim=claim)
        reason = needs_handoff("", claim) or (decision["action"] == "handoff" and decision["message"])
        if reason:
            return stop(reason, claim)
        if decision["action"] == "finish" and not missing_fields(claim):
            log("finish", claim=claim)
            return print("Assistant: Thank you, your claim is recorded.")
        print("Assistant:", decision["message"])
        messages.append({"role": "assistant", "content": decision["message"]})
    stop("turn limit reached", claim)

The tool loop is capped, so a confused model cannot spin. The keyword check runs before the model is called, so a message about an injury never reaches the planner. And finish is only honoured when the required fields are present; the code, not the model, decides when the job is done.

Step 5: log every step

# agent/logger.py
import json, time

def log(event: str, **data):
    with open("logs/run.jsonl", "a") as f:
        f.write(json.dumps({"ts": time.time(), "event": event, **data}) + "\n")

One JSON line per event, appended to a file. Read it top to bottom after a run to see what the agent decided, which tools it called and why it stopped. Commit a sample log so a reviewer can see this without running anything.

Step 6: tests for the tools and the rules

Tools and guardrails are pure functions, so they are quick to test, and they are the part you can run without an API key.

# tests/test_tools.py
from agent.tools import lookup_policy, get_claim_status
from agent.guardrails import missing_fields, needs_handoff

def test_known_policy_is_found():
    assert lookup_policy(" pc-1001 ")["active"] is True

def test_missing_fields_lists_the_gaps():
    assert missing_fields({"policy_number": "PC-1001"}) == [
        "incident_date", "incident_type", "description"]

def test_injury_triggers_handoff():
    assert needs_handoff("my passenger was injured", {}) is not None

Run them with pytest from the project root. You are not testing the model; you are proving that the parts you control behave the way your README says.

The README checklist

A README is often the only thing a reviewer reads. Make sure yours has:

  1. One sentence on what the assistant does and one on what it does not
  2. The two scope lists, word for word
  3. Setup: virtual environment, requirements, the API key as an environment variable, then python -m agent.loop
  4. A short sample transcript showing a normal intake and a hand-off
  5. The sample log, with a line on each event type
  6. How to run the tests
  7. Known limitations and what you would build next

What recruiters look for in an agent project

Reviewers are rarely impressed by a clever prompt. They look for evidence that you know where the risk lives and have controlled it:

  • Bounded behaviour. Turn limits, tool allow-lists and hand-off rules in code, not only in the prompt.
  • Separation of concerns. Planner, tools, memory and guardrails in separate files.
  • Tests. Even a handful, covering the parts that do not depend on a model.
  • Observability. A log that explains every decision after the fact.
  • Honesty. A limitations section that says what breaks. Candidates who can name their own project's weaknesses come across as people who can be trusted with production code.

Be ready to explain the trade-offs: why the keyword list is blunt, why the model does not call functions directly, and what changes once the tools hit a real database.

Where to go next

Once the weekend version works, natural extensions include a SQL database in place of the dictionaries, a third tool that creates a claim record, an HTTP endpoint, and a replay script that checks the agent still hands off in the right places.

If you would rather build this with guidance, the AI agents and automation course covers this pattern in depth, and the artificial intelligence with Python programme is the starting point if you need the foundations first. You can also contact AIInfoxTech to talk through which path suits you.

AI AgentsPythonPortfolio ProjectsLLMAutomation

Frequently asked questions

Do I need a paid LLM API to build this AI agent project in Python?

Not for most of it. The tools, guardrails, logger and tests run without any model at all. You need an API key, or a locally hosted model, only for the planner step, and call_model() is the single place that changes.

Why does the model return JSON instead of talking to the customer directly?

A fixed decision shape lets your code check every action before it happens. If the model replied in free text you could not enforce the tool allow-list or the hand-off rules reliably.

Is an insurance claims assistant a good portfolio project if I do not want to work in insurance?

Yes. The domain is a vehicle for the patterns that matter: structured decisions, tool calls, state across turns, hand-off and logging. Those transfer to support, booking, onboarding and any other intake workflow.

How long should the project take?

The version described here is designed to fit in a weekend: tools and tests on the first day, planner and loop on the second. Extensions such as a database or a web interface can follow later.

What should I show in an interview?

The README, a sample log from a run that ended in a hand-off, and the tests. Be ready to explain why each guardrail lives in code rather than in the prompt.

Learn this in a classroom in Mohali

Mentor-led batches, real projects and placement support. Talk to the team about the programme this article belongs to.

All articles

Request a Call Back

Need assistance or have questions? Simply fill out the form below, and one of our experts will get back to you as soon as possible. We’re here to help with all your queries and ensure you’re on the right path!

Request a Call Back