Metadata-Version: 2.5
Name: talqing
Version: 0.1.0
Summary: Python client for the Talqing API
Project-URL: Homepage, https://talqing.com
Author: talqing
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ai,talqing,voice
Classifier: Development Status :: 3 - Alpha
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: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions>=4.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# Talqing Python SDK

The Python client for the Talqing `/v1` API, generated from `openapi/openapi.json`.
It is the same set of operations, under the same names, as the TypeScript SDK —
both are generated from the surface `backend/api/sdk_surface.py` declares.

`httpx` is the only runtime dependency. Responses come back as decoded JSON,
described by TypedDicts: nothing is validated, coerced or renamed on arrival.

## Install

```bash
pip install talqing
```

Python 3.10 or newer. The package ships a `py.typed` marker, so a type checker
in your project sees every TypedDict it declares.

To work against a checkout instead: `uv pip install -e clients/python`.

## Authentication

Create a personal access token on the dashboard's Tokens page. The client sends
it as `Authorization: Bearer <token>`.

```python
from talqing import Talqing

with Talqing(token="tq_...", base_url="https://api.in.talqing.com") as talqing:
    print(talqing.auth.me())
```

`Talqing.from_env()` reads the same two values from `TALQING_API_KEY` and
`TALQING_BASE_URL`:

```bash
export TALQING_API_KEY="your-personal-access-token"
export TALQING_BASE_URL="http://localhost:8000"
```

```python
with Talqing.from_env() as talqing:
    print(talqing.auth.me())
```

`base_url` has no default. A client that quietly points at localhost fails in
production as a connection error three layers down; one that refuses to start
says what is actually wrong.

## The shape of it

Operations are reached by resource, exactly as the API names them:

```python
talqing.agents.list(limit=50)
talqing.agents.get(agent_id)
talqing.agents.versions.rollback(agent_id, 3)
talqing.calls.batches.pause(batch_id)
talqing.telephony.phone_numbers.assign(number_id, agent_id=agent_id)
```

Path parameters are positional; everything else — request body fields and query
parameters alike — is a keyword argument:

```python
agent = talqing.agents.create(
    config={"name": "Support bot", "channel": "text", "prompt": "Be concise."}
)
talqing.agents.update(agent["id"], config={**agent["config"], "greeting": None})
talqing.agents.publish(agent["id"])
```

**An argument you do not pass is not sent.** That is what makes a PATCH able to
say `null`:

```python
talqing.orgs.update(retention_days=None)  # keep call content forever
talqing.orgs.update(name="Acme")  # leave the retention policy alone
```

The one name that is not the API's own is `telephony.phone_numbers.import_()` —
`import` is a Python keyword.

## Errors

Every non-2xx raises `TalqingAPIError`. There is one error shape, so there is
nothing to branch on:

```python
from talqing import TalqingAPIError

try:
    talqing.agents.publish(agent_id)
except TalqingAPIError as error:
    print(error.status_code, error)  # 400 config is invalid
    for problem in error.errors:  # ['llm: unknown model gpt-4.9']
        print(problem)
```

## Pagination

Every list endpoint pages the same way, so one helper covers all of them.
Anything else the endpoint filters on passes straight through:

```python
from talqing import paginate

for agent in paginate(talqing.agents.list):
    print(agent["config"]["name"])

for call in paginate(talqing.calls.list, agent_id=agent_id, type="SIP_INBOUND"):
    print(call["id"], call["cost"])
```

## Live streams

Six endpoints stay open and push events. Each frame is decoded and repeats its
own name in `event`, which is what tells the frames apart:

```python
for event in talqing.conversations.events(conversation_id):
    if event["event"] == "assistant.delta":
        print(event["text"], end="", flush=True)
```

Use it as a context manager when the loop may exit early, so the connection
closes with it:

```python
with talqing.knowledge.events(kb_id) as events:
    for event in events:
        if event["event"] == "status":
            print("build finished:", event["status"])
            break
```

## Text conversations

Text agents do not use LiveKit rooms. Open the conversation, then send turns —
both addressed by your own `contact_key`, so the same key always reaches the same
thread — and watch it happen on the stream above:

```python
conversation = talqing.conversations.create(contact_key="user-42", agent_id=agent_id)

talqing.conversations.messages.create(
    contact_key="user-42", message="Hello", client_message_id=str(uuid4())
)
```

A message returns as soon as it is accepted, carrying the user's own item. The
agent's reply arrives on the event stream, or from
`talqing.conversations.items.list(conversation["id"])`.

Voice and video agents take a LiveKit room token instead:

```python
token = talqing.calls.token(agent_id=agent_id)
print(token["server_url"], token["participant_token"])
```

## Async

`AsyncTalqing` has the same surface with every operation a coroutine. A stream
is the exception: it is not awaited on either client, so `async for` reads the
way `for` does.

```python
import asyncio
from talqing import AsyncTalqing, paginate_async


async def main() -> None:
    async with AsyncTalqing.from_env() as talqing:
        await talqing.agents.list()

        async for agent in paginate_async(talqing.agents.list):
            print(agent["id"])

        async for event in talqing.copilot.agents.stream(agent_id):
            print(event["event"])


asyncio.run(main())
```

## Types

Every request and response shape is exported from `talqing`, named as the API
names it:

```python
from talqing import AgentConfig, AgentResponse, CallOutcome, OperationRequest
```

A response is a plain `dict` at runtime — the TypedDicts describe it for your
type checker and your editor, and cost nothing when you run. That also means the
SDK can never reject a payload the API considers valid.

`Page[T]` is the shape every list endpoint returns:

```python
page = talqing.tools.list(limit=50)
page["items"], page["has_more"], page["limit"], page["offset"]
```

## Escape hatches

`talqing.request(...)` calls a path directly with this client's credentials and
error handling, for an endpoint that shipped since this SDK was generated.
`talqing.http` is the underlying `httpx.Client`, already carrying the base URL
and the token, for anything else.

`talqing.google_login_url()` and `talqing.oauth_start_url(provider)` build the
two browser redirects, which are not operations and so cannot be generated.

## Regenerating

`src/talqing/gen` is generated and checked in. After re-running
`openapi/export.py`:

```bash
python clients/python/generate.py            # rewrite it
python clients/python/generate.py --check    # or just assert it is current
```

Everything else in `src/talqing` is hand-written: the client, the transport, the
error, and the pagination helper — what the document cannot say.

```bash
cd clients/python && pytest && mypy
```
