Metadata-Version: 2.4
Name: seltz
Version: 1.14.0
Summary: Seltz Python SDK for AI-powered search
Author-email: Seltz <support@seltz.ai>
Project-URL: Homepage, https://seltz.ai
Project-URL: Documentation, https://docs.seltz.ai
Project-URL: Repository, https://github.com/seltz-ai/seltz-python-sdk
Project-URL: Bug Tracker, https://github.com/seltz-ai/seltz-python-sdk/issues
Keywords: seltz,search,ai,sdk,api,web-search,news-search
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: grpcio>=1.76.0
Requires-Dist: protobuf<7,>=5.29.5
Requires-Dist: typing_extensions>=4.0

<div align="center">
  <img src="https://repository-images.githubusercontent.com/1108844600/672f4876-01a4-4fde-8ac2-ecab4c914c98">
</div>

<p align="center">
  <!-- Python -->
  <a href="https://www.python.org" alt="Python"><img src="https://badges.aleen42.com/src/python.svg"></a>
  <!-- Version -->
  <a href="https://pypi.org/project/seltz/"><img src="https://img.shields.io/pypi/v/seltz?color=light-green" alt="PyPI version"></a>
</p>

# Seltz Python SDK

Official Python SDK for [Seltz](https://seltz.ai), the Web search engine for AI agents.

## 💾 Installation

```bash
pip install seltz
```

Requires Python 3.9 or higher.

## ⚡️ Quick Start

```python
from seltz import Seltz

client = Seltz(api_key="your-api-key")

# Search the Web
response = client.search("best ai search engines", max_results=10)

# Access results
for document in response.documents:
    print(f"URL: {document.url}")
    print(f"Content: {document.content}")
```

**Output:**

```
URL: https://www.best-ai-search-engines.com
Content: Generative AI can make finding information faster and more intuitive.
If you’re tired of traditional search, explore some of the best AI-powered
search engines we've tested...
```

### Search tiers

`tier` selects which search tier serves the request, `"base"` or `"pro"`. It
never changes which corpus is searched — that is `scope` — and the two are
orthogonal: any scope can be requested in either tier.

Omitting `tier` defaults to `"pro"`, the higher-precision tier, which is what a
caller who expressed no preference should get. `"base"` is therefore opt-out
rather than opt-in — name it explicitly to skip the reranking:

```python
response = client.search(
    "best ai search engines",
    tier="base",
)
```

The SDK sends no tier of its own when you omit the argument, so the default
follows the service rather than being pinned here.

`tier` is typed as a plain `str` and the name is forwarded to the API as given.
The service owns the set of tiers: it matches the name case-insensitively and
rejects one it does not recognize, in a `400` that names the tiers it accepts.
The SDK carries no list of tier names, so tiers can be added or renamed without
an SDK release — check the API reference for the current names.

### Result fields

`fields` selects which members of each result document come back. Content is
the default; `url` and `published_date` are always emitted:

```python
from seltz import Seltz

client = Seltz(api_key="your-api-key")

response = client.search(
    "best ai search engines",
    fields={"content": True, "snippets": True},
)

for document in response.documents:
    print(f"URL: {document.url}")
    for snippet in document.snippets:
        print(f"  {snippet.text}")
```

`snippets` are the passages of a document that best match your query, in
descending order of score — useful when you want the relevant part of a long
page rather than the whole thing, and cheaper to feed to a model.

**A selection you pass is taken literally.** `{"snippets": True}` asks for
snippets and nothing else, so the documents come back with no content. Name
every member you want:

```python
# passages only — document.content is empty
response = client.search("ai news", fields={"snippets": True})

# both
response = client.search("ai news", fields={"content": True, "snippets": True})
```

Snippets are only available on scopes whose index produces them; elsewhere the
list comes back empty rather than erroring.

**A member also takes a ceiling instead of `True`.** Pass an object in place of
the boolean to bound how much of that member comes back:

```python
# content, capped at 500 characters per result
response = client.search("ai news", fields={"content": {"max_characters_per_result": 500}})

# a ceiling on each member
response = client.search(
    "ai news",
    fields={
        "content": {"max_characters_per_result": 500},
        "snippets": {"max_snippets_per_result": 5, "max_tokens_per_result": 400},
    },
)
```

An object selects the member as well as bounding it, so
`{"content": {"max_characters_per_result": 500}}` returns content and no
snippets. `max_characters_per_result` counts Unicode code points and accepts
100 to 1000000; a value outside that range is a 400 naming the field and the
bound.

### Answer

Get a natural-language answer grounded in Web search results, with citations:

```python
from seltz import Seltz

client = Seltz(api_key="your-api-key")

response = client.answer("Who is Apple's next CEO?")

print(response.answer)
for citation in response.citations:
    print(f"Source: {citation.url}")
```

Pass `model` to pick an answer tier. It defaults to `seltz-base`; `seltz-pro` runs agentic RAG over a single grounding search:

```python
response = client.answer("Who is Apple's next CEO?", model="seltz-pro")
```

Pass `response_format` (an OpenAI-style object) to get structured output. `response.answer` then carries a JSON string matching your schema instead of Markdown; `response.citations` are still returned:

```python
response = client.answer(
    "Who is Apple's next CEO?",
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "news_summary",
            "schema": {
                "type": "object",
                "properties": {"summary": {"type": "string"}},
                "required": ["summary"],
                "additionalProperties": False,
            },
        },
    },
)

import json
print(json.loads(response.answer)["summary"])
```

Pass `system_prompt` to steer how the answer is presented — tone, voice, format:

```python
response = client.answer(
    "Who is Apple's next CEO?",
    system_prompt="Answer in British English. Open with a one-line summary, then the detail.",
)
```

### Answer (streaming)

Stream an answer as it is generated, instead of waiting for the full response. `answer_stream` yields events as they arrive: a `citations` event first, then `text_delta` chunks, then a terminal `finish_reason`. Inspect each event with `event.WhichOneof("event")`:

```python
from seltz import Seltz

client = Seltz(api_key="your-api-key")

for event in client.answer_stream("Who is Apple's next CEO?"):
    kind = event.WhichOneof("event")
    if kind == "citations":
        for citation in event.citations.citations:
            print(f"Source: {citation.url}")
    elif kind == "text_delta":
        print(event.text_delta, end="", flush=True)
    elif kind == "finish_reason":
        print()
```

Streaming is also available asynchronously via `AsyncSeltz` — `async for` over the events:

```python
import asyncio

from seltz import AsyncSeltz


async def main():
    async with AsyncSeltz(api_key="your-api-key") as client:
        async for event in client.answer_stream("Who is Apple's next CEO?"):
            kind = event.WhichOneof("event")
            if kind == "citations":
                for citation in event.citations.citations:
                    print(f"Source: {citation.url}")
            elif kind == "text_delta":
                print(event.text_delta, end="", flush=True)
            elif kind == "finish_reason":
                print()


asyncio.run(main())
```

### Agent runs

An agent run researches a question with Seltz search and returns a grounded,
cited answer. Runs are asynchronous — create one and poll it, or let the SDK
wait for you:

```python
from seltz import Seltz

client = Seltz(api_key="your-api-key")

run = client.agent.create_and_wait(
    "Who are the current CEOs of OpenAI, Anthropic and Mistral AI?"
)
print(run.output.text)  # cited markdown; [n] markers cite output.sources
for source in run.output.sources:
    print(f"  [{source.id}] {source.url}")
```

`wait` and `create_and_wait` return once the run reaches a terminal `status`
(`AGENT_RUN_STATUS_COMPLETED`, `_FAILED`, or `_CANCELLED` — compare with the
`AgentRunStatus` enum); `run.stop_reason` says why it ended.

Pass an OpenAI-style `response_format` object as `output_schema` to also get
structured output (`run.output.structured`, a JSON string shaped by your
schema, with per-field citations in `run.output.grounding`):

```python
run = client.agent.create_and_wait(
    "Who are the current CEOs of OpenAI, Anthropic and Mistral AI?",
    output_schema={
        "type": "json_schema",
        "json_schema": {
            "name": "companies",
            "schema": {
                "type": "object",
                "properties": {
                    "companies": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "ceo": {"type": "string"},
                            },
                        },
                    }
                },
            },
        },
    },
)
print(run.output.structured)
```

The lower-level pieces are there when you need them: `client.agent.create`
returns the pending run at once, `client.agent.get(run_id)` polls it,
`client.agent.wait(run_id)` polls to completion (an optional `timeout` bounds
the wait client-side; the run keeps executing), `client.agent.cancel(run_id)`
stops a run, and `client.agent.list()` pages through past runs newest-first
via its `next` cursor.
### Fetch

Turn URLs into LLM-ready Markdown. Up to 20 per call, fetched concurrently:

```python
from seltz import FetchStatus, Seltz

client = Seltz(api_key="your-api-key")

response = client.fetch(["https://example.com/"])

for result in response.results:
    if result.status == FetchStatus.FETCH_STATUS_OK:
        print(result.markdown)
    else:
        print(f"{result.requested_url}: {result.error.code}")
```

A page that cannot be fetched is not a call failure. Every requested URL gets a
result, in the order requested, and a failed one carries
`status = FetchStatus.FETCH_STATUS_ERROR` and an `error.code`.

### Async

The same API is available asynchronously via `AsyncSeltz` — `await` each call:

```python
import asyncio

from seltz import AsyncSeltz


async def main():
    client = AsyncSeltz(api_key="your-api-key")

    response = await client.search("best ai search engines", max_results=10)

    for document in response.documents:
        print(f"URL: {document.url}")
        print(f"Content: {document.content}")


asyncio.run(main())
```

To close the connection deterministically rather than leaving it to garbage collection, use `AsyncSeltz` as an async context manager (`async with`) or call `await client.close()` when done.

## 📚 Documentation

Browse the [documentation](https://docs.seltz.ai) for more details.
