Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion agent_telephony/twilio/livekit-trunk-setup/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER="+000000000000"
LIVEKIT_SIP_URI="sip:XXXXX.sip.livekit.cloud;transport=tcp"
LIVEKIT_SIP_URI="sip:XXXXX.sip.livekit.cloud;transport=tcp"
TRUNK_NAME="LiveKit Trunk"
8 changes: 8 additions & 0 deletions agent_telephony/twilio/livekit-trunk-setup/inbound_trunk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"trunk": {
"name": "Inbound LiveKit Trunk",
"numbers": [
"+12096194281"
]
}
}
28 changes: 15 additions & 13 deletions agent_telephony/twilio/livekit-trunk-setup/twilio_trunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ def get_env_var(var_name):
return value


def create_livekit_trunk(client, sip_uri):
def create_livekit_trunk(client, sip_uri, trunk_name):
domain_name = f"livekit-trunk-{os.urandom(4).hex()}.pstn.twilio.com"
trunk = client.trunking.v1.trunks.create(
friendly_name="LiveKit Trunk",
friendly_name=trunk_name,
domain_name=domain_name,
)
trunk.origination_urls.create(
sip_url=sip_uri,
weight=1,
priority=1,
enabled=True,
friendly_name="LiveKit SIP URI",
friendly_name=f"{trunk_name} SIP URI",
)
logging.info("Created new LiveKit Trunk.")
logging.info(f"Created new {trunk_name} trunk.")
return trunk


def create_inbound_trunk(phone_number):
trunk_data = {"trunk": {"name": "Inbound LiveKit Trunk", "numbers": [phone_number]}}
def create_inbound_trunk(phone_number, trunk_name):
trunk_data = {"trunk": {"name": f"Inbound {trunk_name}", "numbers": [phone_number]}}
with open("inbound_trunk.json", "w") as f:
json.dump(trunk_data, f, indent=4)

Expand All @@ -58,9 +58,9 @@ def create_inbound_trunk(phone_number):
return None


def create_dispatch_rule(trunk_sid):
def create_dispatch_rule(trunk_sid, trunk_name):
dispatch_rule_data = {
"name": "Inbound Dispatch Rule",
"name": f"Inbound {trunk_name} Dispatch Rule",
"trunk_ids": [trunk_sid],
"rule": {"dispatchRuleIndividual": {"roomPrefix": "call-"}},
}
Expand Down Expand Up @@ -89,23 +89,25 @@ def main():
auth_token = get_env_var("TWILIO_AUTH_TOKEN")
phone_number = get_env_var("TWILIO_PHONE_NUMBER")
sip_uri = get_env_var("LIVEKIT_SIP_URI")
trunk_name = get_env_var("TRUNK_NAME")

client = Client(account_sid, auth_token)

existing_trunks = client.trunking.v1.trunks.list()
livekit_trunk = next(
(trunk for trunk in existing_trunks if trunk.friendly_name == "LiveKit Trunk"),
(trunk for trunk in existing_trunks if trunk.friendly_name == trunk_name),
None,
)

if not livekit_trunk:
livekit_trunk = create_livekit_trunk(client, sip_uri)
livekit_trunk = create_livekit_trunk(client, sip_uri, trunk_name)
else:
logging.info("LiveKit Trunk already exists. Using the existing trunk.")
logging.info(f"{trunk_name} already exists. Using the existing trunk.")

inbound_trunk_sid = create_inbound_trunk(phone_number)

inbound_trunk_sid = create_inbound_trunk(phone_number, trunk_name)
if inbound_trunk_sid:
create_dispatch_rule(inbound_trunk_sid)
create_dispatch_rule(inbound_trunk_sid, trunk_name)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion agent_telephony/twilio/livekit_pipeline/src/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def on_metrics_collected(agent_metrics: metrics.AgentMetrics) -> None:
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
agent_name="AgentStream",
agent_name="AgentTwilio",
prewarm_fnc=prewarm,
)
)
5 changes: 4 additions & 1 deletion agent_telephony/twilio/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ Build an AI agent that do an outbound call with Twilio and can interact with in

- Docker (for running Restack)
- Python 3.10 or higher
- Vapi account (for outbound calls)
- Twilio account (for outbound calls)
- Livekit account (for WebRTC pipeline)
- Deepgram account (for transcription)
- ElevenLabs account (for TTS)

### Trunk setup for outbound calls with Twilio

Expand Down
2 changes: 1 addition & 1 deletion agent_voice/livekit/livekit_pipeline/src/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def on_metrics_collected(agent_metrics: metrics.AgentMetrics) -> None:
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
agent_name="AgentStream",
agent_name="AgentVoice",
prewarm_fnc=prewarm,
)
)
2 changes: 2 additions & 0 deletions community/docling/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
poetry.lock
.env
26 changes: 26 additions & 0 deletions community/docling/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM python:3.12-bookworm

WORKDIR /app

RUN apt-get update && apt-get install -y \
git build-essential gcc libc-dev \
&& apt-get clean

RUN pip install --no-cache-dir --timeout=60 --retries=5 poetry

COPY pyproject.toml ./

COPY . .

# Configure poetry to not create virtual environment
RUN poetry config virtualenvs.create false

ENV PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu

# Install dependencies
RUN poetry install --no-interaction --no-ansi

# Expose port 80
EXPOSE 80

CMD poetry run python -m src.services
37 changes: 37 additions & 0 deletions community/docling/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[tool.poetry]
name = "docling-test"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "readme.md"
packages = [
{ include = "src" }
]

[[tool.poetry.source]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
priority = "supplemental"

[tool.poetry.dependencies]
python = "^3.12"
pydantic-settings = "^2.7.1"
aiohttp = "^3.11.11"
pydantic = "^2.10.6"
tavily-python = "^0.5.1"
openai = "^1.61.1"
mistune = "^3.1.1"
requests = "^2.32.3"
beautifulsoup4 = "^4.13.3"
gitpython = "^3.1.44"
validators = "^0.34.0"
restack-ai = "0.0.62"
docling = "^2.25.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
services = "src.services:run_services"

9 changes: 9 additions & 0 deletions community/docling/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@



poetry env use 3.12

poetry install

poetry run services

27 changes: 27 additions & 0 deletions community/docling/src/agents/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pydantic import BaseModel
from restack_ai.agent import (
agent,
log,
)

class EndEvent(BaseModel):
end: bool

class AgentInput(BaseModel):
test_input: str | None = None

@agent.defn()
class Agent:
def __init__(self) -> None:
self.end = False

@agent.event
async def end(self, end: EndEvent) -> EndEvent:
log.info("Received end")
self.end = True
return end

@agent.run
async def run(self, agent_input: AgentInput):
log.info("Received agent input", agent_input=agent_input)
await agent.condition(lambda: self.end)
19 changes: 19 additions & 0 deletions community/docling/src/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os

from dotenv import load_dotenv
from restack_ai import Restack
from restack_ai.restack import CloudConnectionOptions

# Load environment variables from a .env file
load_dotenv()


engine_id = os.getenv("RESTACK_ENGINE_ID")
address = os.getenv("RESTACK_ENGINE_ADDRESS")
api_key = os.getenv("RESTACK_ENGINE_API_KEY")
api_address = os.getenv("RESTACK_ENGINE_API_ADDRESS")

connection_options = CloudConnectionOptions(
engine_id=engine_id, address=address, api_key=api_key, api_address=api_address
)
client = Restack(connection_options)
Empty file.
10 changes: 10 additions & 0 deletions community/docling/src/functions/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from restack_ai.function import function, log

@function.defn(name="welcome")
async def welcome(function_input: str) -> str:
try:
log.info("welcome function started", function_input=function_input)
return f"Hello, {function_input}!"
except Exception as e:
log.error("welcome function failed", error=e)
raise e
32 changes: 32 additions & 0 deletions community/docling/src/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import asyncio

from src.agents.agent import Agent
from src.client import client
from src.functions.function import (
welcome,
)
from src.workflows.workflow import (
Workflow,
)

async def main():

await client.start_service(
agents=[
Agent,
],
workflows=[
Workflow,
],
functions=[
welcome,
]
)


def run_services():
asyncio.run(main())


if __name__ == "__main__":
run_services()
21 changes: 21 additions & 0 deletions community/docling/src/workflows/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import timedelta
from pydantic import BaseModel
from restack_ai.workflow import workflow, import_functions, log
with import_functions():
from src.functions.function import welcome


class Input(BaseModel):
name: str = "world"

class Output(BaseModel):
result: str

@workflow.defn()
class Workflow:
@workflow.run
async def run(self, workflow_input: Input) -> Output:
log.info("ChildWorkflow started")
result = await workflow.step(function=welcome, function_input=workflow_input.name, start_to_close_timeout=timedelta(seconds=120))
log.info("ChildWorkflow completed", result=result)
return Output(result=result)