Metadata-Version: 2.4
Name: makeyouragent
Version: 0.2.0
Summary: Official Python SDK for Make Your Agent (MYA) — build AI agents with knowledge bases, tool execution, streaming chat, and per-session token usage
Project-URL: Homepage, https://makeyouragent.ai
Project-URL: Documentation, https://github.com/Make-Your-Agent/core/tree/main/sdk-python
Project-URL: Repository, https://github.com/Make-Your-Agent/core
Author: FireInBelly
License: MIT
License-File: LICENSE
Keywords: agents,ai,chatbot,llm,makeyouragent,mya,sdk
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.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# makeyouragent (Python SDK)

Official Python SDK for [Make Your Agent (MYA)](https://makeyouragent.ai) — build AI agents with
knowledge bases, tool execution, file/image attachments, streaming chat, and per-session token usage.

This is the server-side SDK, feature-equivalent to the Node SDK's server module
([`@makeyouragent/sdk`](https://www.npmjs.com/package/@makeyouragent/sdk)).

## Install

```bash
pip install makeyouragent
```

## Quick Start

```python
from makeyouragent import MakeYourAgent

mya = MakeYourAgent(api_key="mya_live_...")

# Create an agent
agent = mya.agents.create({
    "name": "Support Bot",
    "systemPrompt": "You are a helpful support agent.",
})

# Chat (blocking) — optionally identify the end user (id/email/name/metadata),
# like a tracking tool's identify(); powers CRM/helpdesk intent-rule integrations
res = mya.chat.send(agent["id"], {
    "message": "What can you help me with?",
    "user": {"id": "user_8f3a", "email": "jane@acme.com", "name": "Jane Doe",
             "metadata": {"plan": "pro"}},
})
print(res["message"]["content"])
print(res["usage"])         # tokens for THIS call
print(res["sessionUsage"])  # cumulative {totals, byModel} for the whole conversation

# Chat (streaming)
stream = mya.chat.stream(agent["id"], {"message": "Tell me a story"})
for chunk in stream:
    if chunk.type == "content":
        print(chunk.delta, end="", flush=True)
final = stream.final_response()

# Knowledge bases
kb = mya.knowledge_bases.create(agent["id"], {"name": "Docs", "sourceType": "MARKDOWN"})
mya.knowledge_bases.import_(agent["id"], kb["id"], {
    "content": "# Getting Started\n\nWelcome...",
    "title": "Getting Started",
})

# File / image uploads
with open("manual.pdf", "rb") as f:
    mya.files.upload(agent["id"], f.read(), filename="manual.pdf")

# Token usage (billing) — reconcile invoices. from_/to are Unix seconds.
usage = mya.usage.get(from_=1748736000, to=1751327999)
print(usage["totals"]["totalTokens"], usage["byModel"])
```

## Resources

| Namespace | Methods |
|---|---|
| `mya.agents` | `create`, `list`, `get`, `update`, `delete` |
| `mya.chat` | `send`, `stream` |
| `mya.knowledge_bases` | `create`, `list`, `get`, `delete`, `import_`, `search`, `retrieval_preview` |
| `mya.files` | `upload` |
| `mya.images` | `upload` |
| `mya.usage` | `get` |
| `mya.intent_definition_sets` | `create`, `list`, `get`, `add_intent`, `update_intent`, `remove_intent`, `validate`, `submit_review`, `publish`, `rollback`, `conflicts`, `test_run` |
| `mya.action_receipts` | `list`, `get`, `get_in_conversation` |
| `mya.chatbot_config` | `get`, `update`, `get_effective`, `validate`, `get_capabilities` |
| `mya.evaluation_suites` | `create`, `list`, `get`, `create_revision`, `get_revision`, `update_revision`, `validate_revision`, `publish_revision` |
| `mya.evaluation_runs` | `create`, `list`, `get`, `gate_check` |
| `mya.feedback` | `submit`, `update` |
| `mya.quality` | `review_queue`, `create_label`, `adjudicate`, `outcomes` |
| `mya.decision_traces` | `list`, `get` |

Use `mya.request(path, method=..., body=...)` for endpoints not covered by a resource
(returns the raw `httpx.Response`).

## Locale-aware replies

Tell the agent how to localize a turn by passing `language` (BCP 47), `timeZone` (IANA), and
`currency` (ISO 4217) — all optional and validated server-side. The resolved locale comes back on
`res["metadata"]["effectiveLocale"]`, so your UI can format dates and money to match. (`locale`
still works as a free-form back-compat tag.)

```python
res = mya.chat.send(agent_id, {
    "message": "When does my trial end and what will I pay?",
    "language": "fr-FR",
    "timeZone": "Europe/Paris",
    "currency": "EUR",
})
res["metadata"]["effectiveLocale"]  # {"language": "fr", "formattingLocale": "fr-FR", "timeZone": "Europe/Paris"}
```

An agent-wide default language / time zone / currency can be set in the chatbot configuration's
`localization` block (see below).

## Verified identity, idempotent turns, and action confirmation

Three optional chat fields harden agents that execute real business actions (all backward
compatible — requests are plain dicts, so they pass straight through):

```python
res = mya.chat.send(agent["id"], {
    "message": "Cancel order 123",
    # Idempotency: retrying with the same value replays the stored turn —
    # no duplicate message, no re-executed action (response sets duplicate: True).
    "clientMessageId": "turn-8f3a-001",
    # Verified identity (distinct from the display-only `user` traits):
    # a signed end-user JWT verified against your tenant's issuer config,
    # or {"subject": ...} for server-to-server assertion (needs identity:assert scope).
    "identity": {"token": signed_end_user_jwt},
})

# Consequential writes pause instead of executing:
if res.get("pendingAction"):
    # {id, risk: "HIGH_WRITE", summary, details, expiresAt, allowedDecisions}
    mya.chat.send(agent["id"], {
        "conversationId": res["conversationId"],
        "actionDecision": {
            "pendingActionId": res["pendingAction"]["id"],
            "decision": "CONFIRM",  # or "CANCEL"
        },
    })
```

The confirmed action executes exactly once — replaying a resolved confirmation is rejected, and
nothing runs until the explicit decision arrives.

### Business intents and multi-turn tasks

Agents with configured business intents return structured decision metadata on every turn, and
multi-turn tasks (slot collection, disambiguation) surface a redaction-safe summary:

```python
res = mya.chat.send(agent["id"], {"message": "Cancel my subscription"})
res["metadata"].get("intent")  # {"intentKey": "cancel_subscription", "mode": "clarify", ...}
res["metadata"].get("task")    # {"taskId": ..., "status": "COLLECTING", "missingSlotNames": [...]}
# Reply with the missing value (or "the second one" against presented options) to continue.
```

Intent definitions, external API credentials, and entity-resolution rules are managed through
admin endpoints (`/api/agents/{agent_id}/business-intents`, `/api/credentials`,
`/api/agents/{agent_id}/openapi-specs/{spec_id}/security-bindings`,
`/api/agents/{agent_id}/entity-resolution-rules`) — reachable via `mya.request(...)`; see the
service README for the full setup guide.

## Admin, evaluation, and quality APIs

The SDK also wraps the agent-governance surfaces. Requests and responses are plain dicts.

### Intent definition sets

Author business intents as a versioned set with a draft -> validate -> submit-review -> publish
-> rollback lifecycle, and dry-run a draft in the sandbox before publishing. Mutations are
optimistically concurrent — pass the set's current `version` as `expectedVersion`.

```python
draft = mya.intent_definition_sets.create(agent_id)
mya.intent_definition_sets.add_intent(agent_id, draft["id"], {
    "intent": {"key": "cancel_order", "name": "Cancel order", "allowedModes": ["act"]},
    "expectedVersion": draft["version"],
})

summary = mya.intent_definition_sets.validate(agent_id, draft["id"])
if summary["status"] == "passed":
    mya.intent_definition_sets.publish(agent_id, draft["id"], {"expectedVersion": draft["version"] + 1})

# Dry-run a single message, or a batch of up to 50 cases, against the draft
result = mya.intent_definition_sets.test_run(agent_id, draft["id"], {"message": "cancel order 123"})
```

### Action receipts

Every consequential action the agent takes yields a redaction-safe receipt. Read a conversation's
receipts, or fetch one by id. Receipts created during a turn also appear inline on
`res["metadata"]["actionReceipts"]`.

```python
receipts = mya.action_receipts.list(agent_id, conversation_id)
receipt = mya.action_receipts.get(agent_id, receipt_id)
# receipt["status"] -> "SUCCEEDED" | "AWAITING_CONFIRMATION" | "FAILED" | ...
```

### Knowledge grounding

Documents carry typed grounding metadata (publication status, authority, effective dates, locale,
regions, products). Preview how the retrieval policy resolves a query; grounded citations are
attached to chat turns via `res["metadata"]["knowledge"]`.

```python
mya.knowledge_bases.import_(agent_id, kb_id, {
    "content": "# Refund policy ...",
    "authority": "AUTHORITATIVE",
    "effectiveFrom": "2026-01-01T00:00:00Z",
    "regions": ["US"],
})

preview = mya.knowledge_bases.retrieval_preview(agent_id, {"query": "refund window", "limit": 5})
# preview["groundingState"], preview["selected"], preview["excluded"]
```

### Chatbot configuration and capabilities

One typed, versioned configuration contract per agent (generation, context, model routing,
planning). Read the stored config, update it with optimistic concurrency, resolve the effective
config, or inspect capabilities. When you pass routing fields (`routingEnabled`, `routerModel`, …)
to `agents.update`, the returned agent carries the resulting `modelRouting` block and
`configRevision`.

```python
current = mya.chatbot_config.get(agent_id)
mya.chatbot_config.update(agent_id, {
    "config": {**current["config"], "planning": {**current["config"]["planning"], "enabled": True}},
    "expectedRevision": current["revision"],
})

effective = mya.chatbot_config.get_effective(agent_id)
capabilities = mya.chatbot_config.get_capabilities(agent_id)["capabilities"]

# Optional agent-wide locale defaults (PRD 020) — the one section with no built-in default
mya.chatbot_config.update(agent_id, {
    "config": {**current["config"],
               "localization": {"defaultLanguage": "de-DE", "defaultTimeZone": "Europe/Berlin",
                                "defaultCurrency": "EUR"}},
    "expectedRevision": current["revision"],
})
```

### Business scenario evaluation

Define evaluation suites of business scenarios, publish revisions, run them against the current
agent, and gate-check the result against the suite's release policy.

```python
suite = mya.evaluation_suites.create(agent_id, {"key": "refunds", "name": "Refund flows"})
rev = mya.evaluation_suites.create_revision(agent_id, suite["id"])
mya.evaluation_suites.update_revision(agent_id, suite["id"], rev["id"], {
    "cases": [{"caseKey": "basic", "severity": "high",
               "turns": [{"message": "cancel order 1"}], "expect": {"mode": "act"}}],
    "expectedVersion": rev["version"],
})
mya.evaluation_suites.publish_revision(agent_id, suite["id"], rev["id"], {"expectedVersion": rev["version"] + 1})

run = mya.evaluation_runs.create(agent_id, {"suiteId": suite["id"], "runKey": "nightly-01"})
gate = mya.evaluation_runs.gate_check(agent_id, run["id"])["gate"]
# gate["decision"] -> "pass" | "fail"
```

### Feedback and quality

Collect end-user feedback (idempotent + owned via `requestKey`), then review, label, and
adjudicate it, and read recorded business-outcome facts. The closed reason-tag vocabulary is
importable as `FEEDBACK_REASON_TAGS`.

```python
from makeyouragent import FEEDBACK_REASON_TAGS

mya.feedback.submit(agent_id, conversation_id, {
    "targetType": "message", "targetId": message_id, "requestKey": "fb-1",
    "rating": -1, "reasonTags": ["wrong_action"],
})

queue = mya.quality.review_queue(agent_id)
label = mya.quality.create_label(agent_id, {
    "targetType": "turn", "targetId": turn_id, "expectedValues": {"intentKey": "cancel_order"},
})
mya.quality.adjudicate(agent_id, label["id"], {"nextState": "CONFIRMED"})
facts = mya.quality.outcomes(agent_id, {"conversationId": conversation_id})
```

### Decision traces (operator diagnostics)

A redaction-safe, stage-by-stage record of how each turn's decision was made. **Admin scope only**
— a chat-only key or end-user principal is rejected (403). Each turn's trace id is surfaced on
`res["metadata"]["traceId"]`; list traces (without events) or fetch one with its ordered events.

```python
traces = mya.decision_traces.list(agent_id, {"conversationId": conversation_id, "limit": 20})
trace = mya.decision_traces.get(agent_id, traces[0]["id"])
# trace["status"] -> "COMPLETE" | "OPEN" | "WAITING_ASYNC" | ...
# trace["events"] -> [{"sequence": ..., "stage": ..., "eventType": ..., "status": ..., "safePayload": ...}]
```

## Errors

All API and transport failures raise `MakeYourAgentError` with `.status`, `.code`, and `.data`.

```python
from makeyouragent import MakeYourAgentError

try:
    mya.agents.get("does-not-exist")
except MakeYourAgentError as e:
    print(e.status, e.code, e.message)
```

## Configuration

```python
MakeYourAgent(
    api_key="mya_live_...",
    base_url="https://api.makeyouragent.ai",  # default
    timeout=30.0,                              # seconds
    max_retries=3,                             # retries on 5xx with backoff
)
```

## License

MIT
