Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Basic Agentic Support App

A small Python command-line application for teaching the core agent pattern:

ticket -> model -> tool call -> observation -> model -> final answer

The model decides which read-only support tools to use. Ordinary Python code validates and runs those tools, adds their results to the conversation, and asks the model what to do next. The terminal shows that loop as numbered MODEL, ACTION, OBSERVATION, and ANSWER events.

This is intentionally a teaching example, not a production support system. All customer, service, and knowledge-base data is local fixture data.

Presentation

Download the complete presentation deck.

What makes it agentic?

A normal chatbot produces one response. This application gives a model a goal, tools, observations from those tools, and repeated chances to act:

  1. app.py receives a support ticket and selects the configured model.
  2. agent.py imports the registered tool schemas from tools/ and sends them with the ticket and system instructions to the model.
  3. The model either requests one or more tools or returns a final answer.
  4. tools/__init__.py runs requested tools through an explicit allow-list.
  5. Each result is appended as a tool observation with the matching tool-call ID.
  6. The loop repeats until the model answers or reaches the six-turn limit.

The trace shows decisions and tool results, not hidden chain-of-thought or raw provider payloads.

Requirements

  • Python 3.10 through 3.14
  • uv
  • An API key for a provider supported by LiteLLM
  • A model with native function-calling support

Setup

Install the locked dependencies:

uv sync

Create a local environment file:

cp .env.example .env

Then edit .env: keep or change its concrete AGENT_MODEL, and add the matching provider API key. Use a LiteLLM model identifier; these provider-prefixed examples are recognized by the pinned LiteLLM version and report native function-calling support:

Provider Model setting Credential
OpenAI openai/gpt-4o-mini OPENAI_API_KEY
Anthropic anthropic/claude-sonnet-4-5 ANTHROPIC_API_KEY
Google Gemini gemini/gemini-2.5-flash GEMINI_API_KEY

Provider availability still depends on your account. The application passes the identifier to LiteLLM unchanged and checks that LiteLLM reports native function-calling support before processing a ticket. OpenAI also documents function calling for gpt-4o-mini.

Run the application

Pass a ticket for one-shot mode:

uv run python app.py --model openai/gpt-4o-mini \
  "Customer CUST-1001 is locked out and says the password-reset email is not arriving."

--model takes precedence over AGENT_MODEL. If the environment variable is configured, the shorter equivalent is:

uv run python app.py \
  "Customer CUST-1001 is locked out and says the password-reset email is not arriving."

Omit the ticket to start interactive mode:

uv run python app.py --model openai/gpt-4o-mini

The first message starts a support session. Later messages are follow-ups in that same session, so the model can use the earlier customer request, its tool calls, and their observations. For example, after resolving a login problem, ask:

Which evidence ruled out a service outage?

Type new or reset before starting work for another customer. That command discards the current session history and creates a clear privacy boundary between customers. As a guardrail, the app also refuses a different explicit customer ID until the session is reset. Type quit or exit, press Ctrl-D, or press Ctrl-C to stop.

One-shot mode is unchanged: it processes exactly the ticket supplied on the command line with fresh history and then exits.

Trace numbers continue across follow-ups in one session and restart at [1] after new or reset, making the session boundary visible during a demo.

Reading the trace

A valid run can have this shape (the model may choose a different tool order). Every customer, service, incident, and knowledge-base value below comes from the corresponding module in tools/:

[1] MODEL           Requesting the next decision from openai/gpt-4o-mini
[2] ACTION          lookup_customer({"customer_id": "CUST-1001"})
[3] OBSERVATION     {"ok": true, "result": {"account_status": "locked", "contact_email": "maya.chen@example.test", "customer_id": "CUST-1001", "found": true, "name": "Maya Chen", "plan": "Pro"}}
[4] MODEL           Requesting the next decision from openai/gpt-4o-mini
[5] ACTION          check_service_status({"service": "authentication"})
[6] OBSERVATION     {"ok": true, "result": {"found": true, "incident_id": null, "message": "Sign-in and password reset services are operating normally.", "service": "authentication", "status": "operational"}}
[7] ACTION          search_knowledge_base({"query": "cannot log in"})
[8] OBSERVATION     {"ok": true, "result": {"matches": [{"article_id": "KB-LOGIN-001", "score": 1, "steps": ["Verify the customer ID and account status.", "Direct a locked customer to the self-service account-unlock flow.", "Ask the customer to request one fresh password-reset email and use only the newest link."], "summary": "Confirm the customer, use the self-service unlock flow, then request one fresh password-reset email.", "title": "Unlock an account and reset a password"}], "query": "cannot log in"}}
[9] MODEL           Requesting the next decision from openai/gpt-4o-mini
[10] ANSWER
Status: RESOLVED
Customer response: Your account is locked, while authentication is operating normally. Use the self-service account-unlock flow, then request one fresh password-reset email.
Evidence:
- Customer CUST-1001 is Maya Chen on the Pro plan with a locked account.
- The authentication service is operational with no active incident.
- KB-LOGIN-001 provides the account-unlock and password-reset steps.
Next step: Unlock the account, then use only the newest password-reset link.

The final response is prompted and validated to use this presentation-friendly structure. If the first answer is malformed, the loop asks the model to reformat it without adding facts:

Status: ESCALATED
Customer response: I could not find supported guidance for this issue.
Evidence:
- The customer record was found, but the knowledge-base search returned no matching article.
Next step: A human support specialist should investigate the unsupported issue.

Available tools

Tool Input Purpose
lookup_customer customer_id: string Find the customer's plan and account status.
check_service_status service: string Check authentication, billing, or email health and incident details.
search_knowledge_base query: string Rank local troubleshooting articles by relevant keywords.

All three tools read deterministic in-memory fixtures. They do not update an account, open a ticket, call an external service, or persist data.

Each tool module co-locates its complete contract: the model-facing JSON schema, the deterministic fixture data, and the Python function that implements it:

  • tools/lookup_customer.py contains the lookup schema, customer records, and customer lookup.
  • tools/check_service_status.py contains the status schema, service records, and status check.
  • tools/search_knowledge_base.py contains the search schema, articles, and local search.

tools/__init__.py is both the package's public interface and its execution boundary. It assembles the three module contracts into TOOL_SCHEMAS, maps the model-visible names to those Python functions with an explicit TOOL_HANDLERS allow-list, and provides the dispatcher. This keeps each tool's description, accepted arguments, fixture, and behavior together while leaving registration and dispatch in one easy-to-inspect package entry point.

Three presentation tickets

Use these in order to show resolution, incident awareness, and safe escalation:

Customer CUST-1001 is locked out and says the password-reset email is not arriving. Check the account, relevant services, and troubleshooting guidance.
Customer CUST-1002 sees a duplicate pending charge. Check their account and whether there is a wider billing issue today.
Customer CUST-1003 reports that device sync stopped working. Check their account, look for supported guidance, and avoid inventing an answer.

The first should combine Maya Chen's locked account, operational authentication and email services, and knowledge-base guidance. The second should connect Jordan Lee's active account to degraded billing incident INC-204. The third combines Priya Shah's seeded past_due account with an unsupported device-sync topic. The account fact does not establish a cause, and the knowledge base has no matching article, so the agent should escalate instead of guessing. Final wording is model-dependent.

Safety and failure behavior

  • Only handlers in the tools/__init__.py allow-list can run; a model cannot name an arbitrary Python function and execute it.
  • Tool arguments are parsed and validated before dispatch.
  • Malformed arguments, unknown tools, missing fixture records, and local tool exceptions become concise observations. This gives the model a chance to recover or escalate.
  • The loop is limited to six model turns. Reaching the limit returns a human escalation response instead of looping forever.
  • Final answers must contain one valid status plus Customer response, Evidence, and Next step. A malformed answer is sent back for correction within the same six-turn limit.
  • Provider, network, and authentication failures remain visible application errors. There is no scripted answer that could be mistaken for model output.
  • Models without native function calling are rejected during startup.
  • The UI prints curated events only; it does not expose private reasoning or raw API responses.
  • Interactive history exists only for the current in-memory support session. new or reset clears it before another customer's ticket, and an explicit customer-ID change is rejected until then. Failed model turns are not committed to the session, and nothing is persisted when the process exits.

These controls illustrate a useful boundary: the model chooses an action, but application code decides which actions are possible and when execution must stop.

Tests

Run the complete offline test suite with:

uv run pytest

Tests inject scripted completion responses and block real model calls. They cover fixture tools, multi-step and multi-tool trajectories, tool-call IDs, malformed requests and answers, allow-list enforcement, the turn limit, CLI configuration, one-shot mode, interactive follow-up history, customer isolation, failed-turn rollback, and history clearing with new and reset. No credentials or network access are required.

For a manual live smoke test, set a real AGENT_MODEL and provider API key in .env, then run one of the presentation tickets above.

Project layout

app.py                          CLI, interactive prompt, and trace rendering
agent.py                        system prompt and bounded model/tool loop
tools/
  __init__.py                   schemas, allow-list, dispatcher, and public interface
  lookup_customer.py            schema, customer fixture, and lookup tool
  check_service_status.py       schema, service fixtures, and status tool
  search_knowledge_base.py      schema, article fixtures, and search tool
tests/                          offline unit and end-to-end scripted-model tests
DEMO_SCRIPT.md                  short presenter walkthrough
assets/
  build-a-basic-agentic-app-presentation.pptx  complete presentation deck
  meetup-agentic-app-banner.png   Meetup event artwork
LICENSE                         MIT license

LiteLLM is pinned to 1.96.2 so the demo uses a reproducible model interface. See LiteLLM's function-calling documentation for provider-specific details.

License

This project is available under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages