Metadata-Version: 2.4
Name: dyadic
Version: 1.3.1
Summary: The official Python SDK for the Dyadic API: runs with your own agents and functions, plus the OpenAI-compatible API.
Author: Dyadic
License: MIT
Project-URL: Homepage, https://dyadic.app/developers/
Project-URL: Documentation, https://dyadic.app/developers/
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.25
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-asyncio>=0.23; extra == "test"

# dyadic

The official Python SDK for the [Dyadic API](https://dyadic.app/developers/): use Iris
from Python with streaming, function calling, images and typed errors. Requests use your Dyadic credits.

```sh
pip install dyadic
```

```python
from dyadic import Dyadic

client = Dyadic()  # reads DYADIC_API_KEY
print(client.ask("Hello!", model="iris"))
```

## Keep your key on the server

A Dyadic API key is a long-lived secret that spends your credits. Use it only in
server-side code; never put it in a mobile app, a desktop app you ship, or
anything that runs in someone else's browser. Front-end code should call your
own server, which calls Dyadic.

`request()` only ever sends the key to the API you configured: a relative path
joins `base_url`, and an absolute URL on any other host, port or scheme (or with
credentials in it) raises `DyadicError` instead of being sent. Redirects are not
followed. Plain `http://` is accepted only for a local or private-network API
(`localhost`, a private IP, or a single-label name such as `dyadic-api`).

## Retries

GET, HEAD, DELETE and OPTIONS are retried on busy or failing servers. Anything
else (a completion, an upload, a run) may already have run, and been billed,
when it fails, so it is retried only if it provably never started: the
connection was never made, or Dyadic refused it with `rate_limit_exceeded` or
`concurrency_limit`. Every attempt of one call carries the same
`Idempotency-Key`; pass `idempotency_key=` to choose it, or `idempotent=True` to
`request()` for a call that is safe to send twice.

## Runs: your own agent, Dyadic's tools, your functions

An application brings its own agent (name and instructions), lets it use
Dyadic's built-in tools, and gives it functions it runs itself. `runs.run`
calls your functions whenever the agent asks and returns the finished run.

```python
def lookup_order(order_id):
    """Look up an order by id."""
    return {"order_id": order_id, "status": "shipped"}

lookup_order.schema = {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}

run = client.runs.run(
    agent={"id": "helpdesk", "name": "Helpdesk", "instructions": "You answer customers of a small shop."},
    messages=[{"role": "user", "content": "Where is order 1042? Also, what's the weather in Lisbon today?"}],
    builtin_tools=["data.web_search", "data.web_fetch"],
    functions={"lookup_order": lookup_order},
    end_user="customer-17",           # your own id for who asked, kept with the run's usage
    conversation_id="ticket-88",
)
print(run.output_text, run.credits)
```

`runs.create(..., stream=True)` streams events (`run.started`, `status`,
`tool.started`, `tool.completed`, `sources`, `image.generated`, `delta`,
`requires_action`, `run.completed`).

## Decisions: typed answers, fast and cheap

Ask one or more questions about some text (or text and images) and get typed answers
back: a probability (`predicate`), one of your values (`choice`) or a rubric level
(`score`). A fraction of a second and a fraction of a cent; billed at cost.

```python
answer = client.decisions.create(
    input="I can't log in after resetting my password.",
    questions=[
        {"type": "predicate", "name": "urgent", "instructions": "The customer is blocked from using the product."},
        {"type": "choice", "name": "team", "instructions": "Which team should handle this.",
         "choices": [{"value": "account", "description": "Sign-in and account access"},
                     {"value": "billing", "description": "Payments and invoices"},
                     {"value": "other", "description": "Anything else"}]},
    ],
)
answers = {a["name"]: a for a in answer["answers"]}  # urgent: probability; team: choice
```

## Connected apps: saving into a person's Workspace

With the person's approval, an application can save into their Dyadic
Workspace, naming them by the Discord account linked to their Dyadic account.

```python
from dyadic import DyadicError

status = client.connections.discord(discord_id)
if not status.authorized:
    print("Allow this app:", status.authorize_url)   # show the person this link

try:
    saved = client.workspace.saves.create(discord_id=discord_id, kind="message", title="From #general",
                                          text="Worth keeping", url="https://discord.com/channels/1/2/3")
    print(saved.url)
except DyadicError as error:
    if error.code == "authorization_required":
        print("Allow this app:", error.body["authorize_url"])
```

`kind` is `message`, `conversation` (with `messages`, saved as a chat the
person can continue unless `as_chat=False`), `image` or `file` (with `files`:
base64, up to 10 of 8 MB).

See https://dyadic.app/developers/ for the full guide.
