Metadata-Version: 2.4
Name: livekit-piopiy
Version: 0.1.2
Summary: Piopiy (TeleCMI) telephony for LiveKit Agents: take calls, transfer them, hang up
Author-email: TeleCMI <support@telecmi.com>
License-Expression: 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: 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

Give your [LiveKit Agents](https://github.com/livekit/agents) agent a phone.

[Piopiy](https://piopiy.com) is a communication platform: we provide the
phone numbers, carry the calls, and host all the voice infrastructure,
including the LiveKit rooms your agent talks in. You build the agent with
LiveKit Agents and run it wherever you like; this package connects the two.
Your agent answers calls to your Piopiy numbers, places calls through the
Piopiy API, and mid-call it can hand the caller to a human or end the call.
Nothing telephony-related to host or configure on your side.

**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+.

## 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` registers your process with Piopiy as the worker for your
agent. For every call, Piopiy hands it the call; the runner connects the
room for that call and calls `bot(room, call)`. The caller is joined as soon
as the room is connected. `call.wait_for_end()` returns when the caller
leaves, and the room is closed when `bot` returns.

You run the process; Piopiy runs everything else. There is no LiveKit
project to create, no rooms to manage and no telephony to set up.

## Run the example

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

Then call your agent: ring one of your Piopiy numbers, or place a call with
`POST /v3/voice/agent/call`. 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

Two values, both from your Piopiy dashboard:

| variable | what |
|---|---|
| `PIOPIY_AGENT_ID` | the agent this process serves |
| `PIOPIY_TOKEN` | your API token |

Optional: `PIOPIY_MAX_SESSIONS` (calls one process handles at once, default
10). `PIOPIY_API_URL` and `PIOPIY_REGISTER` exist only for regional or
private deployments; the public platform needs neither.

## What the package gives you

**`PiopiyCall`** - the call in hand: `call_id`, `direction`, `from_number`,
`to_number`, `agent_id`, the `variables` you attached when placing the call,
`sip_account_id` when the call came from a phone system you connected to
Piopiy, and `wait_for_end()`.

**`PiopiyCallControl`** - act on the 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 in conversation
with the agent; when the human answers the caller is handed over and the
agent leaves; if nobody answers the conversation simply 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 your code so a caller
cannot talk the agent into dialling anywhere else. `transfer_caller_id` is
the number shown to the human being called; use one of your Piopiy numbers.

**`attach_transfer_status()`** - Piopiy tells the agent how a transfer is
going. This speaks "I'm connecting you now" as the human's phone rings, an
apology if nobody answers, 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 `PiopiyCall.call_id`; the runner gives you the right one.
- Accept timing is handled for you: if your process was too slow to join a
  call, the bot is not run, so two agents never share one call.
- The Pipecat equivalent is [`pipecat-piopiy`](https://github.com/telecmi/pipecat-piopiy).

## License

MIT. Copyright TeleCMI.
