Skip to content

Commit 86b7196

Browse files
Merge pull request #202 from restackio/agentVapi
Add Agent with telephony - vapi
2 parents 44ec8e9 + bf0d70a commit 86b7196

File tree

32 files changed

+539
-49
lines changed

32 files changed

+539
-49
lines changed

agent_apis/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Restack AI - Agent with Third Party APIs
1+
# Restack AI - Agent with third party APIs
22

3-
This repository contains an agent with Third Party APIs
3+
This repository contains an agent with third party APIs
44
It demonstrates how to set up a multi steps workflow with Weather API and OpenAI.
55

66
## Prerequisites

agent_chat/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Restack AI - Agent Chat
1+
# Restack AI - Agent with chat
22

3-
This repository contains a an agent chat for Restack.
3+
This repository contains an agent with chat for Restack.
44
It demonstrates how to set up a workflow to have a conversation with an AI agent.
55

66
## Prerequisites

agent_rag/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Restack AI - Agent Rag
1+
# Restack AI - Agent with RAG
22

33
This repository demonstrates how to set up a basic agent with RAG.
44

agent_stream/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Restack AI - Agent with Stream
1+
# Restack AI - Agent with stream
22

33
Build an Agent user can chat with and return streaming response.
44

agent_telephony/twilio/agent_twilio/src/agents/agent.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
class MessagesEvent(BaseModel):
1515
messages: list[Message]
1616

17+
1718
class EndEvent(BaseModel):
1819
end: bool
1920

21+
2022
class CallInput(BaseModel):
2123
phone_number: str
2224

25+
2326
@agent.defn()
2427
class AgentTwilio:
2528
def __init__(self) -> None:
@@ -65,10 +68,9 @@ async def end(self, end: EndEvent) -> EndEvent:
6568
log.info("Received end")
6669
self.end = True
6770
return end
68-
71+
6972
@agent.run
7073
async def run(self) -> None:
71-
7274
room = await agent.step(function=livekit_room)
7375
self.room_id = room.name
7476

agent_telephony/twilio/livekit-trunk-setup/twilio_trunk.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def get_env_var(var_name):
1515
exit(1)
1616
return value
1717

18+
1819
def create_livekit_trunk(client, sip_uri):
1920
domain_name = f"livekit-trunk-{os.urandom(4).hex()}.pstn.twilio.com"
2021
trunk = client.trunking.v1.trunks.create(
@@ -31,20 +32,17 @@ def create_livekit_trunk(client, sip_uri):
3132
logging.info("Created new LiveKit Trunk.")
3233
return trunk
3334

35+
3436
def create_inbound_trunk(phone_number):
35-
trunk_data = {
36-
"trunk": {
37-
"name": "Inbound LiveKit Trunk",
38-
"numbers": [phone_number]
39-
}
40-
}
37+
trunk_data = {"trunk": {"name": "Inbound LiveKit Trunk", "numbers": [phone_number]}}
4138
with open("inbound_trunk.json", "w") as f:
4239
json.dump(trunk_data, f, indent=4)
4340

4441
result = subprocess.run(
4542
["lk", "sip", "inbound", "create", "inbound_trunk.json"],
4643
capture_output=True,
47-
text=True, check=False
44+
text=True,
45+
check=False,
4846
)
4947

5048
if result.returncode != 0:
@@ -59,23 +57,21 @@ def create_inbound_trunk(phone_number):
5957
logging.error("Could not find inbound trunk SID in output.")
6058
return None
6159

60+
6261
def create_dispatch_rule(trunk_sid):
6362
dispatch_rule_data = {
6463
"name": "Inbound Dispatch Rule",
6564
"trunk_ids": [trunk_sid],
66-
"rule": {
67-
"dispatchRuleIndividual": {
68-
"roomPrefix": "call-"
69-
}
70-
}
65+
"rule": {"dispatchRuleIndividual": {"roomPrefix": "call-"}},
7166
}
7267
with open("dispatch_rule.json", "w") as f:
7368
json.dump(dispatch_rule_data, f, indent=4)
7469

7570
result = subprocess.run(
7671
["lk", "sip", "dispatch-rule", "create", "dispatch_rule.json"],
7772
capture_output=True,
78-
text=True, check=False
73+
text=True,
74+
check=False,
7975
)
8076

8177
if result.returncode != 0:
@@ -84,6 +80,7 @@ def create_dispatch_rule(trunk_sid):
8480

8581
logging.info(f"Dispatch rule created: {result.stdout}")
8682

83+
8784
def main():
8885
load_dotenv()
8986
logging.basicConfig(level=logging.INFO)
@@ -98,7 +95,7 @@ def main():
9895
existing_trunks = client.trunking.v1.trunks.list()
9996
livekit_trunk = next(
10097
(trunk for trunk in existing_trunks if trunk.friendly_name == "LiveKit Trunk"),
101-
None
98+
None,
10299
)
103100

104101
if not livekit_trunk:
@@ -110,5 +107,6 @@ def main():
110107
if inbound_trunk_sid:
111108
create_dispatch_rule(inbound_trunk_sid)
112109

110+
113111
if __name__ == "__main__":
114112
main()

agent_telephony/twilio/livekit_pipeline/src/pipeline.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,30 @@
2121
logging.basicConfig(level=logging.INFO)
2222
logger = logging.getLogger(__name__)
2323

24+
2425
def validate_envs() -> None:
25-
"""
26-
Check for the presence of all required environment variables.
27-
Logs a warning if any variable is missing.
28-
"""
2926
required_envs = {
3027
"LIVEKIT_URL": "LiveKit server URL",
3128
"LIVEKIT_API_KEY": "API Key for LiveKit",
3229
"LIVEKIT_API_SECRET": "API Secret for LiveKit",
3330
"DEEPGRAM_API_KEY": "API key for Deepgram (used for STT)",
34-
"ELEVEN_API_KEY": "API key for ElevenLabs (used for TTS)"
31+
"ELEVEN_API_KEY": "API key for ElevenLabs (used for TTS)",
3532
}
3633
for key, description in required_envs.items():
3734
if not os.environ.get(key):
3835
logger.warning("Environment variable %s (%s) is not set.", key, description)
3936

37+
4038
# Validate environments at module load
4139
validate_envs()
4240

41+
4342
def prewarm(proc: JobProcess) -> None:
4443
logger.info("Prewarming: loading VAD model...")
4544
proc.userdata["vad"] = silero.VAD.load()
4645
logger.info("VAD model loaded successfully.")
4746

47+
4848
async def entrypoint(ctx: JobContext) -> None:
4949
metadata = ctx.job.metadata
5050

@@ -75,11 +75,10 @@ async def entrypoint(ctx: JobContext) -> None:
7575
engine_api_address = os.environ.get("RESTACK_ENGINE_API_ADDRESS")
7676
if not engine_api_address:
7777
agent_backend_host = "http://localhost:9233"
78+
elif not engine_api_address.startswith("https://"):
79+
agent_backend_host = "https://" + engine_api_address
7880
else:
79-
if not engine_api_address.startswith("https://"):
80-
agent_backend_host = "https://" + engine_api_address
81-
else:
82-
agent_backend_host = engine_api_address
81+
agent_backend_host = engine_api_address
8382

8483
logger.info("Using RESTACK_ENGINE_API_ADDRESS: %s", agent_backend_host)
8584

@@ -120,6 +119,7 @@ def on_metrics_collected(agent_metrics: metrics.AgentMetrics) -> None:
120119
# Start the voice pipeline agent.
121120
agent.start(ctx.room, participant)
122121

122+
123123
if __name__ == "__main__":
124124
cli.run_app(
125125
WorkerOptions(

agent_telephony/twilio/readme.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11

2-
wip# Restack AI - Agent with Telephony - Twilio
2+
# Restack AI - Agent with telephony - Twilio
33

44
Build an AI agent that do an outbound call with Twilio and can interact with in realtime with voice.
55

66
## Prerequisites
77

88
- Docker (for running Restack)
99
- Python 3.10 or higher
10-
- Livekit account (for WebRTC pipeline)
11-
- Deepgram account (For speech-to-text transcription)
12-
- ElevenLabs account (for text-to-speech and voice cloning)
13-
- Twilio account (for outbound calls)
10+
- Vapi account (for outbound calls)
1411

1512
### Trunk setup for outbound calls with Twilio
1613

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
RESTACK_API_KEY=
3+
VAPI_TOKEN=
4+
5+
# Restack Cloud (Optional)
6+
7+
# RESTACK_ENGINE_ID=<your-engine-id>
8+
# RESTACK_ENGINE_API_KEY=<your-engine-api-key>
9+
# RESTACK_ENGINE_API_ADDRESS=<your-engine-api-address>
10+
# RESTACK_ENGINE_ADDRESS=<your-engine-address>
11+
12+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__pycache__
2+
.pytest_cache
3+
venv
4+
.env
5+
.vscode
38.4 KB
Loading
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import asyncio
2+
import sys
3+
4+
from restack_ai import Restack
5+
6+
7+
async def main(agent_id: str, run_id: str) -> None:
8+
client = Restack()
9+
10+
await client.send_agent_event(
11+
agent_id=agent_id,
12+
run_id=run_id,
13+
event_name="call",
14+
event_input={"messages": [{"role": "user", "content": "Tell me another joke"}]},
15+
)
16+
17+
sys.exit(0)
18+
19+
20+
def run_event_workflow() -> None:
21+
asyncio.run(main(agent_id="your-agent-id", run_id="your-run-id"))
22+
23+
24+
if __name__ == "__main__":
25+
run_event_workflow()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[project]
2+
name = "agent_telephony_agent_vapi"
3+
version = "0.0.1"
4+
description = "An agent with streaming for Restack"
5+
authors = [{ name = "Restack Team", email = "[email protected]" }]
6+
requires-python = ">=3.10,<3.13"
7+
readme = "README.md"
8+
dependencies = [
9+
"pydantic>=2.10.6",
10+
"watchfiles>=1.0.4",
11+
"python-dotenv==1.0.1",
12+
"openai>=1.61.0",
13+
"restack-ai>=0.0.77",
14+
"vapi-server-sdk>=0.4.0",
15+
]
16+
17+
[project.scripts]
18+
dev = "src.services:watch_services"
19+
services = "src.services:run_services"
20+
schedule = "schedule_agent:run_schedule_agent"
21+
event = "event_agent:run_event_agent"
22+
23+
[tool.hatch.build.targets.sdist]
24+
include = ["src"]
25+
26+
[tool.hatch.build.targets.wheel]
27+
include = ["src"]
28+
29+
[build-system]
30+
requires = ["hatchling"]
31+
build-backend = "hatchling.build"

0 commit comments

Comments
 (0)