Skip to content

Latest commit

 

History

703 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI Native Call Center

An open-source call center where the AI is the default answer, not an add-on.

Calls arrive at FreeSWITCH and are answered by a voice model over a speech-to-speech connection the application terminates itself. The model talks; a flow steers the conversation and decides when a person is needed. When one is, the caller is transferred into a real queue where real agents are waiting, their SIP leg held by a companion Chrome extension. Everything the two halves do lands in one call record.

It is one Go binary — REST API, event stream and the whole web interface inside it — plus PostgreSQL and FreeSWITCH.

简体中文 · Deploying · Design

Agent desktop: an inbound call is answered, the live transcript fills in, then after-call work

Try it

Linux:

curl -fsSL https://github.com/rasonyang/ai-native-callcenter/releases/latest/download/install.sh | sudo sh

macOS:

curl -fsSL https://github.com/rasonyang/ai-native-callcenter/releases/latest/download/install.sh | sh

Details: deploy/one-line-installer.md.

What it does

Answers with a model, not a menu. The AI leg is a SIP endpoint inside the application: FreeSWITCH bridges the caller to it and audio goes straight to the provider — G.711 passed through byte for byte where the provider accepts it, so nothing decodes or resamples on the way.

Steers without scripting the conversation. The model owns the dialogue; the flow owns the phase. A phase carries instructions and a list of tools the model may use; transitions fire on tool results. A phase may also carry a line of its own — a greeting, a hand-over script, a goodbye — which the bot is meant to say as written rather than paraphrase: doubao is handed the words to speak, the other four providers are instructed to repeat them word for word. The built-in tools may refuse — "the queue is closed" is something to talk about, not an error — and the persona, the rules and the bot's voice are published and versioned together.

Hands over to people properly. Transfers go into mod_callcenter queues, and the agent's screen has popped by the time the phone rings: a PARTY_RINGING event over SSE says who is calling and carries the call's userData, and the bot-phase transcript is already there to read, because the call id is minted before any leg exists and survives the transfer. Agents work in the browser: presence, call control, callbacks — but not the audio, which belongs to the web-sip-phone Chrome extension, a separate repository. It holds the agent's SIP registration with credentials this application issues at sign-in, and it has no dialpad of its own: answering, holding and hanging up are REST calls here, carried to the phone over ESL uuid_phone_event. Supervisors get a live wallboard, the queue view and the roster.

Keeps one record per conversation. A call that a bot answered, handed to a queue and an agent finished is one CDR with one transcript and one recording — not three fragments. Recordings go to a filesystem or to any S3-compatible store.

Speaks two languages, and admits which provider it runs. English and Chinese throughout, interface and bot. One provider answers every call in a deployment, chosen at startup: qwen or doubao inside mainland China, openai or gemini elsewhere, or gateway for a Realtime gateway of your own. A call's language never selects it. Three of those are profiles of one protocol; doubao and gemini each speak a different one and have a client of their own (how a provider is added).

How it fits together

                    ┌──────────── one Go binary ────────────┐
  caller ──▶ FreeSWITCH ──▶ SIP UAS ──▶ provider (Realtime, speech-to-speech)
                 │            │
                 │            └─ flow engine: phases, tools, transfers
                 │
                 ├─ mod_callcenter queues ──▶ agents (browser + web-sip-phone)
                 │
                 └─ ESL ──▶ call registry ──▶ REST + SSE ──▶ web interface
                                                    │
                                              PostgreSQL

FreeSWITCH reads its directory and its mod_callcenter queues from the database, through Lua. The dialplan is static XML, but its rules decide nothing by themselves: each one hands the call to a Lua script that looks the answer up. Adding an extension, a queue or a number is a database change; the switch's own files change only when the routing itself does.

Two design notes worth knowing before reading the code:

  • The domain model is Genesys-lineage. A call aggregates parties; leg events are PARTY_*, call-scoped ones are CALL_*.
  • Every live call is an actor — one goroutine as its sole mutator, snapshots by mailbox. Nothing in internal/telephony locks call state, because there is no shared call state; the AI leg is the exception, holding mutexes over the playback and recording state its own goroutines share.

The full design is in docs/design/, starting with the overview.

Building

make dev-up            # PostgreSQL in Docker
cd web && npm install && npm run build && cd ..
make build             # bin/aicc, with the interface embedded
./bin/aicc useradd -username admin -password '…' -role ADMIN
./bin/aicc

For frontend work, make web-dev runs Vite on 5173 against the API on 8080.

go test -race ./...    # always -race; it has caught real bugs here
make lint              # go vet, gofmt, oxlint
make api-check         # the API contract gate

The HTTP API is spec-first: docs/openapi.json is the single source of truth, and the Go server and the TypeScript client are generated from it. Edit the contract, run make api-generate, then implement. Never the other way round.

Extending it

  • A new voice provider is a profile, not a client: docs/provider-extension.md.
  • A new flow is a JSON document validated at load — see internal/seed/flows/ for a working bilingual one.
  • A new screen follows the design system in web/CLAUDE.md, which is binding rather than advisory.
  • An integration with another product is a service behind the flow's backend URL, never code in this tree: CONTRIBUTING.md.

Status

The human path, the AI path, the product surface and the packaging are built and verified against live FreeSWITCH and live providers.

Performance is not yet a claim this project makes. The design has a capacity budget and a latency target (design 06), and the harness to test them against is in the repository (docs/load-tests.md) — but the benchmark campaign itself is still to come, so treat the budget as an intention rather than a measurement.

What is deliberately not here, and will not be: any cascaded ASR + LLM + TTS pipeline inside this process. That composition belongs in a separate service speaking the same protocol.

License

Apache-2.0. See LICENSE.

About

Self-hosted, API-first AI call center. Realtime voice bots answer every call, human agents take over without a drop. Full REST + SSE API with OpenAPI spec to embed in your product. FreeSWITCH + OpenAI Realtime / Qwen, one Go binary.

Topics

Resources

Contributing

Stars

211 stars

Watchers

8 watching

Forks

Releases

Packages

Contributors

Languages