🟡 SalesBuff — Backend (FastAPI)
The Python service for Pre-call due diligence (citation-grounded Actions + Facts) and On-fly live coaching (real-time tips during a call). Built with FastAPI + asyncio.
Architecture deep-dive (folders, data flow, prompts): see
ARCHITECTURE.md. Domain glossary:../CONTEXT.md.
Features
Pre-call
One request runs a 3-stage pipeline:
- Resolve — figure out the real buyer, contact, seller, meeting motion (expansion vs displacement), and any competitor to beat. (LLM → Tavily search → LLM)
- Research — deep-research each entity on the web; optionally search court records. (Tavily Deep Research + CourtListener)
- Brief — from one shared fact pack, generate Actions (coaching moves) and Facts (evidence dossier) concurrently, then ground every card to a real source URL. (OpenAI)
Because a full run takes ~1–2 minutes, the API is job-based: submit, then poll.
On-fly
Live coaching during a call:
- Session start — extract a structured deal brief from seed context; optional bootstrap Tavily search when pre-call data is thin.
- Transcript chunks — browser sends speech every ~25s or on manual "Get tip now".
- Tip generation — fast LLM pass with conversation memory (compacted summary + raw tail + current chunk), stage/tip-type metadata, dedup filters.
- Reactive research — async Tavily quick (one search) or deep (multi-query synthesis) when the coach flags a gap; never blocks immediate tips.
- Session end — optional JSON log of every chunk, candidate, rejection, and tip.
On-fly uses Core only (CoreContainer) — it never imports the pre-call pipeline.
Two ways to use it
It's one package with feature extras you mix and match:
| Install | You get |
|---|---|
pip install salesbuff |
Shared core — LLM + search clients, config, models. |
pip install "salesbuff[precall]" |
The pre-call due-diligence feature (SalesBuff, research_once). |
pip install "salesbuff[onfly]" |
Live coaching — Core only; no pre-call dependencies pulled in. |
pip install "salesbuff[api]" |
The FastAPI host layer + the salesbuff-serve command. |
pip install "salesbuff[precall,api]" |
Pre-call and host it as an API (includes On-fly routes). |
pip install "salesbuff[all]" |
Everything. |
Extras are additive — they layer optional dependencies onto the core. Pre-call SDK symbols load lazily so
salesbuff[onfly]never forces pre-call-only deps likerapidfuzz.
Host it from the terminal (no code)
pip install "salesbuff[precall,api]"
# set OPENAI_API_KEY + TAVILY_API_KEY in your env (or a .env), then:
salesbuff-serve --port 8000 # defaults to $PORT or 8000
As a library (SDK)
from salesbuff import SalesBuff
async with SalesBuff(openai_api_key="sk-...", tavily_api_key="tvly-...") as sb:
result = await sb.research("Meeting the VP of Ops at Acme Health next week...")
print(result.brief) # Actions brief (or None)
print(result.facts) # Facts dossier (or None)
One-shot synchronous helper (for scripts/notebooks):
from salesbuff import research_once
result = research_once(
"Expanding our rollout at Acme Health...",
openai_api_key="sk-...",
tavily_api_key="tvly-...",
)
courtlistener_token=... is optional (enables court-record lookups). Advanced
tuning (research_concurrency, openai_model, …) can be passed as keyword args.
The FastAPI service is just a thin host over this same SDK — both share one engine, so they never drift.
Setup (local)
Run everything from the SalesBuff/ directory (the parent of the salesbuff
package).
cd SalesBuff
pip install -r salesbuff/requirements.txt
cp salesbuff/.env.example salesbuff/.env # fill in your keys
uvicorn salesbuff.api:app --port 8000 --reload
Health check: curl http://127.0.0.1:8000/health → {"status":"ok"}.
There's also a CLI for quick local pre-call runs:
python -m salesbuff "Meeting the VP of Ops at Acme Health next week..."
Environment variables
Set in salesbuff/.env locally, or in the Render dashboard in production.
See salesbuff/.env.example for the full template.
Required
| Var | Purpose |
|---|---|
OPENAI_API_KEY |
Brief + facts + entity resolution + On-fly tips |
TAVILY_API_KEY |
Web search + deep research + On-fly reactive lookup |
Core / pre-call
| Var | Default | Purpose |
|---|---|---|
OPENAI_MODEL |
gpt-4o-mini |
Default chat model |
COURTLISTENER_TOKEN |
— | Legal/court records (optional) |
USAGE_MAX |
25 |
Shared-quota runs before users must bring their own keys |
USAGE_CURRENT |
0 |
Runs already consumed (in-memory counter) |
MAX_CASES_PER_ENTITY |
8 |
Court cases kept per company |
TAVILY_MAX_RESULTS |
5 |
Web search results |
TAVILY_SEARCH_DEPTH |
basic |
Tavily search depth |
LEGAL_BATCH_SIZE |
10 |
Court cases processed per batch |
TAVILY_RESEARCH_MODEL |
mini |
Tavily Deep Research model (pre-call) |
TAVILY_RESEARCH_CONCURRENCY |
6 |
Max in-flight deep-research tasks |
TAVILY_RESEARCH_POLL_INTERVAL |
4.0 |
Seconds between poll attempts |
TAVILY_RESEARCH_MAX_POLLS |
60 |
Max polls before timeout |
TAVILY_RESEARCH_OUTPUT_LENGTH |
standard |
Deep research output size |
On-fly live coaching
| Var | Default | Purpose |
|---|---|---|
ONFLY_TIP_MODEL |
OPENAI_MODEL |
Fast model for hot-path JSON tips |
ONFLY_COMPACTION_MODEL |
OPENAI_MODEL |
Model for async transcript compaction |
ONFLY_COMPACT_CHUNK_THRESHOLD |
10 |
Compact when unsummarized chunks exceed this |
ONFLY_COMPACT_TOKEN_THRESHOLD |
3000 |
…or when raw unsummarized text exceeds this |
ONFLY_SUMMARY_MAX_TOKENS |
3000 |
Target size for compacted summary |
ONFLY_RAW_TAIL_CHUNKS |
3 |
Recent chunks kept verbatim |
ONFLY_REACTIVE_SEARCH_MAX |
3 |
Max quick reactive searches per session |
ONFLY_DEEP_RESEARCH_MAX |
2 |
Max deep (multi-query) research runs per session |
ONFLY_DEEP_MAX_QUERIES |
3 |
Tavily queries per deep research run |
ONFLY_TIP_TYPE_WINDOW |
3 |
Dedup window for repeated tip types |
ONFLY_SESSION_LOG |
1 |
Dump JSON session log on end (0/false to disable) |
ONFLY_LOG_DIR |
salesbuff/onfly_log/ |
Directory for session log files |
API
Pre-call
| Method | Path | Description |
|---|---|---|
POST |
/research |
Submit { "prompt": "...", "keys"?: {openai, tavily, courtlistener} } → { request_id, status, usage } |
GET |
/research/{id} |
Poll → { status, stage, progress, brief, facts, warnings, error } |
GET |
/usage |
{ used, limit, remaining } |
GET |
/health |
Liveness probe |
- Without user
keys, runs count against the sharedUSAGE_MAX(HTTP 429 when exhausted). - With valid user
keys, the run bypasses the quota and uses a throwaway client; bad keys return 400 with a clear message.
On-fly
| Method | Path | Description |
|---|---|---|
POST |
/onfly/sessions |
Create session { pasted_context, spoken_setup, precall_brief?, precall_facts?, max_tips, tts_enabled } → { session_id, expires_at } |
POST |
/onfly/sessions/{id}/chunks |
Ingest transcript chunk { text, manual? } |
GET |
/onfly/sessions/{id} |
Current session state (tips, stage, etc.) |
GET |
/onfly/sessions/{id}/events |
SSE stream of new tips |
DELETE |
/onfly/sessions/{id} |
End session; returns { ended, log } path if logging enabled |
Sessions are in-memory (2-hour TTL). Keep one backend instance in production.
Project structure
salesbuff/
├── api.py # FastAPI app: pre-call jobs + mounts /onfly
├── core.py # CoreContainer — shared LLM + search (no feature deps)
├── container.py # Container extends Core — wires pre-call pipeline (DI)
├── pipeline.py # orchestrates resolve → research → brief
├── config.py # env → frozen Config
├── client.py # SDK entry (SalesBuff, research_once)
├── ports/ # abstract interfaces (LLM, web, legal)
├── adapters/ # OpenAI, Tavily, CourtListener implementations
├── precall/ # pre-call feature: resolve, web, legal, deep, brief, facts
├── onfly/ # live coaching: coach, session, prompts, api routes
├── domain/ # prompts, framing, grounding rules, source tiers, YAML loader
├── domain_logic_sales/ # editable YAML: categories, questions, ranking, compliance
└── models/ # typed shapes: entities, findings, brief, facts, onfly
The ports/adapters split is what lets a user bring their own keys at runtime (a different adapter, same pipeline) and makes the LLM/search providers swappable. Core is shared; precall and onfly are separate features that both build on Core.
Deploy (Render)
Use the blueprint at the repo root (../render.yaml) or set
manually:
- Root Directory:
SalesBuff - Build:
pip install -r salesbuff/requirements.txt(orpip install ".[api]"— same result viapyproject.toml) - Start:
uvicorn salesbuff.api:app --host 0.0.0.0 --port $PORT - Health check path:
/health - Instances: 1 — the job store, usage counter, and live sessions live in memory, so multiple instances would split state.
Set OPENAI_API_KEY and TAVILY_API_KEY (and optionally COURTLISTENER_TOKEN)
as dashboard secrets. On-fly vars are optional — defaults work out of the box.
Metadata
Release files for salesbuff 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| salesbuff-0.2.0.tar.gz | 68.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| salesbuff-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 150.7 kB
Release files / salesbuff-0.2.0.tar.gz
| Download URL | salesbuff-0.2.0.tar.gz |
|---|---|
| Size | 68.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
75e7ebc7de32374956de6a4e5726e441a0ec759d59979e7989fce071877f7434
|
|
BLAKE2b-256 checksum How to use checksums |
2402eb27ae570fec3cd08c9d83ffaa00973048f141fad407500ff5afee09a573
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / salesbuff-0.2.0-py3-none-any.whl
| Download URL | salesbuff-0.2.0-py3-none-any.whl |
|---|---|
| Size | 81.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1a8ac9c3f026170b65e0eb2b621688b42442055fbe695542db40ee35e31fcabd
|
|
BLAKE2b-256 checksum How to use checksums |
da4e0528b2499488b94bd5f8059fd41780f5e42b3dee06e539134ad0f8d91e3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|