Skip to content

Repository files navigation

Langchain AI Agent

A personal assistant that answers questions about one person's background, grounded in a markdown knowledge base they write themselves.

Built with FastAPI + LangChain / LangGraph. Deployable to Railway (or any Docker host). Talked to over HTTPS by a website chat widget.

The browser never talks to OpenAI or Anthropic. Keys stay on this service.

Why this exists

A static portfolio is a one-way pitch. This lets a visitor ask about experience, projects, and skills and get an answer from facts you wrote down — not something the model invented.

Architecture

Browser chat widget
  → POST /api/v1/chat
  → FastAPI / ChatService
  → LangGraph agent + search_knowledge_base
  → knowledge_base/*.md, *.yaml

Full request lifecycle: ARCHITECTURE.md.

There is no vector database. The knowledge base is small enough to load in memory. Retrieval is keyword match with a safe fallback to the full corpus.

Project structure

app/            API, config, graph, tools, services, observability
knowledge_base/ Markdown/YAML facts — edit these, not Python
tests/          unit, api, integration, evaluation
scripts/        local run + retrieval eval

How the agent works

  1. Incoming message is validated, rate-limited, and optionally short-circuited by regex moderation.
  2. If it proceeds, a LangGraph tool-calling agent runs with one tool: search_knowledge_base.
  3. The system prompt requires the tool to be called before any factual claim, forbids invented history, and keeps replies as plain text.
  4. Conversation history (last 12 turns) is stored in SQLite and passed into the next turn.

When ZIZKADB_HOST or ZIZKADB_API_KEY is set, each chat turn is also traced in ZizkaDB: user message, routing decision, LangGraph/LLM/tool steps, and the final reply, all under the same sessionId. Tracing is optional and never fails the chat.

Knowledge base

Files in knowledge_base/:

File Contents
profile.md Name, title, contact, links, summary
career_timeline.yaml Roles (dates marked provisional where they overlap)
education.md Degree
skills.md Technical skills
projects.md Shipped work and links
hobbies_and_misc.md Intentionally empty — the agent must say it does not know

To update facts: edit a file and redeploy. No code change.

To add a document: drop another .md or .yaml into knowledge_base/. The loader picks it up automatically.

Local development

python3.11 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env   # set OPENAI_API_KEY or ANTHROPIC_API_KEY
uvicorn app.main:app --reload

Or bash scripts/run_local.sh.

curl http://localhost:8000/healthz
curl http://localhost:8000/readyz
curl -X POST http://localhost:8000/api/v1/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"11111111-1111-1111-1111-111111111111","message":"What is Saad'\''s experience?"}'

OpenAPI: http://localhost:8000/docs

Tests

pytest
ruff check .
mypy app
python scripts/eval.py

Evaluation cases in tests/evaluation/ check that retrieval returns the right facts (profile, experience, React Native, projects, education, links, unknown hobbies). CI does not call a live LLM.

API

Endpoint Purpose
GET /healthz Liveness
GET /readyz Knowledge loaded + LLM key present
POST /api/v1/chat { sessionId, message }{ reply, sessionId, messageId, createdAt, simulated }
GET /api/v1/chat/history?sessionId= Recent messages

Errors are always {"error": "..."}.

Docker

docker compose up --build

Railway: connect this repo, mount a volume at /app/data, set env vars from ENVIRONMENT.md. Health check is /healthz (railway.json). Full steps, CORS, and the website widget URL: DEPLOYMENT.md.

Minimum production vars: an LLM key, CORS_ALLOWED_ORIGINS (your site, including www if needed), ENVIRONMENT=production.

Connecting a website

Point the chat widget at this service:

VITE_CHAT_API_BASE=http://localhost:8000/api/v1/chat

or the Railway URL in production. The widget does not import LangChain.

Adding a tool

  1. Write a function in app/tools/.
  2. Register it next to search_knowledge_base in app/graph/agent.py.
  3. Add a unit test.

The API contract does not change.

Security

See SECURITY.md. Summary: moderation before the LLM, rate limits, daily budget, size/timeouts, locked CORS, redacted logs, no stack traces to the client.

Troubleshooting

Symptom Likely cause
/readyz is degraded No LLM key, or knowledge_base/ empty
CORS error in the browser Origin missing from CORS_ALLOWED_ORIGINS
History gone after redeploy No volume at /app/data
Canned fallback reply Daily budget exhausted or graph timeout

License

MIT

About

Personal assistant agent grounded in a markdown knowledge base — FastAPI + LangChain/LangGraph

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages