Metadata-Version: 2.4
Name: livekit-piopiy
Version: 0.1.1
Summary: Piopiy (TeleCMI) telephony for LiveKit Agents: take calls, transfer them, hang up
Author-email: TeleCMI <support@telecmi.com>
License: MIT
Project-URL: Homepage, https://piopiy.com
Project-URL: Repository, https://github.com/telecmi/livekit-piopiy
Project-URL: Documentation, https://github.com/telecmi/livekit-piopiy#readme
Keywords: livekit,livekit-agents,voice,telephony,sip,piopiy,telecmi
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Telephony
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: livekit-agents<2,>=1.7.0
Requires-Dist: piopiy-agent>=1.2.0
Provides-Extra: example
Requires-Dist: livekit-agents[deepgram,openai,silero]<2,>=1.7.0; extra == "example"
Requires-Dist: python-dotenv>=1.0; extra == "example"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Dynamic: license-file

# livekit-piopiy

Piopiy telephony for [LiveKit Agents](https://github.com/livekit/agents).
Your LiveKit agent takes real phone calls on the Piopiy platform by TeleCMI:
calls the Piopiy API places, calls arriving on your numbers, and calls
arriving from your PBX over SIP Connect. Mid-call it can transfer the caller
to a human, warm or blind, and hang up.

Piopiy bridges each call into a LiveKit room and hands your worker the room
and a per-call token. Your `AgentSession` runs in that room; this package
supplies the rest: taking the call, knowing who is calling, acting on the
call, and hearing about transfers.

**Tested with livekit-agents v1.7.1.** Maintained by
[TeleCMI](https://telecmi.com).

## Install

```bash
pip install livekit-piopiy
# for the example, with Deepgram + OpenAI + Silero VAD:
pip install "livekit-piopiy[example]"
```

Python 3.10+. Depends on `livekit-agents` and `piopiy-agent`, the
framework-agnostic Piopiy worker SDK.

## Use with an AgentSession

```python
from livekit.agents import Agent, AgentSession
from livekit.plugins import deepgram, openai, silero
from livekit_piopiy import PiopiyRunner, PiopiyCallControl, piopiy_tools, attach_transfer_status

async def bot(room, call):
    control = PiopiyCallControl(call)
    tools = piopiy_tools(control, transfer_number="919876543210",
                         transfer_caller_id="911203134087")
    agent = Agent(instructions=PROMPT, tools=tools.tools)

    session = AgentSession(stt=deepgram.STT(model="nova-3", language="en"),
                           llm=openai.LLM(model="gpt-4o-mini"),
                           tts=deepgram.TTS(), vad=silero.VAD.load())
    attach_transfer_status(call, session)     # narrates transfer progress

    await session.start(agent=agent, room=room)
    session.generate_reply(instructions="Greet the caller.")
    await call.wait_for_end()
    await session.aclose()

PiopiyRunner().run(bot)
```

`PiopiyRunner` connects to Piopiy as a worker for your agent. For every call
it connects an `rtc.Room` with the token the platform minted, accepts the
call (which is when the caller is bridged in), and calls `bot(room, call)`.
The room is disconnected when `bot` returns; `call.wait_for_end()` returns
when the caller leaves.

This is the "bring your own worker" model: your process runs anywhere with
outbound internet, no LiveKit Cloud agent dispatch is involved, and the
`livekit-agents` CLI worker is not used.

## Run the example

```bash
cd examples/foundational
cp .env.example .env        # agent id, token, API base, transfer number, keys
python 01_piopiy_agent.py
```

Then call your agent: place a call with `POST /v3/voice/agent/call`, ring one of
your numbers mapped to the agent, or dial the agent id from a PBX registered
over SIP Connect. Ask for a person to see a warm transfer, ask for "the billing
line" to see a blind transfer, and say goodbye to see it hang up.

## Configuration

| variable | what |
|---|---|
| `PIOPIY_AGENT_ID` | the agent this worker serves, from the dashboard |
| `PIOPIY_TOKEN` | the Bearer token, the same one that creates calls |
| `PIOPIY_API_URL` | optional; the REST base, default `https://rest.piopiy.com/v3` |
| `PIOPIY_REGISTER` | optional; default `register.piopiy.com` (host only); `host:port` for a private register |
| `PIOPIY_TLS` | optional; `false` to talk to the register without TLS (development) |
| `PIOPIY_MAX_SESSIONS` | calls one process handles at once |

## What the package gives you

**`PiopiyCall`** - `call_id`, `direction`, `from_number`, `to_number`,
`agent_id`, `variables`, `sip_account_id` on SIP Connect calls, and
`wait_for_end()`.

**`PiopiyCallControl`** (from `piopiy-agent`) - actions on the live call:

```python
result  = await control.warm_transfer(to_number="9198...", transfer_summary="Refund on order A-1042")
result  = await control.blind_transfer(to_number="9198...", caller_id="9112...")
await control.hangup(reason="resolved")
verdict = await control.wait_for_transfer(result.request_id)   # queued -> completed | failed
```

A warm transfer rings the human while the caller stays with the agent; on
answer the caller is handed over and the agent leaves; if nobody answers the
conversation continues. A blind transfer hands the caller over at once. One
transfer at a time per call: a second is refused with
`PiopiyAPIError(409, "transfer_in_progress")`.

**`piopiy_tools()`** - `transfer_call` and `end_call` as LiveKit function
tools (`tools.tools`). The destination is fixed in code; pass
`transfer_caller_id` for the DID to present, which SIP Connect calls require.

**`attach_transfer_status()`** - the platform pushes every transfer's progress
into the room as `transfer_status` data messages. This speaks "I'm connecting
you now" as the target rings, an apology if it fails, and adds a note to the
agent's chat context so the model carries on. Pass `narrate=False` and
`on_status=` to handle it yourself.

## Notes

- Every action uses the call's customer leg, which `PiopiyCall.call_id` is.
- SIP Connect calls consume no phone number, so a transfer to a phone from one
  needs `transfer_caller_id` (a DID you own).
- Accept timing is handled for you: the runner accepts right after the room
  connects; a join past the platform's deadline leaves the room and does not
  run the bot, so two agents never share a call.
- The Pipecat equivalent is [`pipecat-piopiy`](https://github.com/telecmi/pipecat-piopiy).

## License

MIT. Copyright TeleCMI.
