Metadata-Version: 2.4
Name: somia
Version: 0.1.0a5
Summary: Python SDK for interacting with Somia Agent API
Author: Somia
License-Expression: MIT
Keywords: somia,agents,api,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0.0,>=0.27.0
Requires-Dist: pydantic<3.0.0,>=2.7.0
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.1.0; extra == "langgraph"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Dynamic: license-file

# Somia Python SDK

> **Alpha release (`0.1.0a5`)** — early SDK for testing and evaluation. APIs may change. Install with `pip install somia --pre`.

Official Python client for the [Somia](https://somia-platform.com) platform.

The SDK is open source under the MIT license. Access to the Somia platform requires a Somia account and API key; usage of the hosted API is subject to Somia's terms and billing.

## Supported endpoints

- `POST /v1/external/agent/{agent_id}/session` — create a session
- `POST /v1/external/agent/{agent_id}/session/{session_id}` — continue a session
- `POST /v1/external/runs` — log completed runs for monitoring
- `GET /v1/external/datasets/{dataset_id}` — fetch eval dataset examples
- `POST /v1/external/eval/begin` — reserve an eval run id (local `agent_fn` evals)
- `POST /v1/external/eval/submit` — submit eval runs (local `agent_fn` outputs)
- `POST /v1/external/evaluation-runs` — trigger a server-side eval of an internal pipeline
- `GET /v1/external/eval/{eval_run_id}` — poll eval scoring status (both modes)



## Installation

From PyPI (pre-release):

```bash
pip install somia --pre
```

Or pin the exact version:

```bash
pip install somia==0.1.0a5
```

From source (development):

```bash
git clone https://github.com/somia-platform/somia-python-sdk.git
cd somia-python-sdk
pip install -e ".[dev]"
```

Requires Python 3.11+.

## Quickstart

```python
from somia import SomiaClient

with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    response = client.sessions.create_session(
        agent_id=123,
        input_data="Hello",
        pipeline_version="production",
    )
    print(response.session_id, response.message)
```



## Continue an existing session

```python
from somia import SomiaClient

with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    response = client.sessions.interact_session(
        agent_id=123,
        session_id="550e8400-e29b-41d4-a716-446655440000",
        input_data="Can you explain it in one paragraph?",
    )
    print(response.message)
```

You can also use `client.sessions.run(...)` with an optional `session_id` to create or continue in one call.

## Streaming responses (SSE)

```python
from somia import SomiaClient

with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    events = client.sessions.create_session(
        agent_id=123,
        input_data="Stream this answer",
        stream=True,
    )
    for event in events:
        if event.event_type == "chunk":
            print("chunk:", event.data)
        elif event.event_type == "error":
            print("error:", event.data)
        elif event.event_type == "end":
            print("stream ended")
```



## Log a run (monitoring)

Preferred path for custom agents (and LangGraph graphs that do not emit
LangChain LLM/tool callbacks):

```python
from somia import start_run, node_span, llm_span, tool_span

run = start_run(
    {"question": "hello"},
    agent_slug="my-agent",
    workspace_id=123,  # or set SOMIA_WORKSPACE_ID
)
with node_span("planner") as span:
    answer = "Hi there!"
    span.finish(outputs={"answer": answer})
run.submit_success(output={"answer": answer})
```

`SomiaClient.from_env()` and `start_run` read `SOMIA_API_KEY`, `SOMIA_BASE_URL`,
`SOMIA_AGENT_SLUG`, `SOMIA_WORKSPACE_ID`, and `SOMIA_MONITORING_ENABLED`. Span
helpers no-op when monitoring is off. `log_run` and `SomiaCallbackHandler`
require `workspace_id` or `SOMIA_WORKSPACE_ID`; `start_run` fail-opens and skips
the upload if neither is set.

Low-level alternative (`SomiaTrace` + `client.log_run`):

```python
from somia import SomiaClient, SomiaTrace

with SomiaClient.from_env() as client:
    trace = SomiaTrace().start()
    with trace.span(name="planner", kind="agent", inputs={"question": "hello"}) as span:
        answer = "Hi there!"
        span.finish(outputs={"answer": answer})
    trace.finish()

    result = client.log_run(
        agent_slug="my-agent",
        workspace_id=123,  # or set SOMIA_WORKSPACE_ID
        input={"question": "hello"},
        output={"answer": answer},
        trace=trace.to_dict(),
        status="SUCCESS",
    )
    print(result)
```



## Run an evaluation

`client.eval()` is a single entry point with two execution modes, auto-selected by whether you pass
`agent_fn`:

- **Local (external agents)** — pass `agent_fn`. The SDK fetches the dataset and calls `agent_fn`
  directly, in-process, wherever this script runs (your laptop, a CI job, a notebook). No deployment
  or public endpoint is required.
- **Server (internal pipelines)** — omit `agent_fn`. The Somia platform runs your pipeline itself,
  once per example. Use this for agents that are already deployed on the platform.

To compare candidates before shipping, run local evals against each candidate (a different branch,
checkpoint, or local function) and give each run a different `agent_version`; the results show up
side by side in Somia's Validate tab.

### `agent_slug` vs `agent_id`

- **`agent_slug`** (string, e.g. `"my-agent"`) always identifies an **external** agent — one with no
  Somia-defined pipeline. Used by `client.log_run(...)` and local evals
  (`client.eval(agent_fn=...)`). The backend upserts (get-or-creates) the external agent by slug on
  first use, scoped to your organization.
- **`agent_id`** (integer, e.g. `123`) always identifies an **internal** platform pipeline. Used by
  `client.sessions.run(...)` and by server-side evals (`client.eval(...)` with no `agent_fn`, which
  also accepts a slug/name string as an alternate way to address the same internal pipeline).

If you request a server-side eval for an agent that has no server runner (an external-only agent),
the backend responds with a `400` telling you to pass `agent_fn` instead.

### Local eval (run your function)

```python
from somia import SomiaClient

def my_agent(payload):
    return {"result": f"Echo: {payload}"}

with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    eval_result = client.eval(
        agent_fn=my_agent,
        dataset_id="dataset-uuid",
        profile_id="profile-uuid",
        agent_id="my-agent",  # forwarded on the wire as agent_slug
        agent_version="v1.0.0",
    )
    eval_result.wait(timeout=300)
    print(eval_result.status, eval_result.overall_score)
    print(eval_result.mapping_coverage)
```

Optional ``mapping_input`` remaps each example onto the fields your ``agent_fn``
expects. This is SDK-side only — platform saved mappings are not applied on
``/v1/external/eval/submit``.

```python
eval_result = client.eval(
    agent_fn=my_agent,
    dataset_id="dataset-uuid",
    profile_id="profile-uuid",
    agent_id="my-agent",
    input_fields=[
        {"name": "question", "type": "string", "required": True},
        {"name": "locale", "type": "string", "required": False},
    ],
    mapping_input={
        "question": "q",                   # shorthand: pull from set field "q"
        "locale": {"constant": "en-US"},   # fixed value, not from the set
    },
    force=True,  # default: skip unresolved required fields; do not fail the run
)
print(eval_result.mapping_coverage)
# {"total": N, "resolvable": M, "unresolved_fields": [...]}
# If M == 0, no POST is sent (the API requires at least one run).
```

Declare the same fields on a version when you create it:

```python
client.pipelines.versions.create(
    pipeline_id,
    version="v1.1.3",
    input_fields=[
        {"name": "question", "type": "string", "required": True},
        {"name": "locale", "type": "string", "required": False},
    ],
)
```

Unresolved **required** fields skip that example; unresolved **optional** fields
are omitted from the payload. Pass ``runs=[...]`` (no ``agent_fn`` / no
``dataset_id``) to submit an ad-hoc eval.



### Server-side eval (platform runs your pipeline)

```python
with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    eval_result = client.eval(
        agent_id=123,                       # internal pipeline id
        profile_id="profile-uuid",
        set_ids=["set-uuid"],              # or dataset_id="set-uuid" for a single set
    )
    eval_result.wait(timeout=300)
    print(eval_result.status, eval_result.overall_score)
```



## LangGraph integration (optional)

Install optional dependency:

```bash
pip install "somia[langgraph]"
```

Then attach the callback:

```python
from somia import SomiaClient
from somia.integrations import SomiaCallbackHandler

with SomiaClient(
    base_url="https://platform.somiasolutions.com/api",
    api_key="your_api_key",
) as client:
    handler = SomiaCallbackHandler(
        client=client,
        agent_slug="my-agent",
        workspace_id=123,  # or set SOMIA_WORKSPACE_ID
    )
    result = graph.invoke({"input": "Hello"}, config={"callbacks": [handler]})
```



## Authentication

The SDK sends your API key in the `x-api-key` header.

## Error handling

```python
from somia import AuthError, RateLimitError, ServiceUnavailableError, SomiaClient

try:
    with SomiaClient(base_url="https://platform.somiasolutions.com/api", api_key="...") as client:
        client.sessions.create_session(agent_id=123, input_data="Hi")
except AuthError:
    print("Invalid API key")
except RateLimitError:
    print("Rate limit exceeded")
except ServiceUnavailableError as exc:
    print(f"Retry later: {exc}")
```

Common error classes:

- `BadRequestError` — invalid payloads
- `AuthError` — missing or invalid API key
- `PermissionDeniedError` — access or usage-limit failures
- `NotFoundError` — missing agents or sessions
- `RateLimitError` — rate limits exceeded
- `ServiceUnavailableError` — platform saturation or timeouts
- `ServerError` — unexpected server-side failures
- `TransportError` — network-level failures
- `StreamParseError` — malformed SSE payloads



## Session flow notes

- Create-session may ignore `input_data` when Begin has no required query fields.
- Interact-session always uses `session_id` in the path and forwards `input_data` as the turn payload.
- Non-streaming responses include fields such as `session_id`, `message`, `history`, `pending`, and `finished`.



## Development

See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, testing, versioning, and release instructions.

## License

MIT — see [LICENSE](LICENSE).
