Chat agent service
A tool-using chat agent service shared across tenants
One streaming chat service answers for every site on the platform, with each tenant's own voice and knowledge, validated tool calls, and limits that hold under load.
How it fits together
Inputs
Site visitors
A chat window on each tenant's site sends the conversation so far.
Tenant knowledge
Each tenant's facts come from its own content system.
Payload CMS
Engine
Chat service
A TypeScript service on Node that streams Claude responses through the Anthropic SDK's tool runner.
- Claude
TypeScript
Validate the request
Message length, conversation length, session id, and every field are checked before the model sees anything. Unknown tenants are refused.
Assemble the prompt
The tenant's persona plus its knowledge base, cleaned of instruction-like text and cached between requests.
Typed tool calls
The lead-capture tool validates its input with a schema, and a failed handoff returns a polite message instead of breaking the chat.
- Zod
Outputs
Streamed answer
Text streams back to the visitor as it is written.
Captured lead
Contact details go to the lead intake workflow.
Guardrails
- Rate limits per tenant and session
- A per-tenant circuit breaker on monthly sessions
- Tests for streaming, upstream failure, limits, and tenant gating
The problem
Every site on a shared platform wants a chat agent that sounds like that business and knows its facts. Building one per site multiplies the work and the risk. One service has to stay correct for every tenant at once, including when the model provider fails.
What I built
- A streaming chat endpoint on the Anthropic SDK with a schema-validated lead-capture tool.
- Per-tenant prompts in two layers: the tenant's persona, then its knowledge base, sanitized and cached.
- Input validation, rate limits keyed by tenant and session rather than by address, and a per-tenant circuit breaker.
- Separate layers that keep the agent from stating prices, from the prompt to the cleaned knowledge base.
- Tests for the happy path, upstream failure fallback, rate limiting, the circuit breaker, and refusing unknown tenants.
How I built it
TypeScript on Node with Hono, the Anthropic SDK, and Zod, deployed as a container on the same self-hosted platform as the sites. A tenant registry holds each persona and points at that tenant's content system, so adding a tenant is data, not code.
What it shows
I build production agent services with the failure paths tested, not only the demo path.