@pinecall/skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/build.mjs +204 -0
- package/package.json +29 -0
- package/skills/pinecall-concepts/SKILL.md +41 -0
- package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
- package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
- package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
- package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
- package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
- package/skills/pinecall-examples/SKILL.md +59 -0
- package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
- package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
- package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
- package/skills/pinecall-examples/references/examples/index.md +183 -0
- package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
- package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
- package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
- package/skills/pinecall-guides/SKILL.md +68 -0
- package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
- package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
- package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
- package/skills/pinecall-guides/references/guides/events.md +677 -0
- package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
- package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
- package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
- package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
- package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
- package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
- package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
- package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
- package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
- package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
- package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
- package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
- package/skills/pinecall-quickstart/SKILL.md +54 -0
- package/skills/pinecall-quickstart/references/index.md +123 -0
- package/skills/pinecall-quickstart/references/quickstart.md +185 -0
- package/skills/pinecall-reference/SKILL.md +43 -0
- package/skills/pinecall-reference/references/reference/cli.md +578 -0
- package/skills/pinecall-reference/references/reference/events.md +366 -0
- package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
- package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
- package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
- package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
- package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
- package/skills/pinecall-sdk-api/SKILL.md +56 -0
- package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
- package/skills/pinecall-sdk-api/references/api/call.md +324 -0
- package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
- package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
- package/skills/pinecall-security/SKILL.md +37 -0
- package/skills/pinecall-security/references/security.md +138 -0
- package/skills/pinecall-web-chat/SKILL.md +38 -0
- package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
- package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
- package/skills/pinecall-web-components/SKILL.md +37 -0
- package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
- package/skills/pinecall-web-voice/SKILL.md +40 -0
- package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
- package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
- package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
- package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
- package/skills/pinecall-web-widget/SKILL.md +41 -0
- package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
- package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
- package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
- package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
- package/skills/pinecall-web-widget/references/web/widget/use-voice-session-hook.md +130 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Human Takeover"
|
|
3
|
+
description: "Pause the AI agent so a human can intervene in real-time conversations."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Human Takeover
|
|
7
|
+
|
|
8
|
+
The human-in-the-loop system lets a human operator take over a conversation from the AI agent. While paused, messages still flow to the SDK — the LLM just doesn't respond. The human sends messages through the SDK, and the AI resumes with full context when done.
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
AI_ACTIVE ──(pause)──▶ HUMAN_ACTIVE ──(resume)──▶ AI_ACTIVE
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
When paused:
|
|
17
|
+
- Incoming messages are forwarded to the SDK (with `paused: true`)
|
|
18
|
+
- The LLM **does not generate responses** — no auto-reply
|
|
19
|
+
- Voice notes are still transcribed (so the human can read them)
|
|
20
|
+
- Human messages are added to LLM history for seamless context on resume
|
|
21
|
+
|
|
22
|
+
## Pause granularity
|
|
23
|
+
|
|
24
|
+
Three levels, all through the same API:
|
|
25
|
+
|
|
26
|
+
| Method | Scope | Use case |
|
|
27
|
+
|--------|-------|----------|
|
|
28
|
+
| `agent.pause(sessionId)` | One conversation | "I'll handle this customer" |
|
|
29
|
+
| `agent.pause({ contact })` | All sessions with a contact | "This person needs human attention" |
|
|
30
|
+
| `agent.pause()` | Entire agent | "Turn off the AI completely" |
|
|
31
|
+
|
|
32
|
+
Resume follows the same pattern. Global `agent.resume()` clears all levels.
|
|
33
|
+
|
|
34
|
+
## Full example: WhatsApp customer support
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
38
|
+
|
|
39
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
40
|
+
|
|
41
|
+
const support = pc.agent("support", {
|
|
42
|
+
language: "en",
|
|
43
|
+
llm: "openai/gpt-5-chat-latest",
|
|
44
|
+
prompt: "You are a helpful support agent.",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
support.addWhatsapp({
|
|
48
|
+
phoneNumberId: process.env.WA_PHONE_ID!,
|
|
49
|
+
accessToken: process.env.WA_TOKEN!,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Track active sessions for the dashboard
|
|
53
|
+
const sessions = new Map<string, { contact: string; name: string }>();
|
|
54
|
+
|
|
55
|
+
support.on("whatsapp.sessionStarted", (event) => {
|
|
56
|
+
sessions.set(event.sessionId as string, {
|
|
57
|
+
contact: event.contactPhone as string,
|
|
58
|
+
name: event.contactName as string,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
support.on("whatsapp.message", (event) => {
|
|
63
|
+
const sessionId = event.sessionId as string;
|
|
64
|
+
const paused = event.paused as boolean;
|
|
65
|
+
|
|
66
|
+
if (paused) {
|
|
67
|
+
// AI is paused — route to human dashboard
|
|
68
|
+
console.log(`[PAUSED] ${event.name}: ${event.text}`);
|
|
69
|
+
notifyHumanDashboard(sessionId, event);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Normal: AI handles automatically
|
|
74
|
+
console.log(`[AI] ${event.name}: ${event.text}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ── Dashboard API (e.g. Express routes) ──
|
|
78
|
+
|
|
79
|
+
// Human takes over a session
|
|
80
|
+
app.post("/api/takeover/:sessionId", (req, res) => {
|
|
81
|
+
support.pause(req.params.sessionId);
|
|
82
|
+
res.json({ ok: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Human sends a message
|
|
86
|
+
app.post("/api/send/:sessionId", (req, res) => {
|
|
87
|
+
support.sendMessage({
|
|
88
|
+
sessionId: req.params.sessionId,
|
|
89
|
+
text: req.body.text,
|
|
90
|
+
});
|
|
91
|
+
res.json({ ok: true });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Human hands back to AI
|
|
95
|
+
app.post("/api/handback/:sessionId", (req, res) => {
|
|
96
|
+
support.resume(req.params.sessionId);
|
|
97
|
+
res.json({ ok: true });
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Events
|
|
102
|
+
|
|
103
|
+
| Event | When | Data |
|
|
104
|
+
|-------|------|------|
|
|
105
|
+
| `session.paused` | After `agent.pause()` | `{ sessionId?, contact? }` |
|
|
106
|
+
| `session.resumed` | After `agent.resume()` | `{ sessionId?, contact? }` |
|
|
107
|
+
| `whatsapp.message` | Message received (always) | `{ paused: true }` when paused |
|
|
108
|
+
| `whatsapp.response` | Response sent | `{ source: "human" }` when human |
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
support.on("session.paused", (event) => {
|
|
112
|
+
console.log(`⏸ Paused: session=${event.sessionId}`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
support.on("session.resumed", (event) => {
|
|
116
|
+
console.log(`▶ Resumed: session=${event.sessionId}`);
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Protocol messages
|
|
121
|
+
|
|
122
|
+
These are the wire messages exchanged between SDK and server. You don't need to use these directly — the SDK methods handle them.
|
|
123
|
+
|
|
124
|
+
### `session.pause` (SDK → Server)
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"event": "session.pause",
|
|
129
|
+
"agent_id": "support",
|
|
130
|
+
"session_id": "wa-abc123"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Omit `session_id` and send `contact` for contact-level pause. Omit both for global.
|
|
135
|
+
|
|
136
|
+
### `session.resume` (SDK → Server)
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"event": "session.resume",
|
|
141
|
+
"agent_id": "support",
|
|
142
|
+
"session_id": "wa-abc123"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `session.send` (SDK → Server)
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"event": "session.send",
|
|
151
|
+
"agent_id": "support",
|
|
152
|
+
"session_id": "wa-abc123",
|
|
153
|
+
"text": "I'm a human agent. Let me help."
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Confirmations (Server → SDK)
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{ "event": "session.paused", "agent_id": "support", "session_id": "wa-abc123" }
|
|
161
|
+
{ "event": "session.resumed", "agent_id": "support", "session_id": "wa-abc123" }
|
|
162
|
+
{ "event": "session.sent", "agent_id": "support", "session_id": "wa-abc123" }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Context preservation
|
|
166
|
+
|
|
167
|
+
Human messages are recorded in the LLM conversation history as `assistant` messages. When the AI resumes, it has full context of what the human said. The conversation flows naturally without the customer noticing the handover.
|
|
168
|
+
|
|
169
|
+
## Channel support
|
|
170
|
+
|
|
171
|
+
| Channel | Pause/Resume | Send as Human | Status |
|
|
172
|
+
|---------|-------------|---------------|--------|
|
|
173
|
+
| WhatsApp | ✅ | ✅ | Available now |
|
|
174
|
+
| Voice | ✅ (planned) | via `inject_text` | Roadmap |
|
|
175
|
+
| Chat | ✅ (planned) | ✅ (planned) | Roadmap |
|
|
176
|
+
|
|
177
|
+
The pause state data model already supports voice call IDs and chat session IDs — the routing just needs to be wired in `LLMHandler.on_user_message()`.
|
|
178
|
+
|
|
179
|
+
## What's next
|
|
180
|
+
|
|
181
|
+
- [WhatsApp Dashboard example](/examples/whatsapp-dashboard) — runnable example with React UI
|
|
182
|
+
- [WhatsApp guide](/guides/whatsapp) — set up the WhatsApp channel
|
|
183
|
+
- [Events reference](/reference/events) — all event data shapes
|
|
184
|
+
- [Agent API](/api/agent) — `pause()`, `resume()`, `sendMessage()` reference
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Inbound Voice"
|
|
3
|
+
description: "Build a voice agent that answers phone calls."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inbound Voice
|
|
7
|
+
|
|
8
|
+
This guide walks through building a phone agent end-to-end: registering a phone number, greeting callers, handling tool calls, and ending the conversation gracefully.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- A Pinecall API key
|
|
13
|
+
- A phone number on your Pinecall account (purchase one or port one — see [REST API → fetchPhones](/reference/rest-api))
|
|
14
|
+
- Node.js ≥ 18
|
|
15
|
+
|
|
16
|
+
## The minimum viable phone agent
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
20
|
+
|
|
21
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
22
|
+
|
|
23
|
+
const receptionist = pc.agent("receptionist", {
|
|
24
|
+
prompt: "You are the receptionist for Acme Corp. Be brief and warm.",
|
|
25
|
+
llm: "openai/gpt-5-chat-latest",
|
|
26
|
+
voice: "elevenlabs/sarah",
|
|
27
|
+
stt: "deepgram/flux",
|
|
28
|
+
language: "en",
|
|
29
|
+
phoneNumber: "+13186330963",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
receptionist.on("call.started", (call) => {
|
|
33
|
+
if (call.direction === "inbound") {
|
|
34
|
+
call.say("Thanks for calling Acme. How can I help?");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
receptionist.on("call.ended", (call, reason) => {
|
|
39
|
+
console.log(`[${call.id}] ${reason} (${call.duration}s)`);
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That's a working phone agent. The server handles audio transport, STT, the LLM, TTS, and turn detection.
|
|
44
|
+
|
|
45
|
+
## Greeting
|
|
46
|
+
|
|
47
|
+
There are two ways to greet inbound callers:
|
|
48
|
+
|
|
49
|
+
### Option 1: `greeting` in `agent()` (declarative)
|
|
50
|
+
|
|
51
|
+
If you use `pc.agent()`, the `greeting` field handles everything — no event handler needed:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const agent = pc.agent("receptionist", {
|
|
55
|
+
voice: "elevenlabs/sarah",
|
|
56
|
+
llm: "openai/gpt-5-chat-latest",
|
|
57
|
+
stt: "deepgram/flux",
|
|
58
|
+
prompt: "You are a receptionist for Acme Corp.",
|
|
59
|
+
phoneNumber: "+13186330963",
|
|
60
|
+
|
|
61
|
+
// Static
|
|
62
|
+
greeting: "Thanks for calling Acme. How can I help?",
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The greeting is added to LLM history by default, so the model knows what was said. You can disable that:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
greeting: { text: "Welcome to Acme.", addToHistory: false }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or make it dynamic per-call:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
greeting: async (call) => {
|
|
76
|
+
const customer = await db.findByPhone(call.from);
|
|
77
|
+
return customer ? `Hi ${customer.name}!` : "Hi! How can I help?";
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Option 2: `call.say()` in `call.started` (programmatic)
|
|
82
|
+
|
|
83
|
+
If you use `pc.agent()`, handle the greeting yourself:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
agent.on("call.started", (call) => {
|
|
87
|
+
call.say("Hello! How can I help you today?");
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use this when you need logic beyond what `greeting` supports — multiple says, conditional behavior, loading data before speaking, etc.
|
|
92
|
+
|
|
93
|
+
> **Outbound calls** use a different mechanism: pass `greeting` in `agent.dial()`. The server speaks it as soon as the callee picks up. See [Outbound Calls](/guides/outbound-calls).
|
|
94
|
+
|
|
95
|
+
## Adding tools
|
|
96
|
+
|
|
97
|
+
Define tools with `tool()` and Zod schemas. The SDK auto-executes them when the LLM calls them:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { Pinecall, tool } from "@pinecall/sdk";
|
|
101
|
+
import { z } from "zod";
|
|
102
|
+
|
|
103
|
+
const lookupOrder = tool({
|
|
104
|
+
name: "lookupOrder",
|
|
105
|
+
description: "Look up an order by ID",
|
|
106
|
+
schema: z.object({ orderId: z.string() }),
|
|
107
|
+
execute: async ({ orderId }) => {
|
|
108
|
+
const order = await db.orders.findOne(orderId);
|
|
109
|
+
return order ?? { error: "not_found" };
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const transferToHuman = tool({
|
|
114
|
+
name: "transferToHuman",
|
|
115
|
+
description: "Escalate to a human specialist.",
|
|
116
|
+
schema: z.object({}),
|
|
117
|
+
execute: async (_, call) => {
|
|
118
|
+
call.say("One moment, connecting you to a specialist.");
|
|
119
|
+
call.forward("+15558675309");
|
|
120
|
+
return { transferred: true };
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const endCall = tool({
|
|
125
|
+
name: "endCall",
|
|
126
|
+
description: "End the call when the customer says goodbye.",
|
|
127
|
+
schema: z.object({}),
|
|
128
|
+
execute: async (_, call) => {
|
|
129
|
+
call.say("Have a great day. Goodbye!");
|
|
130
|
+
call.once("bot.finished", () => call.hangup());
|
|
131
|
+
return { ended: true };
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const agent = pc.agent("receptionist", {
|
|
136
|
+
prompt: "You are a receptionist. Look up orders when asked.",
|
|
137
|
+
llm: "openai/gpt-5-chat-latest",
|
|
138
|
+
voice: "elevenlabs/sarah",
|
|
139
|
+
stt: "deepgram/flux",
|
|
140
|
+
language: "en",
|
|
141
|
+
phoneNumber: "+13186330963",
|
|
142
|
+
tools: [lookupOrder, transferToHuman, endCall],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
agent.on("call.started", (call) => call.say("Thanks for calling. How can I help?"));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
See [Tools and Functions](/guides/tools-and-functions) for the full pattern.
|
|
149
|
+
|
|
150
|
+
## Automatic call endings
|
|
151
|
+
|
|
152
|
+
- When the user hangs up — emits `call.ended` with reason `hangup`
|
|
153
|
+
- After `max_duration_seconds` (default: 10 minutes) — reason `max_duration`
|
|
154
|
+
- After `idle_timeout_seconds` of silence (default: 60s) — reason `idle_timeout`
|
|
155
|
+
|
|
156
|
+
See [Session Limits](/reference/session-limits) for tuning these.
|
|
157
|
+
|
|
158
|
+
## Listening for live transcripts
|
|
159
|
+
|
|
160
|
+
Use `bot.word` and `user.message` events to build a live transcript UI or log the conversation as it happens:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
agent.on("user.message", (event, call) => {
|
|
164
|
+
console.log(`[${call.id}] User: ${event.text}`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
let currentBotMessage = "";
|
|
168
|
+
agent.on("bot.speaking", () => { currentBotMessage = ""; });
|
|
169
|
+
agent.on("bot.word", (event, call) => {
|
|
170
|
+
currentBotMessage += event.word + " ";
|
|
171
|
+
process.stdout.write(`\r[${call.id}] Bot: ${currentBotMessage}`);
|
|
172
|
+
});
|
|
173
|
+
agent.on("bot.finished", () => console.log());
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## After the call ends
|
|
177
|
+
|
|
178
|
+
When `call.ended` fires, the `Call` object is fully populated:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
agent.on("call.ended", async (call, reason) => {
|
|
182
|
+
await db.calls.create({
|
|
183
|
+
id: call.id,
|
|
184
|
+
from: call.from,
|
|
185
|
+
to: call.to,
|
|
186
|
+
duration: call.duration,
|
|
187
|
+
reason,
|
|
188
|
+
transcript: call.transcript,
|
|
189
|
+
messages: call.messages, // full LLM history including tool calls
|
|
190
|
+
startedAt: call.startedAt,
|
|
191
|
+
endedAt: call.endedAt,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## What's next
|
|
197
|
+
|
|
198
|
+
- [Outbound calls](/guides/outbound-calls) — make programmatic outbound calls
|
|
199
|
+
- [Tools and Functions](/guides/tools-and-functions) — let the agent take actions
|
|
200
|
+
- [Dev mode](/guides/dev-mode) — share one number between prod and any number of devs
|
|
201
|
+
- [`Call` API reference](/api/call) — every method
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Knowledge bases (RAG)
|
|
3
|
+
description: Tutorial — ground a voice or chat agent on your own documents with retrieval-augmented generation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Knowledge bases (RAG)
|
|
7
|
+
|
|
8
|
+
A **knowledge base** is a set of documents your agent answers from. You upload the
|
|
9
|
+
documents once, attach the knowledge base to an agent, and on every turn the
|
|
10
|
+
server retrieves the most relevant chunks for what the user said and injects them
|
|
11
|
+
into the prompt — no fine-tuning, no vector database to run yourself.
|
|
12
|
+
|
|
13
|
+
It works the same for **voice** and **chat**.
|
|
14
|
+
|
|
15
|
+
This tutorial builds a support agent grounded in your help docs, end to end.
|
|
16
|
+
|
|
17
|
+
> **Paid feature.** Knowledge bases require a paid plan (**Starter** or higher). On
|
|
18
|
+
> a free trial, creating or using a knowledge base is blocked — both the dashboard
|
|
19
|
+
> and the CLI will prompt you to upgrade. Everything else below assumes a paid org.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Step 1 — Create a knowledge base
|
|
24
|
+
|
|
25
|
+
You can do this in the dashboard or from the CLI. Either way you get a **knowledge
|
|
26
|
+
base id** (e.g. `kb_1a2b3c`) — you'll attach that to your agent.
|
|
27
|
+
|
|
28
|
+
### Option A — Dashboard
|
|
29
|
+
|
|
30
|
+
1. Open [platform.pinecall.io](https://platform.pinecall.io) → **Knowledge**.
|
|
31
|
+
2. Click **New knowledge base**, give it a name (e.g. "Help docs"), and create it.
|
|
32
|
+
3. The knowledge base page shows its **id** (copyable) — keep it for Step 3.
|
|
33
|
+
|
|
34
|
+
### Option B — CLI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pinecall knowledge create "Help docs"
|
|
38
|
+
# ✓ Created knowledge base Help docs
|
|
39
|
+
# id: kb_1a2b3c
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Step 2 — Add your documents
|
|
45
|
+
|
|
46
|
+
Upload Markdown or text files (`.md`, `.markdown`, `.txt`). Each upload re-trains
|
|
47
|
+
the index automatically.
|
|
48
|
+
|
|
49
|
+
### Dashboard
|
|
50
|
+
|
|
51
|
+
On the knowledge base page, drag files into the uploader (or paste text). You'll
|
|
52
|
+
see each document listed; click one to read it.
|
|
53
|
+
|
|
54
|
+
### CLI
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Upload local files — paths are kept, so re-pushing updates in place (idempotent)
|
|
58
|
+
pinecall knowledge push kb_1a2b3c ./help/*.md
|
|
59
|
+
|
|
60
|
+
# List what's in the knowledge base
|
|
61
|
+
pinecall knowledge docs kb_1a2b3c
|
|
62
|
+
|
|
63
|
+
# Check what the agent will retrieve for a question — retrieval only, no LLM
|
|
64
|
+
pinecall knowledge query kb_1a2b3c "how do I reset my password"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See the [CLI reference](/reference/cli) for every `pinecall knowledge` command.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Step 3 — Build the agent
|
|
72
|
+
|
|
73
|
+
Pass the knowledge base id as `knowledgeBase`. Use the **`{{RAG_CONTEXT}}`** prompt
|
|
74
|
+
variable to control exactly where the retrieved documents are placed:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
78
|
+
|
|
79
|
+
const pc = new Pinecall();
|
|
80
|
+
|
|
81
|
+
const agent = pc.agent("support", {
|
|
82
|
+
voice: "elevenlabs/sarah",
|
|
83
|
+
llm: "anthropic/claude-haiku-4-5",
|
|
84
|
+
language: "en",
|
|
85
|
+
|
|
86
|
+
// Attach the knowledge base from Step 1
|
|
87
|
+
knowledgeBase: "kb_1a2b3c",
|
|
88
|
+
|
|
89
|
+
prompt: `You are a friendly support agent for Acme.
|
|
90
|
+
Answer the customer using ONLY the help documentation below. If the answer
|
|
91
|
+
isn't there, say you're not sure and offer to create a ticket — never guess.
|
|
92
|
+
|
|
93
|
+
{{RAG_CONTEXT}}
|
|
94
|
+
|
|
95
|
+
Keep replies short and conversational.`,
|
|
96
|
+
|
|
97
|
+
greeting: "Hi! You've reached Acme support — how can I help?",
|
|
98
|
+
phoneNumber: "+14155551234", // omit for chat-only
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That's the whole integration. Before each LLM turn, the server:
|
|
103
|
+
|
|
104
|
+
1. takes the user's latest message,
|
|
105
|
+
2. retrieves the top matching chunks from `kb_1a2b3c`,
|
|
106
|
+
3. substitutes them into `{{RAG_CONTEXT}}` (or appends them if you omit the
|
|
107
|
+
variable), and
|
|
108
|
+
4. runs the LLM with that grounded prompt.
|
|
109
|
+
|
|
110
|
+
### Where the context goes — `{{RAG_CONTEXT}}`
|
|
111
|
+
|
|
112
|
+
- **Prompt contains `{{RAG_CONTEXT}}`** → retrieved docs are inserted exactly there.
|
|
113
|
+
- **Prompt omits `{{RAG_CONTEXT}}`** → retrieved docs are appended automatically, so
|
|
114
|
+
a knowledge base works out of the box.
|
|
115
|
+
- **Nothing relevant / no knowledge base** → `{{RAG_CONTEXT}}` resolves to empty and
|
|
116
|
+
the agent behaves like a normal agent.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Step 4 — Run and test it
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Start the agent
|
|
124
|
+
pinecall run support.ts
|
|
125
|
+
|
|
126
|
+
# In another terminal, chat with it (text)
|
|
127
|
+
pinecall chat support
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Ask it something covered by your docs — the answer should come straight from them.
|
|
131
|
+
Call the number to test the same behaviour by voice. To sanity-check retrieval
|
|
132
|
+
without spending an LLM call, use `pinecall knowledge query`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
- **Retrieval is hybrid** — semantic embeddings *fused with* a keyword (BM25)
|
|
139
|
+
lane. Phrase questions naturally, and exact terms or acronyms (e.g. `TTS` vs
|
|
140
|
+
`STT`, a product name, an error code) still match precisely instead of blurring
|
|
141
|
+
into similar wording.
|
|
142
|
+
- **Documents are chunked by heading** (section-aligned, never mid-section), so
|
|
143
|
+
well-structured Markdown retrieves best.
|
|
144
|
+
- **Sources event** — when retrieval runs, the server emits a `docs.sources` event
|
|
145
|
+
on the data channel with the documents it used (title, heading, score), so a
|
|
146
|
+
browser UI can show citations next to the answer.
|
|
147
|
+
- The retrieved context counts toward the LLM's context window — keep documents
|
|
148
|
+
focused.
|
|
149
|
+
|
|
150
|
+
## Keeping the knowledge base in sync
|
|
151
|
+
|
|
152
|
+
Re-push whenever the source documents change — `push` upserts by path, so it's safe
|
|
153
|
+
to run repeatedly:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pinecall knowledge push kb_1a2b3c ./help/*.md # updates changed docs, adds new ones
|
|
157
|
+
pinecall knowledge rm kb_1a2b3c <docId> # remove one
|
|
158
|
+
pinecall knowledge reindex kb_1a2b3c # force a rebuild
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Limits
|
|
162
|
+
|
|
163
|
+
- Paid plans only (Starter, Pro, Enterprise).
|
|
164
|
+
- Document formats: `.md`, `.markdown`, `.txt`. Convert PDFs/Docx to text first.
|
|
165
|
+
- A knowledge base belongs to your organization; attach it by id to any of your
|
|
166
|
+
agents.
|