octen 0.7.5


pip install octen

  Latest version

Released: Jul 27, 2026

Project Links

Meta
Author: Octen Team
Requires Python: >=3.8

Classifiers

Development Status
  • 4 - Beta

Intended Audience
  • Developers

License
  • OSI Approved :: MIT License

Programming Language
  • Python :: 3
  • Python :: 3.8
  • Python :: 3.9
  • Python :: 3.10
  • Python :: 3.11
  • Python :: 3.12

Topic
  • Software Development :: Libraries :: Python Modules
  • Internet :: WWW/HTTP

Octen Python SDK

PyPI version Python Support License: MIT

Official Python SDK for the Octen API โ€” web search, URL extraction, text embeddings, and multi-model LLM chat in one package.

โœจ Features

  • ๐Ÿ” Web Search โ€” search and retrieve ranked web results with filtering, highlighting, and full content
  • ๐ŸŒ URL Extract โ€” fetch and parse 1-20 URLs in a single batch with markdown / text output, query-driven highlights, and media (images / videos / audio / favicon)
  • ๐Ÿ’ฌ Multi-model Chat โ€” access 15+ LLMs (GPT, Claude, Gemini, Kimi, MiniMax, Qwen, DeepSeek) through a single unified API
  • ๐Ÿงฎ Text Embeddings โ€” convert text into high-quality vector representations
  • ๐Ÿ–ผ๏ธ VL (Multimodal) Embeddings โ€” encode text, images, and videos into a single fused vector or independent per-element vectors
  • โšก Streaming (SSE) โ€” real-time token streaming with typed event objects
  • ๐Ÿ”„ Auto Retry โ€” exponential backoff for transient errors
  • ๐Ÿ›ก๏ธ Type Safe โ€” full Pydantic models with IDE auto-completion
  • ๐Ÿ”€ Async Support โ€” native asyncio client for concurrent workloads
  • ๐Ÿ“ฆ HTTP/2 โ€” connection pooling and keep-alive out of the box

๐Ÿ“ฆ Installation

pip install octen

Requires Python 3.8 or higher.

Development Version

pip install octen[dev]

Async Support

pip install octen[async]

๐Ÿš€ Quick Start

Search

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.search.search(query="Python programming", count=5)

    for result in response.results:
        print(f"Title: {result['title']}")
        print(f"URL: {result['url']}")
        print(f"Highlight: {result.get('highlight', '')}")

Broad Search

Decompose a query into multiple sub-queries, search them concurrently, and get results grouped per sub-query. Best for comparisons (pricing, products, vendors), surveys/research, and multi-angle questions where a single search() only reaches a few subtopics โ€” pass the query as-is and raise max_queries for broader coverage.

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.search.broad_search(
        query="compare cloud GPU pricing across providers",
        max_queries=5,   # up to N sub-queries (1-30, default 5)
        count=10,        # per-sub-query SearchOptions, same as search()
    )

    print("Sub-queries:", response.queries)
    for group in response.search_results:
        print(f"\n# {group['query']}  ({group.get('latency')} ms)")
        for result in group["results"]:
            print(f"  {result['title']} โ€” {result['url']}")

Extract

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.extract.extract(
        urls=["https://example.com", "https://octen.ai"],
        format="markdown",
    )

    for item in response.items:
        if item.status == "success":
            print(f"{item.title} โ€” {item.url}")
        else:
            print(f"FAILED {item.url}: {item.error_message}")

Chat

from octen import Octen, ChatMessage, OctenSearchTool

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="openai/gpt-5.4",
        messages=[ChatMessage(role="user", content="Hello!")],
        tools=[OctenSearchTool()],  # enable live web search
    )
    print(response.text)

Embeddings

from octen import Octen

with Octen(api_key="your-api-key") as client:
    embedding = client.embedding.create(
        input=["Hello, world!"],
        model="octen-embedding-4b"
    )
    vector = embedding.get_first_embedding()
    print(f"Vector dimension: {len(vector)}")

VL (Multimodal) Embeddings

from octen import Octen

with Octen(api_key="your-api-key") as client:
    # Fuse text + image into one vector
    response = client.vl_embedding.create(
        model="octen-vl-embedding-large",
        contents=[
            {"text": "A cute orange cat on a wooden chair"},
            {"image": "https://example.com/cat.jpg"},
        ],
        enable_fusion=True,
    )
    vector = response.get_first_embedding()
    if vector is not None:
        print(f"Fused vector dimension: {len(vector)}")

๐Ÿ” Search API

Advanced Search

from octen import Octen, HighlightOptions, FullContentOptions

with Octen(api_key="your-api-key") as client:
    response = client.search.search(
        query="machine learning best practices",
        count=10,
        topic="news",  # general (default) or news
        include_domains=["github.com", "arxiv.org"],  # Search only these domains
        language=["en", "zh"],  # Filter results by language (ISO 639-1 codes)
        time_range="month",  # Relative time window
        include_images=True,  # Return image URLs per result
        highlight=HighlightOptions(
            enable=True,
            max_tokens=500
        ),
        full_content=FullContentOptions(
            enable=True,
            max_tokens=2000
        ),
        timeout=60.0  # Custom timeout
    )

    print(f"Found {len(response.results)} results")
    print(f"Token usage: {response.usage}")

๐ŸŒ Extract API

Fetch and parse one or more URLs into structured content. A single request accepts 1-20 URLs and is served as one upstream batch.

Batch Extract

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.extract.extract(
        urls=[
            "https://example.com",
            "https://octen.ai",
            "https://www.iana.org/about",
        ],
        format="markdown",          # "text" or "markdown" (default markdown)
        max_age_seconds=600,        # accept results cached within 10 minutes
        timeout=30,                 # per-URL upstream fetch budget (1-60s)
        include_images=True,        # also returns cover_image when the page has one
        request_timeout=90.0,       # local httpx deadline; >= timeout + overhead
    )

    print(f"OK: {response.successful_urls}/{response.total_urls}  ({response.latency}ms)")

    for item in response.items:
        if item.status == "success":
            print(f"  โœ“ {item.title} โ€” {item.url}")
            if item.favicon:  # favicon is returned by default when available
                print(f"    favicon: {item.favicon}")
            if item.cover_image:
                print(f"    cover: {item.cover_image.url}")
        else:
            # Partial-success is first-class: failed URLs don't poison siblings.
            print(f"  โœ— {item.url}: {item.error_message}")

Note: response.items is parsed lazily and raises OctenAPIError if any row fails to parse (signals server schema drift). The raw dicts remain accessible via response.results as an escape hatch.

Query-driven Highlights

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.extract.extract(
        urls=["https://en.wikipedia.org/wiki/Python_(programming_language)"],
        query="async programming",   # max 500 chars
    )

    for item in response.items:
        for snippet in item.highlights or []:
            print(f"โ€ข {snippet}")

Single URL (Convenience Shortcut)

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.extract.simple_extract("https://example.com")
    print(response.items[0].title)

Two Timeouts, Two Layers

timeout and request_timeout operate at different layers โ€” easy to confuse, important to get right:

Parameter Layer Controls On timeout
timeout (int, 1-60s) server upstream per-URL fetch budget the slow URL is reported with status="failed" and an error_message; sibling URLs in the same batch are returned normally as long as the upstream responds within the bounded round trip
request_timeout (float) client (httpx) local socket deadline for the whole HTTP call raises OctenTimeoutError

Rule of thumb: request_timeout >= timeout + network_overhead.

๐Ÿ–ผ๏ธ Image Search API

In Beta โ€” contact us to request beta access.

Multimodal image search by text query and/or a reference image URL.

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.image_search.search(
        query="golden retriever puppy",   # text input
        # image_url="https://example.com/ref.jpg",  # optional reference image
        topic="general",                  # "general" or "design"
        count=5,                          # 1-10 (default 5)
    )

    for result in response.results:
        print(result["title"], result["url"], result.get("thumbnail"))

๐ŸŽฌ Video Search API

In Beta โ€” contact us to request beta access.

Text-driven video search.

from octen import Octen

with Octen(api_key="your-api-key") as client:
    response = client.video_search.search(
        query="how to make fresh pasta",
        count=5,                          # 1-10 (default 5)
    )

    for result in response.results:
        print(result["title"], result["url"], result.get("duration_seconds"))

๐Ÿ’ฌ Chat API

Web search is now a tool. The old top-level web_search / web_search_options parameters were removed. Enable web search by passing an OctenSearchTool in tools; tune it with OctenSearchToolParameters. For broad (multi-query) search, pass an OctenBroadSearchTool with OctenBroadSearchToolParameters instead.

Non-streaming

from octen import Octen, ChatMessage, OctenSearchTool, OctenSearchToolParameters

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="openai/gpt-5.4",
        messages=[
            ChatMessage(role="system", content="You are a helpful assistant."),
            ChatMessage(role="user", content="What happened in tech today?"),
        ],
        tools=[OctenSearchTool(parameters=OctenSearchToolParameters(safesearch="off", count=5))],
        max_tokens=500,
        temperature=0.7
    )

    print(response.text)
    print(f"Tokens used: {response.usage.total_tokens}")

    # Access search results
    if response.search_results:
        for group in response.search_results:
            for item in group.results:
                print(f"  - {item.title}: {item.url}")

Streaming

from octen import Octen, ChatMessage, OctenSearchTool

with Octen(api_key="your-api-key") as client:
    for event in client.chat.create(
        model="openai/gpt-5.4",
        messages=[ChatMessage(role="user", content="Tell me a story")],
        stream=True,
        tools=[OctenSearchTool()],
    ):
        if event.type == "search_done":
            print(f"[{len(event.search_results or [])} search groups]")

        elif event.type == "content" and event.choices:
            print(event.choices[0].delta.content or "", end="", flush=True)

        elif event.type == "finish":
            print()  # newline

        elif event.type == "usage" and event.usage:
            print(f"[total tokens: {event.usage.total_tokens}]")

Tool Calling

from octen import Octen
from octen.models import ChatMessage, Tool, ToolFunction

weather_tool = Tool(
    function=ToolFunction(
        name="get_weather",
        description="Get current weather for a city",
        parameters={
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
            },
            "required": ["city"],
        }
    )
)

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="openai/gpt-5.4",
        messages=[ChatMessage(role="user", content="What's the weather in London?")],
        tools=[weather_tool],
        tool_choice="auto"
    )
    if response.choices[0].finish_reason == "tool_calls":
        tc = response.choices[0].message.tool_calls[0]
        print(f"Tool: {tc.function.name}, Args: {tc.function.arguments}")

JSON Output Mode

from octen import Octen, ChatMessage
from octen.models import ResponseFormat

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="google/gemini-3-flash-preview",
        messages=[ChatMessage(role="user", content="Return a JSON list of 3 programming languages")],
        response_format=ResponseFormat(type="json_object"),
    )
    print(response.text)

Web Search with Full Page Content

from octen import Octen, ChatMessage, OctenSearchTool, OctenSearchToolParameters
from octen.models.search import FullContentOptions

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="openai/gpt-5.4",
        messages=[ChatMessage(role="user", content="Latest Python 3.13 features?")],
        tools=[OctenSearchTool(parameters=OctenSearchToolParameters(
            safesearch="off",
            full_content=FullContentOptions(enable=True, max_tokens=2048),
        ))],
    )
    print(f"Full content tokens: {response.usage.full_content_tokens}")

Broad Web Search (multi-query)

The octen_broad_search tool fans the question out into up to max_queries sub-queries and searches them concurrently โ€” a peer of octen_search in tools. Results land in response.search_results as usual.

from octen import Octen, ChatMessage, OctenBroadSearchTool, OctenBroadSearchToolParameters

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="openai/gpt-5.4",
        messages=[ChatMessage(role="user", content="Compare the leading vector databases")],
        tools=[OctenBroadSearchTool(parameters=OctenBroadSearchToolParameters(
            max_queries=3,   # up to 3 generated sub-queries (1-30, default 5)
            count=5,         # results per sub-query
        ))],
    )
    print(response.text)
    for group in response.search_results or []:
        print(f"[{group.query}] {len(group.results)} results")

Prompt Caching (content blocks)

from octen import Octen, ChatMessage, ChatContentBlock, CacheControl

with Octen(api_key="your-api-key") as client:
    response = client.chat.create(
        model="anthropic/claude-sonnet-4.6",
        messages=[
            ChatMessage(role="user", content=[
                ChatContentBlock(
                    type="text",
                    text="<long reusable context...>",
                    cache_control=CacheControl(ttl="1h"),  # 5m (default) or 1h
                ),
                ChatContentBlock(type="text", text="Summarise the above."),
            ]),
        ],
    )
    print(response.usage.prompt_tokens_details.cached_tokens)

๐Ÿค– Supported Chat Models

For the full and up-to-date list of supported models, visit the Octen official website.

๐Ÿงฎ Embeddings API

Batch Embeddings

from octen import Octen

with Octen(api_key="your-api-key") as client:
    # Process multiple texts
    texts = [
        "Artificial intelligence is transforming the world",
        "Applications of deep learning",
        "Natural language processing technology"
    ]

    response = client.embedding.create(
        input=texts,
        model="octen-embedding-8b",
        input_type="document"
    )

    vectors = response.get_embeddings()
    print(f"Generated {len(vectors)} vectors")

    # Or use convenience methods
    query_vector = client.embedding.embed_query("search query")
    doc_vectors = client.embedding.embed_documents(["document 1", "document 2"])

Multimodal (VL) Embeddings

The vl_embedding resource encodes text, images, and videos through the /vl-embedding endpoint. Two output modes:

  • Fusion (enable_fusion=True) โ€” all contents collapse into a single vector (type="fusion")
  • Independent (enable_fusion omitted or False) โ€” one vector per content element (type="vl"). Omitting the field inherits the server default (False).

Default-value semantics: enable_fusion / fps / instruct default to None in the SDK and are stripped from the wire via exclude_none=True. When omitted, the server applies its own defaults (shown in the table). enable_fusion=False and omitting the field produce different wire payloads but currently the same behavior โ€” pass False explicitly only if you need to override a future server default change.

Parameter Type Required Description
model string โœ“ octen-vl-embedding (max dim 2048) or octen-vl-embedding-large (max dim 4096)
contents list[dict | VLEmbeddingContent] โœ“ Each item has exactly one of text / image / video (non-empty). Max 20 total, 5 images, 1 video
enable_fusion bool โ€” Single fused vector vs. independent vectors. Omit โ†’ server default False
dimension int โ€” Output dim, โ‰ค model's max. Omit โ†’ model's max
fps float (0-1) โ€” Video frame sampling density. Omit โ†’ server default 1; lower values reduce video token cost
instruct string โ€” Custom task description. Omit โ†’ server default "Represent the user's input."

Content element constraints:

  • text โ€” string, โ‰ค 32,000 tokens per entry
  • image โ€” URL or Base64 string, โ‰ค 5MB, formats: JPEG/PNG/WEBP/BMP/TIFF/ICO/DIB/ICNS/SGI
  • video โ€” URL only, โ‰ค 50MB, formats: MP4/AVI/MOV
from octen import Octen, VLEmbeddingContent

with Octen(api_key="your-api-key") as client:
    # 1) Text-only embedding
    response = client.vl_embedding.create(
        model="octen-vl-embedding",
        contents=[{"text": "What is multimodal vector search?"}],
    )
    vector = response.get_first_embedding()
    if vector is not None:
        print(f"text-only dim={len(vector)}")  # 2048

    # 2) Fused multimodal embedding (text + images + video)
    response = client.vl_embedding.create(
        model="octen-vl-embedding-large",
        contents=[
            {"text": "Outdoor tent, 3-4 person, waterproof"},
            {"image": "https://example.com/tent_setup.jpg"},
            {"image": "https://example.com/tent_inside.jpg"},
            {"video": "https://example.com/tent_demo.mp4"},
        ],
        enable_fusion=True,
        dimension=2048,
        fps=0.3,
        instruct="Represent the outdoor product for retrieval",
    )
    print(f"fusion type={response.items[0].type} dim={response.items[0].dimension}")

    # 3) Independent per-image embeddings
    response = client.vl_embedding.create(
        model="octen-vl-embedding",
        contents=[
            {"image": "https://example.com/product_1.jpg"},
            {"image": "https://example.com/product_2.jpg"},
        ],
        enable_fusion=False,
    )
    for item in response.items:
        print(f"index={item.index} type={item.type} dim={item.dimension}")

    # 4) Typed content objects (alternative to dicts)
    response = client.vl_embedding.create(
        model="octen-vl-embedding",
        contents=[
            VLEmbeddingContent(text="A photo of a cat"),
            VLEmbeddingContent(image="https://example.com/cat.jpg"),
        ],
        enable_fusion=True,
    )

    # Usage breakdown
    if response.usage:
        print(
            f"input_tokens={response.usage.input_tokens} "
            f"text_tokens={response.usage.text_tokens} "
            f"image_tokens={response.usage.image_tokens} "
            f"image_count={response.usage.image_count} "
            f"duration={response.usage.duration}s"
        )

Async usage:

import asyncio
from octen import AsyncOcten

async def main():
    async with AsyncOcten(api_key="your-api-key") as client:
        response = await client.vl_embedding.create(
            model="octen-vl-embedding-large",
            contents=[
                {"text": "modern art exhibition"},
                {"image": "https://example.com/painting.jpg"},
            ],
            enable_fusion=True,
        )
        print(response.get_first_embedding()[:3])

asyncio.run(main())

๐Ÿ“– Full API reference: https://docs.octen.ai/api-reference/vl-embedding

Custom Configuration

from octen import Octen

client = Octen(
    api_key="your-api-key",
    base_url="https://api.octen.ai",  # Custom API endpoint
    timeout=10.0,  # Global default timeout (seconds)
    max_retries=3,  # Maximum retry attempts
    http2=True  # Enable HTTP/2
)

try:
    # This request uses global timeout (10 seconds)
    response1 = client.search.search("query 1")

    # This request overrides timeout to 30 seconds
    response2 = client.search.search("complex query", timeout=30.0)
finally:
    client.close()  # Release connection pool resources

๐Ÿ“š API Documentation

Search API

client.search.search()

Perform a web search query.

Parameters:

  • query (str, required): Search query string, max 500 characters
  • topic (str, optional): Search category, options: "general" (default), "news"
  • count (int, optional): Number of results to return, range 1-100, default 5
  • include_domains (List[str], optional): Include only results from these domains (max 1000, each โ‰ค30 chars)
  • exclude_domains (List[str], optional): Exclude results from these domains (max 150, each โ‰ค30 chars)
  • include_text (List[str], optional): Results must contain these texts (max 5, each โ‰ค30 chars)
  • exclude_text (List[str], optional): Results must exclude these texts (max 5, each โ‰ค30 chars)
  • language (List[str], optional): Languages to filter results by, as a list of ISO 639-1 codes (e.g. ["en", "ja"]). Supported: ar, de, en, es, fr, hi, id, it, ja, ko, nl, pl, pt, ru, th, tr, vi, zh. Empty/omitted = no language filter. See https://localizely.com/iso-639-1-list/
  • time_basis (str, optional): Time basis, options: "auto", "published", "crawled"
  • time_range (str, optional): Relative time window, options: "day", "week", "month", "year" (or "d", "w", "m", "y")
  • start_time (str, optional): Start time in ISO 8601 format
  • end_time (str, optional): End time in ISO 8601 format
  • highlight (HighlightOptions, optional): Highlight options (max_tokens 100โ€“20000, default 512)
  • format (str, optional): Content format, options: "text", "markdown"
  • safesearch (str, optional): Safe search, options: "off", "strict" (default)
  • full_content (FullContentOptions, optional): Full content options (max_tokens 100โ€“100000, default 2048)
  • include_images (bool, optional): Include image URLs in results, default false
  • include_videos (bool, optional): Include video URLs in results, default false
  • timeout (float, optional): Request timeout in seconds

Returns: SearchResponse object

Response Properties:

  • results - List of search results
  • query - The actual query used
  • usage - Token usage information
  • latency - Latency information

client.search.broad_search()

Decompose a query into related sub-queries, search them concurrently, and return results grouped per sub-query.

Parameters:

  • query (str, required): Original search query, max 500 characters
  • max_queries (int, optional): Upper bound on sub-queries generated, range 1-30, default 5
  • all per-sub-query options from search() (topic, count, include_domains, exclude_domains, include_text, exclude_text, language, time_basis, time_range, start_time, end_time, highlight, format, safesearch, full_content, include_images, include_videos) โ€” applied to every sub-query (sent nested inside search_options)
  • timeout (float, optional): Request timeout in seconds

Returns: BroadSearchResponse object

Response Properties:

  • queries - List of generated sub-queries
  • search_results - Per-sub-query result groups ({query, results, latency})
  • query - The original query
  • usage - Token usage information
  • latency - Latency information

Extract API

client.extract.extract()

Fetch and parse one or more URLs in a single batch.

Parameters:

  • urls (List[str], required): URLs to extract โ€” 1-20 per request, each โ‰ค 2048 characters
  • format (str, optional): Content format โ€” "text" or "markdown" (server default "markdown")
  • max_age_seconds (int, optional): Cache window in seconds (minimum 300); older cached versions are re-fetched. Defaults to 86_400 (24h) when omitted. Values outside the allowed range are adjusted server-side
  • query (str, optional): Query for per-result highlight extraction, max 500 characters. When set, each item's highlights field is populated
  • timeout (int, optional): Per-URL upstream fetch budget in seconds, range 1-60, default 30. A URL that exceeds this returns status="failed" โ€” siblings continue
  • include_images (bool, optional): Return image objects, and cover_image when the page has one (default False)
  • include_videos (bool, optional): Return video objects (default False)
  • include_audio (bool, optional): Return audio objects (default False)
  • request_timeout (float, optional): Local HTTP socket deadline (httpx), distinct from the upstream timeout above. Set >= timeout + network_overhead

Caller typos (e.g. url= instead of urls=, inculde_images=) are rejected at construction time by Pydantic โ€” they will not silently reach the server.

Returns: ExtractResponse object

Response Properties:

  • code (int) โ€” 0 on success
  • msg (str) โ€” Server message
  • request_id (str | None) โ€” Server-generated request id; echoed from X-Request-Id if you supplied that header
  • results (List[dict]) โ€” Raw per-URL result dicts
  • items (List[ExtractItem]) โ€” Parsed, typed per-URL results
  • usage (dict | None) โ€” {"total_urls": int, "successful_urls": int}
  • total_urls (int | None) โ€” Convenience accessor
  • successful_urls (int | None) โ€” Number of URLs with status="success" in this batch
  • latency (int | None) โ€” End-to-end server latency in ms
  • warning (str | None) โ€” Non-fatal warning

ExtractItem Fields:

  • url (str) โ€” The requested URL
  • status (Literal["success", "failed"]) โ€” Extraction outcome
  • title (str | None) โ€” Page title
  • full_content (str | None) โ€” Extracted page content
  • highlights (List[str] | None) โ€” Snippets when query was set
  • time_published / time_last_crawled (str | None) โ€” ISO 8601 timestamps
  • error_message (str | None) โ€” Populated only when status="failed"
  • favicon (str | None) โ€” Page favicon URL; returned by default when available
  • cover_image (ExtractCoverImage | None) โ€” Page cover image {url} (when include_images=True and the page has one)
  • images / videos / audio (List[Any] | None) โ€” Passthrough media objects; schema is upstream-defined
  • category (ExtractCategory | None) โ€” primary / secondary classification labels (when classifier enabled server-side)
  • page_structure (ExtractPageStructure | None) โ€” primary / secondary structure labels

client.extract.simple_extract(url)

Convenience shortcut for a single URL with default parameters.

response = client.extract.simple_extract("https://example.com")

Chat API

client.chat.create()

Create a chat completion (non-streaming or streaming).

Parameters:

  • messages (List[ChatMessage | dict], required): Conversation history. Each item can be a ChatMessage object or a plain dict {"role": ..., "content": ...}
  • model (str, required): Model ID (e.g. "openai/gpt-5.4"). See Supported Chat Models for the full list
  • stream (bool, optional): If True, return a Stream iterator of StreamEvent objects. Default False
  • max_tokens (int, optional): Maximum number of output tokens
  • max_completion_tokens (int, optional): Alternative max-token parameter
  • temperature (float, optional): Sampling temperature [0, 2]
  • top_p (float, optional): Nucleus sampling probability (0, 1]
  • top_k (int, optional): Top-k sampling
  • min_p (float, optional): Minimum probability cutoff [0, 1]
  • top_a (float, optional): Top-a sampling [0, 1]
  • repetition_penalty (float, optional): Repetition penalty (0, 2]
  • frequency_penalty (float, optional): Frequency penalty [-2, 2]
  • presence_penalty (float, optional): Presence penalty [-2, 2]
  • response_format (ResponseFormat, optional): Output format โ€” ResponseFormat(type="text"), ResponseFormat(type="json_object"), or ResponseFormat(type="json_schema", json_schema=...)
  • stop (List[str], optional): Up to 4 stop sequences
  • seed (int, optional): Integer seed for deterministic sampling
  • reasoning_effort (str, optional): Chain-of-thought effort: "xhigh", "high", "medium", "low", "minimal", or "none"
  • reasoning_max_tokens (int, optional): Max reasoning token budget (min 1024)
  • verbosity (str, optional): Output verbosity: "low", "medium", or "high"
  • parallel_tool_calls (bool, optional): Allow parallel tool calls (default True server-side)
  • previous_response_id (str, optional): Continue from a previous response
  • logprobs (bool, optional): Whether to return log probabilities
  • top_logprobs (int, optional): Number of most-likely tokens [0, 20]. Requires logprobs=True
  • logit_bias (Dict[str, float], optional): Token ID to bias value mapping
  • tools (List[Tool | OctenSearchTool | OctenBroadSearchTool | dict], optional): Tool definitions available to the model. Three kinds:
    • Function tool โ€” Tool(function=ToolFunction(name=..., description=..., parameters=..., strict=...))
    • Built-in web search โ€” OctenSearchTool(parameters=OctenSearchToolParameters(...)). Parameters: max_searches (default 5) plus the shared web-search options: topic (general/news), count (1-100), include_domains/exclude_domains, include_text/exclude_text (โ‰ค5 each), language (List[str] of ISO 639-1 codes, e.g. ["en", "ja"]), time_basis (auto/published/crawled), time_range (day/week/month/year or d/w/m/y), start_time/end_time (ISO 8601), highlight (HighlightOptions), format (markdown/text), safesearch (off/strict), full_content (FullContentOptions), include_images (bool)
    • Built-in broad web search โ€” OctenBroadSearchTool(parameters=OctenBroadSearchToolParameters(...)). Fans out into multiple sub-queries. Parameters: max_queries (1-30, default 5) plus the same shared web-search options as octen_search
  • tool_choice (str | dict, optional): "none", "auto", "required", or a dict specifying a particular tool (e.g. {"type": "octen_search"} or {"type": "function", "function": {"name": ...}})
  • user (str, optional): Opaque end-user identifier
  • timeout (float, optional): Per-request timeout in seconds (default 60s for chat)

Returns:

  • ChatCompletion when stream=False
  • Stream (iterable of StreamEvent) when stream=True

ChatCompletion Properties:

  • id - Unique completion ID
  • model - Model used for generation
  • choices - List of Choice objects. Each message exposes content, reasoning, reasoning_details (List[ReasoningDetail]), annotations (List[Annotation] with url_citation), tool_calls, and refusal
  • text - Convenience accessor for the first choice's content
  • reasoning - Convenience accessor for the first choice's reasoning (falls back to legacy reasoning_content)
  • usage - Usage object: prompt_tokens, completion_tokens, total_tokens, num_search_queries, full_content_tokens, prompt_tokens_details (cached_tokens, cache_write_tokens), completion_tokens_details (reasoning_tokens)
  • search_results - List of ChatSearchResult (when the octen_search tool ran)
  • citations - Citation string referencing search results
  • warning - Optional warning message

StreamEvent Properties:

  • type - Event type: "search_done", "content", "finish", "usage", "error"
  • choices - List of StreamChoice (with delta.content and delta.reasoning for incremental text)
  • search_results - Web search results (on search_done event)
  • usage - Token usage (on usage event)
  • citations - Citation string (on search_done event)
  • error - StreamError with message and code (on error event)

Embedding API

client.embedding.create()

Create text embedding vectors.

Parameters:

  • input (str | List[str], required): Input text or list of texts
  • model (str, optional): Model name, options:
    • "octen-embedding-0.6b" - Lightweight model
    • "octen-embedding-4b" - Balanced performance
    • "octen-embedding-8b" - Highest quality
  • dimension (int, optional): Vector dimension
  • input_type (str, optional): Input type, options: "query" or "document"
  • truncation (bool, optional): Whether to truncate long inputs, default True
  • timeout (float, optional): Request timeout in seconds

Returns: EmbeddingResponse object

Response Methods:

  • get_embeddings() - Get all vectors
  • get_first_embedding() - Get first vector (for single input)

Convenience Methods:

  • embed_query(text) - Embed a single query text
  • embed_documents(texts) - Batch embed document texts

VL Embedding API

client.vl_embedding.create()

Create multimodal embedding vectors from text, images, and videos.

Parameters:

  • model (str, required): Model name, options:

    • "octen-vl-embedding" - Max dimension 2048
    • "octen-vl-embedding-large" - Max dimension 4096
  • contents (List[dict | VLEmbeddingContent], required): Content elements. Each item must set exactly one of:

    • text (str) - Text input, โ‰ค 32k tokens
    • image (str) - Image URL or Base64, โ‰ค 5MB (JPEG/PNG/WEBP/BMP/TIFF/ICO/DIB/ICNS/SGI)
    • video (str) - Video URL, โ‰ค 50MB (MP4/AVI/MOV)

    Limits: โ‰ค 20 total elements, โ‰ค 5 images, โ‰ค 1 video per request.

  • enable_fusion (bool, optional): Fuse all contents into a single vector. Omit โ†’ server default False (independent vectors). The SDK uses exclude_none=True, so omitting and passing False produce different wire payloads

  • dimension (int, optional): Output dimension, โ‰ค model max. Omit โ†’ model max

  • fps (float, optional): Video frame sampling density, 0 โ‰ค x โ‰ค 1. Omit โ†’ server default 1

  • instruct (str, optional): Custom task description for the encoder. Omit โ†’ server default "Represent the user's input."

  • timeout (float, optional): Request timeout in seconds

Returns: VLEmbeddingResponse object

Response Properties:

  • code (int) - Response code; 0 means success. Non-zero responses raise OctenAPIError at parse time
  • msg (str) - Server-provided result message
  • request_id - Server-assigned request id (use this to grep server logs)
  • items - List of VLEmbeddingResult with .index, .embedding, .type ("vl" or "fusion"), .dimension. Raises OctenAPIError on schema drift
  • results - Raw result dicts (escape hatch when items raises)
  • model - Server-reported model used
  • usage - VLEmbeddingUsage with input_tokens / text_tokens / image_tokens (covers sampled video frames too) / image_count (excludes video frames) / duration (video seconds)
  • warning - Optional server warning (string only; non-string payloads return None)

Response Methods:

  • get_embeddings() - Get all vectors
  • get_first_embedding() - Get first vector (useful in fusion mode)

๐Ÿ”ง Async Support

import asyncio
from octen import AsyncOcten, ChatMessage

async def main():
    async with AsyncOcten(api_key="your-api-key") as client:
        # Concurrent chat requests
        task1 = client.chat.create(
            model="openai/gpt-5.4",
            messages=[ChatMessage(role="user", content="Explain deep learning")],
        )
        task2 = client.chat.create(
            model="anthropic/claude-sonnet-4.6",
            messages=[ChatMessage(role="user", content="Explain reinforcement learning")],
        )
        r1, r2 = await asyncio.gather(task1, task2)
        print(r1.text)
        print(r2.text)

        # Async streaming
        stream = await client.chat.create(
            model="openai/gpt-5.4",
            messages=[ChatMessage(role="user", content="Hello!")],
            stream=True
        )
        async for event in stream:
            if event.type == "content" and event.choices:
                print(event.choices[0].delta.content or "", end="", flush=True)

        # Search, extract, and embeddings also work async
        results = await client.search.search(query="AI")
        extracted = await client.extract.extract(urls=["https://example.com"])
        embedding = await client.embedding.create(input=["Hello"], model="octen-embedding-4b")

asyncio.run(main())

โš ๏ธ Error Handling

from octen import (
    Octen,
    ChatMessage,
    OctenAPIError,
    OctenTimeoutError,
    OctenConnectionError,
    OctenRateLimitError,
    OctenAuthenticationError,
    OctenStreamError,
)

with Octen(api_key="your-api-key") as client:
    try:
        response = client.chat.create(
            model="openai/gpt-5.4",
            messages=[ChatMessage(role="user", content="Hello")]
        )
    except OctenAuthenticationError:
        print("Invalid or missing API key")
    except OctenRateLimitError as e:
        print(f"Rate limited โ€” retry after {e.retry_after}s")
    except OctenStreamError as e:
        print(f"Stream error: {e.message} (code {e.code})")
    except OctenTimeoutError as e:
        print(f"Request timed out after {e.timeout}s")
    except OctenAPIError as e:
        print(f"API error {e.status_code}: {e.message}")

๐Ÿงช Development

Install Development Dependencies

# Install development version from source
pip install -e ".[dev]"

Run Tests

pytest tests/

Code Formatting

black octen/
ruff check octen/ --fix

Type Checking

mypy octen/

๐Ÿ“ License

MIT License - See LICENSE file for details

๐Ÿ”— Links

๐Ÿ“ง Support

For questions or help, please:

Wheel compatibility matrix

Platform Python 3
any

Files in release

Extras:
Dependencies:
httpx[http2] (>=0.25.0)
pydantic (>=2.0.0)