Skip to content

BlazAI — the agent

Type or speak what you want. BlazAI classifies the intent, extracts the parameters, routes to the best provider, and hands back a one-click confirmation card. It executes; it does not merely advise.

Home: ai.blazpay.com · Chat routes: / and /c/:id · Cost: free

What it is

A conversational front door to everything in the platform. 27 intents covering trading, safety, automation, discovery and dApp building. Replies stream token-by-token over SSE, so it reads like a messaging app rather than a form submission.

The interaction contract is deliberately narrow: the agent never silently spends money. Anything with financial consequence resolves to a card the user signs.

The interaction model

Three repos, three responsibilities:

RepoDoesDoes not
bz-agentClassify intent, extract parametersTouch a chain, execute anything
bz-backendPersist chat, execute the intent, produce { text, data, action }Decide what the user meant
defi-dexStream, render the right card, drive the walletInterpret free text

The model stack

The intent engine is Blazpay's own: a LangGraph state machine in bz-agent with one prompt template and one Zod schema per intent, a Redis checkpointer for multi-turn state, and a PGVector knowledge base behind the fallback path. That graph — not any single model — is what turns a sentence into a structured, validated intent.

Underneath it, models are addressed by tier, never by name. A node asks for a tier; which model serves it is an env var.

TierRoleUsed for
extractionFast, deterministic, cheap. temperature: 0Every intent node — parameter extraction
conversationRicher proseThe conversation node — DeFi answers
complexMost capabledApp creation, contract audit

A separate JSON-mode drafting model backs Autopilot policy generation, and a self-hosted Rasa NLU service plus a wit.ai integration back the legacy swap-ai chat path.

Every node's output is validated by a Zod schema, so a malformed model response fails structurally rather than propagating.

Why the tier indirection matters

Swapping a provider is an env change, not a code change — LLM_MODEL_EXTRACTION, LLM_MODEL_CONVERSATION, LLM_MODEL_COMPLEX. Nothing in the graph names a model. See agent graph.

Why temperature: 0 everywhere

Intent classification and parameter extraction are not creative tasks. The same sentence should produce the same intent every time, because users retry and support needs to reproduce. Creativity lives only in the conversation node's prose.

Capability discovery without a nav bar

The AI site has no navigation. Users find capabilities three ways:

  1. Suggestion chipsGET /v1/blaz/suggestions and GET /v1/blaz/home-chips.
  2. The ⌘K command palettesrc/component/ai/CommandPalette.tsx fuzzy-searches ten agent actions plus past chats. Every action resolves to a prompt seeded into the composer via ?q=, never a route, never auto-sent. Openable anywhere via the blazai:palette-open window event.
  3. Just asking. The CONVERSATION intent answers "what can you do".

The ?q= effect must key on location.search

The palette navigates while the chat is already mounted, so a mount-only effect never fires — leaving ?q= in the URL with an empty composer. Strip the param with react-router navigate(path, { replace: true }), not history.replaceState: replaceState leaves the router's location stale, so picking the same action twice registers as the same location and silently does nothing.

Voice

Voice input uses the Web Speech API and auto-submits after a pause. A user can speak "swap half my eth to usdc" and get the same card. Voice confirmations and hands-free navigation are roadmap, not shipped.

Session and history

ConcernDetail
Session modelbz-backend/v1/models/blazSession.js{ wallet, name, pending, activeDappProjectId }
Message modelv1/models/blazMessage.js{ session, senderType, text, action, data }
IdentityKeyed by wallet address. There is no cookie or bearer session
Chat listGET /v1/blaz/sessions
One chatGET /v1/blaz/chat/:chatId

Because history is keyed by wallet, a returning user with no wallet connected sees an empty sidebar — which is exactly why the AI shell keeps a persistent WalletControl pill despite having no other chrome. See domains.

Disconnect must clear the chat

enabled: !!address only stops the chat-list query; React Query keeps serving the last data, so the previous wallet's history stayed visible after disconnect — someone else's data on a shared machine. The fix clears via queryClient.removeQueries({ queryKey: ['address'] }) and resets local state, gated on a connected→disconnected transition so guests are not reset on mount.

Streaming protocol

POST /v1/blaz/message/stream emits three event types:

EventPayload
metaThe resolved { action, data } — arrives first so the UI can start rendering the card
tokenSuccessive text chunks
doneStream complete

POST /v1/blaz/message is the non-streaming equivalent, returning the whole { text, data, action } at once.

Client: defi-dex/src/apis/ai.api.ts → sendAIMessageStream.

Accuracy

The measured non-fallback rate is 99.9%+ — roughly one unresolved reply in 18,000. That number is a proxy for accuracy, not a direct measure: it counts messages that reached a real intent rather than the fallback path, so it does not catch a message classified as the wrong intent.

Source: PROOF_POINTS in defi-dex/src/data/chainSeo.mjs, all-time snapshot.

Training-data loop

bz-backend/v1/routes/training.routes.js plus models/trainingDataModel.js provide a review pipeline: conversations get labelled, an operator approves or rejects, and approved examples export for prompt refinement.

EndpointPurpose
GET /v1/trainingList candidates
GET /v1/training/statsCounts by status
GET /v1/training/exportExport approved examples
PATCH /v1/training/:idApprove / reject one
POST /v1/training/bulkBulk update

Under the hood

ConcernWhere
Intent listbz-agent/src/utils/constant.ts → AVAILABLE_INTENTS
Entry promptbz-agent/src/template/entry.ts → getInitSystemTemplate(intents)
Per-intent templatesbz-agent/src/template/*.ts
Nodesbz-agent/src/agent/node.ts (+ bzTokenNode, createDappNode, goOnchainNode, modifyDappNode)
Graphbz-agent/src/agent/graph.ts
Compilation + checkpointerbz-agent/src/agent/index.ts, src/utils/redisCheckPointer.ts
Vector storebz-agent/src/agent/vector.ts — PGVector on PostgreSQL
Dispatcherbz-backend/v1/services/agent.service.js → getAgentResponse
Controllerbz-backend/v1/controllers/blaz.controller.js
Rendererdefi-dex/src/component/defi/swapAI/BotMessage.tsx

Deep dives: agent graph, chat request path, action renderer.

Known limits

  • No perpetuals intent. The agent cannot open or manage a perp position.
  • LIMIT_ORDER classifies but cannot execute — the spot limit product is not shipped. The user gets a text reply, which reads as a failure. See limit orders.
  • The DCA amount semantic mismatch — the agent template says per-cycle, the app deposits it as total. See CSIP.
  • connect_wallet is EVM-only and only appears via a suggestion chip or the five-message gate. Solana and TRON connect through the pill.
  • No message-count quota exists. The only per-wallet limit in the platform is the audit quota (SystemSetting.audit_free_limit_per_wallet, default 3). If a chat quota is needed, the pattern to copy is that one.
  • Voice is input-only.

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.