woise (Python)
Voice and chat agents for your systems: your CRM, app or phone system places calls, runs campaigns, chats with agents, talks to them live, and gets the results back as webhooks.
pip install woise # live audio: pip install "woise[live]"
Python 3.9+. One dependency (httpx). Woise is sync, AsyncWoise is the same
API with await. The API key picks your region: sk-woise-us-... talks to
https://us.woise.ai, sk-woise-in-... to https://in.woise.ai. Older keys
without a region need Woise(key, region="us"). Local dev or a partner domain:
Woise(key, base_url="http://localhost:8000").
Build an agent and place a call
import os
from woise import Woise, Agent
woise = Woise(os.environ["WOISE_API_KEY"])
agent = Agent("Smile Dental", prompt="You are Priya, the receptionist at Smile Dental.")
agent.start("greet", say="Hi, this is Priya from Smile Dental.", prompt="Confirm tomorrow's visit.",
next={"done": "they confirmed or cancelled"})
agent.end("done", say="Thanks, bye.")
agent.after_call(collect={"outcome": "confirmed, cancelled or rescheduled"})
saved = woise.agents.save(agent, publish=True) # save again later = new draft
call = woise.calls.create(agent=saved, to="+14155550123", variables={"first_name": "Ravi"})
print(call.status, call.id) # queued, enr_...
next maps the step to go to, to the condition in plain words.
agent.anytime("human", when="asks for a person", transfer="+14155550100") adds a
step that can fire from anywhere. agent.node(id, type, data) adds any other node.
Run a campaign from a CSV
# leads.csv: phone_number,first_name,plan (every other column becomes a {{variable}})
campaign = woise.campaigns.create(agent=saved, contacts="leads.csv", number="+14155550100")
campaign.launch()
print(campaign.progress())
campaign.pause(); campaign.resume()
campaign.add_contacts([{"phone_number": "+14155550124", "first_name": "Sita"}])
Get results back: call.ended in Flask, with the signature check
hook = woise.webhooks.create(url="https://crm.example.com/woise", events=["call.ended"])
print(hook.secret) # shown ONCE: store it as WOISE_WEBHOOK_SECRET
from flask import Flask, abort, request
from woise import verify_signature
app = Flask(__name__)
@app.post("/woise")
def woise_event():
if not verify_signature(request.get_data(), request.headers.get("X-Woise-Signature"),
os.environ["WOISE_WEBHOOK_SECRET"]):
abort(401)
event = request.get_json()
print(event["event"], event["call_id"], event["call_disposition"], event["collected"])
return "", 204
Events: call.started, call.ended, chat.ended, campaign.finished. Failed
deliveries are retried with backoff; woise.webhooks.deliveries(hook) shows the log.
Chat
chat = woise.chats.start(saved, variables={"first_name": "Ravi"})
print(chat.greeting)
print(chat.send("Are you open on Sunday?").text)
chat.end()
Runs: transcripts, outcomes, recordings
for run in woise.runs.list(agent=saved, limit=20):
print(run.id, run.outcome, run.collected)
print(run.transcript) # "agent: ...\ncaller: ..."
print(woise.runs.recording_url(run)) # short-lived link
Live voice: Twilio Media Streams -> Woise in ~20 lines
Point a Twilio <Connect><Stream url="wss://you.example.com/twilio"/></Connect>
at this relay. It forwards Twilio's messages to Woise verbatim and Woise answers
in the same format (media, mark, clear), so barge-in works unchanged.
import asyncio, json, os
import websockets
from woise import AsyncWoise
woise = AsyncWoise(os.environ["WOISE_API_KEY"])
AGENT = "wf_..." # your agent id
async def relay(twilio):
async with woise.live.voice(AGENT, format="twilio") as call:
async def up(): # Twilio -> Woise
async for message in twilio:
await call.send_raw(message)
async def down(): # Woise -> Twilio
async for event in call:
await twilio.send(json.dumps(event.data))
await asyncio.gather(up(), down())
async def main():
async with websockets.serve(relay, "0.0.0.0", 8080):
await asyncio.Future()
asyncio.run(main())
Raw PCM16 instead (your own media server, 8/16/24 kHz mono):
with woise.live.voice(AGENT, sample_rate=16000) as call:
call.send_audio(pcm16_bytes)
for event in call:
if event.type == "audio": speaker.write(event.audio)
elif event.type == "interrupt": speaker.flush() # the caller barged in
elif event.type == "transcript": print(event.data["role"], event.text)
woise.live.chat(agent) is the same socket for text: send_text, then stream
agent_text events.
More
- Numbers:
woise.numbers.connections(),.carrier_numbers(conn),.import_byo(conn, ["+1..."]),.list(connection_id=conn),.attach(number, agent, connection_id=conn),.detach(number, connection_id=conn)(managed numbers:.list(),.attach(number, agent)) woise.tools.create_http("lookup_patient", url="https://crm.example.com/patients", parameters={"phone": "the caller's number"})woise.knowledge.add("faq.pdf")- Errors are
WoiseErrorwith.status,.code,.message,.request_id. GET/PUT/DELETE are retried on 429/5xx;calls.createnever is (passidempotency_key=to retry safely).
BSD-2-Clause.
Metadata
Release files for woise 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| woise-1.0.0.tar.gz | 28.0 kB | Without provenance | Details for woise-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| woise-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for woise-1.0.0-py3-none-any.whl |
Total release size: 49.4 kB
Release files / woise-1.0.0.tar.gz
| Download URL | woise-1.0.0.tar.gz |
|---|---|
| Size | 28.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f99a310b4365cd7b460e9acddbf33c01ed016fe614e80a0601b40cbedab8d153
|
|
BLAKE2b-256 checksum How to use checksums |
215274a722c090ddd8708f53ff7869891121b253aeb46139cbe40da63bfe308b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / woise-1.0.0-py3-none-any.whl
| Download URL | woise-1.0.0-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1bc83061b04ff6122b4a7b3b1de104a16918adf221b0037218763f659a62c2dc
|
|
BLAKE2b-256 checksum How to use checksums |
7d79dc734f12194422d580d8c508b2b24ab0d326646a3fa915f717f0ae4c79da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for woise 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.