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
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
asyncioclient 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.itemsis parsed lazily and raisesOctenAPIErrorif any row fails to parse (signals server schema drift). The raw dicts remain accessible viaresponse.resultsas 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_optionsparameters were removed. Enable web search by passing anOctenSearchToolintools; tune it withOctenSearchToolParameters. For broad (multi-query) search, pass anOctenBroadSearchToolwithOctenBroadSearchToolParametersinstead.
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_fusionomitted orFalse) โ one vector per content element (type="vl"). Omitting the field inherits the server default (False).
Default-value semantics:
enable_fusion/fps/instructdefault toNonein the SDK and are stripped from the wire viaexclude_none=True. When omitted, the server applies its own defaults (shown in the table).enable_fusion=Falseand omitting the field produce different wire payloads but currently the same behavior โ passFalseexplicitly 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 entryimageโ URL or Base64 string, โค 5MB, formats: JPEG/PNG/WEBP/BMP/TIFF/ICO/DIB/ICNS/SGIvideoโ 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 characterstopic(str, optional): Search category, options:"general"(default),"news"count(int, optional): Number of results to return, range 1-100, default 5include_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 formatend_time(str, optional): End time in ISO 8601 formathighlight(HighlightOptions, optional): Highlight options (max_tokens100โ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_tokens100โ100000, default 2048)include_images(bool, optional): Include image URLs in results, defaultfalseinclude_videos(bool, optional): Include video URLs in results, defaultfalsetimeout(float, optional): Request timeout in seconds
Returns: SearchResponse object
Response Properties:
results- List of search resultsquery- The actual query usedusage- Token usage informationlatency- 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 charactersmax_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 insidesearch_options) timeout(float, optional): Request timeout in seconds
Returns: BroadSearchResponse object
Response Properties:
queries- List of generated sub-queriessearch_results- Per-sub-query result groups ({query, results, latency})query- The original queryusage- Token usage informationlatency- 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 charactersformat(str, optional): Content format โ"text"or"markdown"(server default"markdown")max_age_seconds(int, optional): Cache window in seconds (minimum300); older cached versions are re-fetched. Defaults to86_400(24h) when omitted. Values outside the allowed range are adjusted server-sidequery(str, optional): Query for per-result highlight extraction, max 500 characters. When set, each item'shighlightsfield is populatedtimeout(int, optional): Per-URL upstream fetch budget in seconds, range 1-60, default 30. A URL that exceeds this returnsstatus="failed"โ siblings continueinclude_images(bool, optional): Return image objects, andcover_imagewhen 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 upstreamtimeoutabove. Set>= timeout + network_overhead
Caller typos (e.g.
url=instead ofurls=,inculde_images=) are rejected at construction time by Pydantic โ they will not silently reach the server.
Returns: ExtractResponse object
Response Properties:
code(int) โ0on successmsg(str) โ Server messagerequest_id(str | None) โ Server-generated request id; echoed fromX-Request-Idif you supplied that headerresults(List[dict]) โ Raw per-URL result dictsitems(List[ExtractItem]) โ Parsed, typed per-URL resultsusage(dict | None) โ{"total_urls": int, "successful_urls": int}total_urls(int | None) โ Convenience accessorsuccessful_urls(int | None) โ Number of URLs withstatus="success"in this batchlatency(int | None) โ End-to-end server latency in mswarning(str | None) โ Non-fatal warning
ExtractItem Fields:
url(str) โ The requested URLstatus(Literal["success", "failed"]) โ Extraction outcometitle(str | None) โ Page titlefull_content(str | None) โ Extracted page contenthighlights(List[str] | None) โ Snippets whenquerywas settime_published/time_last_crawled(str | None) โ ISO 8601 timestampserror_message(str | None) โ Populated only whenstatus="failed"favicon(str | None) โ Page favicon URL; returned by default when availablecover_image(ExtractCoverImage | None) โ Page cover image{url}(wheninclude_images=Trueand the page has one)images/videos/audio(List[Any] | None) โ Passthrough media objects; schema is upstream-definedcategory(ExtractCategory | None) โprimary/secondaryclassification labels (when classifier enabled server-side)page_structure(ExtractPageStructure | None) โprimary/secondarystructure 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 aChatMessageobject or a plain dict{"role": ..., "content": ...}model(str, required): Model ID (e.g."openai/gpt-5.4"). See Supported Chat Models for the full liststream(bool, optional): IfTrue, return aStreamiterator ofStreamEventobjects. DefaultFalsemax_tokens(int, optional): Maximum number of output tokensmax_completion_tokens(int, optional): Alternative max-token parametertemperature(float, optional): Sampling temperature[0, 2]top_p(float, optional): Nucleus sampling probability(0, 1]top_k(int, optional): Top-k samplingmin_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"), orResponseFormat(type="json_schema", json_schema=...)stop(List[str], optional): Up to 4 stop sequencesseed(int, optional): Integer seed for deterministic samplingreasoning_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 (defaultTrueserver-side)previous_response_id(str, optional): Continue from a previous responselogprobs(bool, optional): Whether to return log probabilitiestop_logprobs(int, optional): Number of most-likely tokens[0, 20]. Requireslogprobs=Truelogit_bias(Dict[str, float], optional): Token ID to bias value mappingtools(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/yearord/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 asocten_search
- Function tool โ
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 identifiertimeout(float, optional): Per-request timeout in seconds (default 60s for chat)
Returns:
ChatCompletionwhenstream=FalseStream(iterable ofStreamEvent) whenstream=True
ChatCompletion Properties:
id- Unique completion IDmodel- Model used for generationchoices- List ofChoiceobjects. Eachmessageexposescontent,reasoning,reasoning_details(List[ReasoningDetail]),annotations(List[Annotation] withurl_citation),tool_calls, andrefusaltext- Convenience accessor for the first choice's contentreasoning- Convenience accessor for the first choice's reasoning (falls back to legacyreasoning_content)usage-Usageobject: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 ofChatSearchResult(when theocten_searchtool ran)citations- Citation string referencing search resultswarning- Optional warning message
StreamEvent Properties:
type- Event type:"search_done","content","finish","usage","error"choices- List ofStreamChoice(withdelta.contentanddelta.reasoningfor incremental text)search_results- Web search results (onsearch_doneevent)usage- Token usage (onusageevent)citations- Citation string (onsearch_doneevent)error-StreamErrorwithmessageandcode(onerrorevent)
Embedding API
client.embedding.create()
Create text embedding vectors.
Parameters:
input(str | List[str], required): Input text or list of textsmodel(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 dimensioninput_type(str, optional): Input type, options:"query"or"document"truncation(bool, optional): Whether to truncate long inputs, default Truetimeout(float, optional): Request timeout in seconds
Returns: EmbeddingResponse object
Response Methods:
get_embeddings()- Get all vectorsget_first_embedding()- Get first vector (for single input)
Convenience Methods:
embed_query(text)- Embed a single query textembed_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 tokensimage(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 defaultFalse(independent vectors). The SDK usesexclude_none=True, so omitting and passingFalseproduce 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 default1 -
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;0means success. Non-zero responses raiseOctenAPIErrorat parse timemsg(str) - Server-provided result messagerequest_id- Server-assigned request id (use this to grep server logs)items- List ofVLEmbeddingResultwith.index,.embedding,.type("vl"or"fusion"),.dimension. RaisesOctenAPIErroron schema driftresults- Raw result dicts (escape hatch whenitemsraises)model- Server-reported model usedusage-VLEmbeddingUsagewithinput_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 returnNone)
Response Methods:
get_embeddings()- Get all vectorsget_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:
- Check the Documentation
- Email us at support@octen.ai