diff --git a/README.md b/README.md index c92baba..bbdd4dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ark Runtime Python SDK -The official Python library for the Volcengine Ark runtime API. It provides convenient access to the Ark REST API from any Python 3.8+ application, with both synchronous and asynchronous clients. +The official Python library for accessing ModelArk on Volcengine and BytePlus. It provides synchronous and asynchronous clients, typed models, streaming, authentication, retries, and timeout configuration. ## Installation @@ -8,39 +8,64 @@ The official Python library for the Volcengine Ark runtime API. It provides conv pip install arkruntime ``` -## Usage +## Choose Volcengine or BytePlus + +Set `ARK_API_KEY`, then choose the client factory for the service you use. The factory configures the correct base URL and region; request construction and all subsequent SDK calls are the same. -Create a client by setting the `ARK_API_KEY` environment variable: +### Volcengine (China) ```python from arkruntime import Ark -client = Ark() -# or explicitly: Ark(api_key="your-api-key") +client = Ark.volc() +# or explicitly: Ark.volc(api_key="your-api-key") ``` +### BytePlus (BP) + +```python +from arkruntime import Ark + +client = Ark.byteplus() +# or explicitly: Ark.byteplus(api_key="your-api-key") +``` + +Use a model ID available in the corresponding Volcengine or BytePlus account. Model IDs can differ between the two services; the examples use `doubao-seed-2-1-pro-260628` for Volcengine and `seed-2-0-lite-260428` for BytePlus. Override either default with `ARK_MODEL`. + +The async client provides the same factories: `AsyncArk.volc()` and `AsyncArk.byteplus()`. + +## Quick start + ### Responses API ```python import os from arkruntime import Ark -client = Ark() +client = Ark.volc() response = client.responses.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), input="Explain how large language models work in three sentences.", ) -print(response.output_text) +for item in response.output or []: + if item.type == "message": + for content in item.content: + if content.type == "output_text": + print(content.text) ``` +For BytePlus, change only the client line to `client = Ark.byteplus()` and set `ARK_MODEL` to a BytePlus model ID. + +## Usage + ### Chat Completions ```python import os from arkruntime import Ark -client = Ark() +client = Ark.volc() completion = client.chat.completions.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), @@ -62,7 +87,7 @@ Both the Responses and Chat Completions APIs support streaming via `stream=True` import os from arkruntime import Ark -client = Ark() +client = Ark.volc() stream = client.responses.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), @@ -79,7 +104,7 @@ for event in stream: import os from arkruntime import Ark -client = Ark() +client = Ark.volc() stream = client.chat.completions.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), @@ -100,14 +125,18 @@ import asyncio import os from arkruntime import AsyncArk -client = AsyncArk() +client = AsyncArk.volc() async def main(): response = await client.responses.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), input="Explain quantum computing briefly.", ) - print(response.output_text) + for item in response.output or []: + if item.type == "message": + for content in item.content: + if content.type == "output_text": + print(content.text) asyncio.run(main()) ``` @@ -120,7 +149,7 @@ Pass images alongside text using multimodal content blocks. import os from arkruntime import Ark -client = Ark() +client = Ark.volc() completion = client.chat.completions.create( model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), @@ -144,7 +173,7 @@ import json import os from arkruntime import Ark -client = Ark() +client = Ark.volc() tools = [ { @@ -179,7 +208,7 @@ print(f"Arguments: {tool_call.function.arguments}") ```python from arkruntime import Ark -client = Ark() +client = Ark.volc() # Upload a file file = client.files.create(file=open("data.jsonl", "rb"), purpose="batch") @@ -201,7 +230,7 @@ The SDK raises typed exceptions for API errors. from arkruntime import Ark from arkruntime._exceptions import ArkAPIError, ArkRateLimitError, ArkAuthenticationError -client = Ark() +client = Ark.volc() try: client.chat.completions.create( @@ -243,7 +272,7 @@ The client automatically retries failed requests (default: 2 retries) with backo from arkruntime import Ark # Customize retries and timeout -client = Ark( +client = Ark.volc( max_retries=5, timeout=120.0, # seconds ) @@ -266,7 +295,7 @@ client.chat.completions.create( ```python from arkruntime import Ark -client = Ark(timeout=24 * 3600) +client = Ark.volc(timeout=24 * 3600) result = client.batch.chat.completions.create( model="doubao-seed-2-1-pro-260628", @@ -275,50 +304,18 @@ result = client.batch.chat.completions.create( print(result) ``` -## API coverage - -| API | Client path | -|---|---| -| Responses | `client.responses.create()` | -| Chat Completions | `client.chat.completions.create()` | -| Embeddings | `client.embeddings.create()` | -| Multimodal Embeddings | `client.multimodal_embeddings.create()` | -| Content Generation | `client.content_generation.tasks.create()` | -| Images | `client.images.generate()` | -| Files | `client.files.create()` / `.list()` / `.delete()` | -| Tokenization | `client.tokenization.create()` | -| Batch | `client.batch.chat.completions.create()` etc. | - ## Examples -See the [examples/](./examples) directory for runnable scripts: - -- `responses/` -- Responses API: multi-turn chat, function calling, structured output, video streaming -- `chat/` -- Chat Completions: basic, function calling, reasoning, structured output, vision -- `batch/` -- Batch inference: chat completions, embeddings, multimodal embeddings (sync + async) -- `files/` -- Files API: upload, wait for processing, list, delete -- `embeddings.py` -- Text embeddings -- `multimodal_embeddings.py` -- Multimodal embeddings with image input -- `content_generation_tasks.py` -- Video generation task lifecycle -- `image_generations.py` -- Image generation -- `tokenization.py` -- Tokenization API - -## Development +For detailed usage guidance and legacy migration, see +[`docs/README.md`](docs/README.md) and +[`docs/migration.md`](docs/migration.md). -This repo uses [uv](https://docs.astral.sh/uv/) for dependency management. - -```bash -uv sync # create venv + install runtime + dev deps -uv run pytest # run tests -uv run ruff check src/ # lint -uv run ruff format src/ # format -``` +See the [examples/](./examples) directory for runnable scripts: -A pre-commit hook runs the same linting as CI: +- `volc/` -- Volcengine China examples for Chat, Responses, images, video generation, embeddings, files, tokenization, batch APIs, and resource APIs +- `byteplus/` -- supported BytePlus counterparts using the BytePlus client and regional model IDs -```bash -uv run pre-commit install # one-time setup -``` +MCP examples are provided for both clouds and explicitly send `ark-beta-mcp: true`. Other built-in-tool examples are CN-only and show their required beta headers. ## Requirements diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..19d4e3d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,40 @@ +# Ark Runtime Python SDK documentation + +This directory contains detailed usage and migration guidance for the Ark +Runtime Python SDK. + +## Choose the right document + +- [Usage guide](usage.md): installation, regional clients, + request bodies, sync/async streaming, output parsing, and built-in tools. +- [Migration guide](migration.md): migrate from the legacy Volcengine or + BytePlus Python SDK. +- [`../examples/volc`](../examples/volc): runnable Volcengine examples. +- [`../examples/byteplus`](../examples/byteplus): runnable BytePlus examples. + +## Important usage rules + +1. Create clients with `Ark.volc()` / `AsyncArk.volc()` for CN or + `Ark.byteplus()` / `AsyncArk.byteplus()` for BytePlus. Do not supply a CN URL + to a BytePlus client or the reverse. +2. Keep credentials in `ARK_API_KEY`; never place a key in code, prompts, + generated patches, tests, logs, or notebooks. +3. Preserve the documented request body shape and type discriminators. A dict + union member needs the correct `type`, field names, and nesting. +4. Streaming APIs return events or chunks, not the final response object. + Handle only the event types needed and safely ignore other valid events. +5. A non-streaming Responses object has no `response.output_text` convenience + field. Traverse `response.output`, message content, and `output_text` items. +6. MCP works in CN and BytePlus. Other hosted built-in tools shown here are + CN-only and require their matching `ark-beta-*` header. + +## Minimal verification + +```bash +python -m compileall src examples +python -m pytest +``` + +Also run one non-streaming and one streaming call in the intended cloud. Test +each built-in tool independently so missing access or headers cannot be hidden +by a successful ordinary request. diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..f32b0bc --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,158 @@ +# Migrate from the legacy Python SDK + +This guide covers the runtime clients imported from: + +- `volcenginesdkarkruntime` +- `byteplussdkarkruntime` + +Both migrate to the `arkruntime` distribution and Python package. + +## 1. Migration order + +Migrate one API flow at a time: + +1. Choose the target cloud: Volcengine (CN) or BytePlus. +2. Install `arkruntime`, update imports, and select the regional sync or async + client factory. +3. Verify every request dictionary's discriminator, nesting, and field names. +4. Update non-streaming Responses output traversal and streaming event types. +5. Preserve response IDs, call IDs, and approval IDs across multi-turn flows. +6. Add the required beta header to every built-in-tool request and remove any + CN-only tool from a BytePlus target. +7. Compile and smoke-test that flow before migrating the next one. + +Do not apply a project-wide regular-expression replacement to request bodies or +stream handlers. Their correct mapping depends on runtime structure and intent. + +## 2. Dependency, import, and client mapping + +Install the new package: + +```bash +pip install arkruntime +``` + +| Legacy | New | +|---|---| +| `from volcenginesdkarkruntime import Ark` | `from arkruntime import Ark` | +| `from byteplussdkarkruntime import Ark` | `from arkruntime import Ark` | +| legacy `.types...` import root | `arkruntime.types...` | +| `Ark(...)` | CN: `Ark.volc(...)`; BP: `Ark.byteplus(...)` | +| `AsyncArk(...)` | CN: `AsyncArk.volc(...)`; BP: `AsyncArk.byteplus(...)` | + +Remove `volcengine-python-sdk` or `byteplus-python-sdk-v2` from application +dependencies only after verifying no other imports need that distribution. +`arkruntime` itself may install a Volcengine dependency for its authentication +implementation; that is not a reason to keep legacy runtime imports. + +If legacy code sets `base_url`, inspect it manually. Prefer the regional factory +default. Retain a custom URL only when the deployment explicitly requires one. + +## 3. Request-body mapping + +Most keyword-based calls remain recognizable, but migration must validate the +body as a discriminated union rather than assuming every old dictionary is +accepted. + +| Intent | New body | +|---|---| +| simple Responses prompt | `input="..."` | +| message input | `input=[{"role": "user", "content": ...}]` | +| text content part | `{"type": "input_text", "text": "..."}` | +| image content part | `{"type": "input_image", "image_url": "..."}` | +| function result | `{"type": "function_call_output", "call_id": id, "output": value}` | +| MCP approval | `{"type": "mcp_approval_response", "approval_request_id": id, "approve": True}` | + +Preserve `previous_response_id` when continuing a stored response. Preserve the +same tool declarations in the follow-up request when required by the flow. +Never collapse a content-part list into a string if it also contains media. + +If an application creates request dictionaries dynamically, add fixture tests +for the final kwargs passed to `responses.create` or `chat.completions.create`. + +## 4. Output and stream mapping + +Non-streaming Responses output must be traversed: + +```python +for item in response.output or []: + if item.type == "message": + for content in item.content: + if content.type == "output_text": + print(content.text) +``` + +There is no `response.output_text` convenience property in this SDK. + +Chat stream text remains on `chunk.choices[0].delta.content`. Responses streams +are typed unions. Import event types from `arkruntime.types.responses` and use +`isinstance`: + +```python +if isinstance(event, ResponseTextDeltaEvent): + consume(event.delta) +elif isinstance(event, ResponseCompletedEvent): + response_id = event.response.id +``` + +Common legacy type mappings are: + +| Legacy | New public type | +|---|---| +| `ResponseFunctionToolCall` | `ItemFunctionToolCall` | +| `McpApprovalRequest` | `ItemFunctionMcpApprovalRequest` | +| deep per-file event imports | imports from `arkruntime.types.responses` | + +For a function call, capture `call_id` from a +`ResponseOutputItemDoneEvent` whose item is `ItemFunctionToolCall`. For MCP, +capture the approval request item and the completed response ID. Async code uses +`await` plus `async for`; do not mechanically change it to the sync iteration +pattern. + +## 5. Extra headers and regional behavior + +Headers are per-call keyword arguments: + +```python +client.responses.create( + ..., + extra_headers={"ark-beta-mcp": "true"}, +) +``` + +MCP (`ark-beta-mcp`) works in CN and BytePlus. Web search +(`ark-beta-web-search`), knowledge search (`ark-beta-knowledge-search`), Doubao +App (`ark-beta-doubao-app`), and image process (`ark-beta-image-process`) are +CN-only. Remove these tools from a BytePlus migration rather than silently +dropping their headers or changing their request bodies. + +## 6. Regional model IDs + +Model names and endpoint IDs are cloud-specific. Prefer application +configuration, and update any legacy hard-coded default when changing clouds: + +| API | Volcengine (CN) example | BytePlus example | +|---|---|---| +| Responses / Chat | `doubao-seed-2-1-pro-260628` | `seed-2-0-lite-260428` | +| Multimodal / sparse embeddings | `doubao-embedding-vision-251215` | `skylark-embedding-vision-251215` | +| Image generation | `doubao-seedream-5-0-pro-260628` | `dola-seedream-5-0-pro-260628` | +| Video generation | `doubao-seedance-2-0-fast-260128` | `dreamina-seedance-2-0-fast-260128` | + +Use a model or endpoint ID provisioned for the target account if it differs +from these example defaults. + +## 7. Validate the migration + +1. Search for legacy runtime imports and direct `Ark(...)` / `AsyncArk(...)` + construction; none should remain in migrated Ark Runtime code. +2. Run the project's formatter, type checker, and tests. +3. Run `python -m compileall` over migrated source. +4. Run one non-streaming request and verify output traversal. +5. Run sync or async streaming through a completed event and verify error paths. +6. Smoke-test each built-in tool with its beta header. +7. Validate CN and BytePlus separately when supporting both. Do not reuse a + key, model, endpoint ID, or client between regions. + +Compilation alone does not prove a migration is correct: dictionary bodies, +event dispatch, output traversal, regional model names, and headers are runtime +contracts. diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..5a9c8db --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,206 @@ +# Usage guide + +Use this guide when creating or modifying a Python application with Ark +Runtime. Copy complete shapes from the matching regional example, then make the +smallest application-specific change. + +## 1. Install and configure + +```bash +pip install arkruntime +export ARK_API_KEY="..." +``` + +Keep the key outside source control. Read the model or endpoint ID from +application configuration, such as `ARK_MODEL`, rather than hard-coding it in a +reusable package. + +## 2. Select the cloud and sync mode + +Only client creation changes; API calls and request setup remain the same. + +```python +from arkruntime import Ark, AsyncArk + +# Volcengine (CN) +client = Ark.volc() +async_client = AsyncArk.volc() + +# BytePlus +client = Ark.byteplus() +async_client = AsyncArk.byteplus() +``` + +The factories read `ARK_API_KEY` by default and also accept `api_key=...` when +the application already manages secrets securely. + +Current regional example defaults are: + +| API | Volcengine (CN) | BytePlus | +|---|---|---| +| Responses / Chat | `doubao-seed-2-1-pro-260628` | `seed-2-0-lite-260428` | +| Multimodal / sparse embeddings | `doubao-embedding-vision-251215` | `skylark-embedding-vision-251215` | +| Image generation | `doubao-seedream-5-0-pro-260628` | `dola-seedream-5-0-pro-260628` | +| Video generation | `doubao-seedance-2-0-fast-260128` | `dreamina-seedance-2-0-fast-260128` | + +Use the model or endpoint ID provisioned for the user's account if it differs. + +BytePlus currently has no model for the text-only `/embeddings` endpoint, so +its examples use `/embeddings/multimodal` instead. + +## 3. Build request bodies + +Simple Responses request: + +```python +response = client.responses.create( + model=model, + input="Explain LLMs in one sentence.", +) +``` + +Structured input uses discriminated dictionaries: + +```python +response = client.responses.create( + model=model, + input=[ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "Describe this image"}, + {"type": "input_image", "image_url": image_url}, + ], + } + ], +) +``` + +When editing a body, preserve: + +- discriminator values such as `input_text`, `function_call_output`, and + `mcp_approval_response`; +- exact snake_case field names; +- list versus scalar forms of `input` and `content`; +- `previous_response_id`, call IDs, and approval IDs across turns; +- user-provided extra fields, timeouts, and headers. + +Typed request dictionaries from `arkruntime.types` can be useful to type +check a reusable library. Ordinary application code can use the documented +dict forms directly. + +## 4. Read non-streaming output + +The Responses object does not provide `response.output_text`. Traverse its +output items: + +```python +for item in response.output or []: + if item.type != "message": + continue + for content in item.content: + if content.type == "output_text": + print(content.text) +``` + +Do not assume the first output item is a message: reasoning and tool-call items +may precede it. + +## 5. Handle streams + +Chat streams yield chunks: + +```python +stream = client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Hello"}], + stream=True, +) +for chunk in stream: + if not chunk.choices: + continue + text = chunk.choices[0].delta.content + if text: + print(text, end="") +``` + +Responses streams yield typed events: + +```python +from arkruntime.types.responses import ( + ResponseCompletedEvent, + ResponseTextDeltaEvent, +) + +stream = client.responses.create(model=model, input="Hello", stream=True) +for event in stream: + if isinstance(event, ResponseTextDeltaEvent): + print(event.delta, end="") + elif isinstance(event, ResponseCompletedEvent): + response_id = event.response.id +``` + +For async clients, await creation where the example does and use +`async for event in stream`. Function calling and MCP require capturing typed +output-item events as well as the completed response ID. Do not treat every +event as a text event or fail on a valid event the application does not use. + +## 6. Built-in tools and headers + +Pass `extra_headers` on every call that contains a beta tool: + +```python +response = client.responses.create( + model=model, + input="Summarize the repository", + tools=[{"type": "mcp", "server_label": "docs", "server_url": url}], + extra_headers={"ark-beta-mcp": "true"}, +) +``` + +| Tool | Cloud | Required header | +|---|---|---| +| MCP | CN and BytePlus | `ark-beta-mcp: true` | +| Web search | CN only | `ark-beta-web-search: true` | +| Knowledge search | CN only | `ark-beta-knowledge-search: true` | +| Doubao App | CN only | `ark-beta-doubao-app: true` | +| Image process | CN only | `ark-beta-image-process: true` | + +Do not add a CN-only tool to BytePlus code. Application-defined function calls +are not hosted built-in tools. + +## 7. Navigate the examples + +Use [`examples/volc`](../examples/volc) or +[`examples/byteplus`](../examples/byteplus). Both trees include Chat and +Responses streaming/non-streaming usage and their region's other supported +APIs. The CN tree includes the CN-only built-in-tool examples. + +Use this routing table instead of reshaping a Chat example for another API: + +| Intent | Example path below the region directory | +|---|---| +| Chat stream/non-stream, reasoning, vision, tools | `chat/` | +| Responses stream/non-stream and hosted tools | `responses/` | +| Text embeddings (Volcengine only) | `cn/embeddings.py` | +| Sparse or multimodal embeddings | `sparse_embeddings.py`, `multimodal_embeddings.py` | +| Image generation | `image_generations.py` | +| Video generation | `content_generation_tasks.py` | +| Files | `files/` | +| Batch APIs | `batch/` | +| Agents, sessions, memory stores, environments | matching top-level script | +| Token counting | `tokenization.py` | + +Sync and async examples are named explicitly. Do not convert between them +unless the application's execution model requires it. + +## 8. Completion checklist + +- Imports come from `arkruntime`, not a legacy package. +- Sync/async and CN/BytePlus client choices are explicit. +- No credentials are present in source or output. +- Dict discriminators, nesting, IDs, custom fields, and headers are preserved. +- Non-streaming Responses text is read from output items. +- Streaming code handles the correct chunk/event types. +- Built-in tools include their headers and respect regional support. +- Compile and test commands pass. diff --git a/examples/README.md b/examples/README.md index b9f5fb5..22acde5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,28 +1,20 @@ # Examples -Runnable examples for the `arkruntime` Python SDK. Each file expects `ARK_API_KEY` in the environment: +Runnable examples for the `arkruntime` Python SDK. Set `ARK_API_KEY` and, for most examples, `ARK_MODEL` to a model ID available in your account. ```bash export ARK_API_KEY=... -python examples/async_responses_create.py +export ARK_MODEL=... +python examples/volc/responses/async_create.py ``` -| File | What it shows | -|---|---| -| `async_responses_create.py` | Async client + POST /v1/responses with streaming | -| `async_responses_doubao_app.py` | Responses with Doubao app tools | -| `async_responses_video.py` | Video input in responses | -| `multimodal_embeddings.py` | POST /embeddings/multimodal | -| `content_generation_tasks.py` | full lifecycle on POST /contents/generations/tasks (create / poll / list / delete) | -| `image_generations.py` | POST /images/generations — Seedream T2I, Seededit edit-from-image, sequential image generation | -| `agents.py` | Managed-Agents: Agent lifecycle — Create/Get/List/Update/ListVersions/Delete | -| `environments.py` | Managed-Agents: Environment lifecycle — Create/Get/List/Update/Delete (cloud + unrestricted networking) | -| `sessions_loop.py` | Managed-Agents: end-to-end agent loop — Agent + Env + Session, send user.message, stream events until idle | -| `memory_stores.py` | Managed-Agents: MemoryStore + nested Memory CRUD | -| `self_hosted_worker.py` | Managed-Agents: self-hosted worker poll / handle loop | +All service-calling examples are grouped by cloud: -`self_hosted_worker.py` uses the client's production default `https://ark.cn-beijing.volces.com/api/v3`. +- [`volc/`](./volc) uses `Ark.volc()` / `AsyncArk.volc()` and Volcengine China model IDs. +- [`byteplus/`](./byteplus) uses `Ark.byteplus()` / `AsyncArk.byteplus()` and BytePlus model IDs. -The Managed-Agents examples additionally accept `ARK_MODEL_ID` for the model id (falls back to a `${YOUR_MODEL_ID}` placeholder that will 400 at runtime). +[`self_hosted_worker.py`](./self_hosted_worker.py) demonstrates the Managed-Agents self-hosted worker poll/handle loop and uses the client's production default `https://ark.cn-beijing.volces.com/api/v3`. -Examples only cover currently-implemented APIs. See the `API Coverage` table in the top-level README for the roadmap. +The paired multimodal and sparse embedding examples default to `doubao-embedding-vision-251215` / `skylark-embedding-vision-251215`. The paired image examples default to `doubao-seedream-5-0-pro-260628` / `dola-seedream-5-0-pro-260628`. The paired video-generation examples default to `doubao-seedance-2-0-fast-260128` / `dreamina-seedance-2-0-fast-260128`. + +MCP is available in both clouds and its examples explicitly send `ark-beta-mcp: true`. Other built-in tools are CN-only: Web Search sends `ark-beta-web-search: true`, and Doubao App sends `ark-beta-doubao-app: true`. diff --git a/examples/byteplus/agents.py b/examples/byteplus/agents.py new file mode 100644 index 0000000..3f7751b --- /dev/null +++ b/examples/byteplus/agents.py @@ -0,0 +1,64 @@ +"""Agent lifecycle example — Create/Get/List/Update/ListVersions/Delete. + +Runs against the outward /api/v3/agents endpoint. + + export ARK_API_KEY=... + export ARK_MODEL_ID=seed-2-0-lite-260428 # or whatever you have access to + python examples/agents.py +""" + +from __future__ import annotations + +import os +import time + +from arkruntime import Ark +from arkruntime.types.agent.model_config import ModelConfig + + +def main() -> None: + api_key = os.environ.get("ARK_API_KEY") + if not api_key: + raise SystemExit("set ARK_API_KEY") + model_id = os.environ.get("ARK_MODEL_ID", "${YOUR_MODEL_ID}") + + client = Ark.byteplus(api_key=api_key) + + # 1. Create + name = f"example-agent-{time.time_ns()}" + created = client.agents.create( + name=name, + model=ModelConfig(id=model_id), + description="created by ark-runtime-python example", + ) + print(f"created: id={created.id} version={created.version} name={created.name}") + + try: + # 2. Get + got = client.agents.retrieve(created.id) + print(f"get: id={got.id} name={got.name}") + + # 3. List — takes limit / page / created_at_gte / created_at_lte. + listed = client.agents.list(limit=5) + print(f"list: {len(listed.data)} items, next_page={listed.next_page!r}") + + # 4. Update — bumps version. Requires the previous version for optimistic + # concurrency control. + updated = client.agents.update( + created.id, + version=created.version, + description="updated by ark-runtime-python example", + ) + print(f"updated: id={updated.id} version={updated.version} (was {created.version})") + + # 5. List versions — should see at least v1 (create) + v2 (update). + versions = client.agents.list_versions(created.id, limit=10) + print(f"versions: {len(versions.data)} items") + finally: + # 6. Delete + deleted = client.agents.delete(created.id) + print(f"deleted: id={deleted.id}") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/batch/async_chat_completions.py b/examples/byteplus/batch/async_chat_completions.py new file mode 100644 index 0000000..371e8f1 --- /dev/null +++ b/examples/byteplus/batch/async_chat_completions.py @@ -0,0 +1,62 @@ +"""Async batch chat completions: parallel synchronous online inference.""" + +from __future__ import annotations + +import asyncio +import sys +from datetime import datetime + +from arkruntime import AsyncArk + + +async def worker(worker_id: int, client: AsyncArk, requests: "asyncio.Queue[dict]") -> None: + print(f"Worker {worker_id} is starting.") + + while True: + request = await requests.get() + try: + completion = await client.batch.chat.completions.create(**request) + print(completion) + except Exception as e: + print(e, file=sys.stderr) + finally: + requests.task_done() + + +async def main() -> None: + start = datetime.now() + max_concurrent_tasks, task_num = 10, 100 + + requests: "asyncio.Queue[dict]" = asyncio.Queue() + client = AsyncArk.byteplus(timeout=24 * 3600) + + for _ in range(task_num): + await requests.put( + { + "model": "${YOUR_ENDPOINT_ID}", + "messages": [ + { + "role": "system", + "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手", + }, + {"role": "user", "content": "常见的十字花科植物有哪些?"}, + ], + } + ) + + tasks = [asyncio.create_task(worker(i, client, requests)) for i in range(max_concurrent_tasks)] + + await requests.join() + + for task in tasks: + task.cancel() + + await asyncio.gather(*tasks, return_exceptions=True) + await client.close() + + end = datetime.now() + print(f"Total time: {end - start}, Total task: {task_num}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/byteplus/batch/async_multimodal_embeddings.py b/examples/byteplus/batch/async_multimodal_embeddings.py new file mode 100644 index 0000000..49e026d --- /dev/null +++ b/examples/byteplus/batch/async_multimodal_embeddings.py @@ -0,0 +1,62 @@ +"""Async batch multimodal embeddings.""" + +from __future__ import annotations + +import asyncio +import sys +from datetime import datetime + +from arkruntime import AsyncArk + + +async def worker(worker_id: int, client: AsyncArk, requests: "asyncio.Queue[dict]") -> None: + print(f"Worker {worker_id} is starting.") + + while True: + request = await requests.get() + try: + resp = await client.batch.multimodal_embeddings.create(**request) + print(resp) + except Exception as e: + print(e, file=sys.stderr) + finally: + requests.task_done() + + +async def main() -> None: + start = datetime.now() + max_concurrent_tasks, task_num = 10, 100 + + requests: "asyncio.Queue[dict]" = asyncio.Queue() + client = AsyncArk.byteplus(timeout=24 * 3600) + + for _ in range(task_num): + await requests.put( + { + "model": "${YOUR_ENDPOINT_ID}", + "input": [ + {"type": "text", "text": "What is the weather like today?"}, + { + "type": "image_url", + "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"}, + }, + ], + } + ) + + tasks = [asyncio.create_task(worker(i, client, requests)) for i in range(max_concurrent_tasks)] + + await requests.join() + + for task in tasks: + task.cancel() + + await asyncio.gather(*tasks, return_exceptions=True) + await client.close() + + end = datetime.now() + print(f"Total time: {end - start}, Total task: {task_num}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/byteplus/batch/chat_completions.py b/examples/byteplus/batch/chat_completions.py new file mode 100644 index 0000000..15befcc --- /dev/null +++ b/examples/byteplus/batch/chat_completions.py @@ -0,0 +1,77 @@ +"""Batch chat completions: parallel synchronous online inference. + +Uses the per-model breaker + retry loop on /batch/chat/completions. +Streaming is not supported on this path. +""" + +from __future__ import annotations + +import queue +import sys +from datetime import datetime +from multiprocessing.pool import ThreadPool + +from arkruntime import Ark + + +def worker(worker_id: int, client: Ark, requests: "queue.Queue[dict | None]") -> None: + print(f"Worker {worker_id} is starting.") + + while True: + request = requests.get() + + # check for signal of no more request + if not request: + # put back on the queue for other workers + requests.put(request) + return + + try: + completion = client.batch.chat.completions.create(**request) + print(completion) + except Exception as e: + print(e, file=sys.stderr) + finally: + requests.task_done() + + +def main() -> None: + start = datetime.now() + max_concurrent_tasks, task_num = 10, 100 + + requests: "queue.Queue[dict | None]" = queue.Queue() + client = Ark.byteplus(timeout=24 * 3600) + + # mock `task_num` tasks + for _ in range(task_num): + requests.put( + { + "model": "${YOUR_ENDPOINT_ID}", + "messages": [ + { + "role": "system", + "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手", + }, + {"role": "user", "content": "常见的十字花科植物有哪些?"}, + ], + } + ) + + # signal no more requests + requests.put(None) + + with ThreadPool(max_concurrent_tasks) as pool: + for i in range(max_concurrent_tasks): + pool.apply_async(worker, args=(i, client, requests)) + + pool.close() + pool.join() + + client.close() + + end = datetime.now() + print(f"Total time: {end - start}, Total task: {task_num}") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/batch/multimodal_embeddings.py b/examples/byteplus/batch/multimodal_embeddings.py new file mode 100644 index 0000000..997961f --- /dev/null +++ b/examples/byteplus/batch/multimodal_embeddings.py @@ -0,0 +1,68 @@ +"""Batch multimodal embeddings: parallel synchronous calls to /batch/embeddings/multimodal.""" + +from __future__ import annotations + +import queue +import sys +from datetime import datetime +from multiprocessing.pool import ThreadPool + +from arkruntime import Ark + + +def worker(worker_id: int, client: Ark, requests: "queue.Queue[dict | None]") -> None: + print(f"Worker {worker_id} is starting.") + + while True: + request = requests.get() + if not request: + requests.put(request) + return + + try: + resp = client.batch.multimodal_embeddings.create(**request) + print(resp) + except Exception as e: + print(e, file=sys.stderr) + finally: + requests.task_done() + + +def main() -> None: + start = datetime.now() + max_concurrent_tasks, task_num = 10, 100 + + requests: "queue.Queue[dict | None]" = queue.Queue() + client = Ark.byteplus(timeout=24 * 3600) + + for _ in range(task_num): + requests.put( + { + "model": "${YOUR_ENDPOINT_ID}", + "input": [ + {"type": "text", "text": "What is the weather like today?"}, + { + "type": "image_url", + "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"}, + }, + ], + } + ) + + requests.put(None) + + with ThreadPool(max_concurrent_tasks) as pool: + for i in range(max_concurrent_tasks): + pool.apply_async(worker, args=(i, client, requests)) + + pool.close() + pool.join() + + client.close() + + end = datetime.now() + print(f"Total time: {end - start}, Total task: {task_num}") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/chat/completions.py b/examples/byteplus/chat/completions.py new file mode 100644 index 0000000..1432582 --- /dev/null +++ b/examples/byteplus/chat/completions.py @@ -0,0 +1,44 @@ +import os + +from arkruntime import Ark + +# Authentication +# 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" +# or specify api key by Ark.byteplus(api_key="${YOUR_API_KEY}"). +# Note: If you use an API key, this API key will not be refreshed. +# To prevent the API from expiring and failing after some time, choose an API key with no expiration date. + +# 2.If you authorize your endpoint with BytePlus Identity and Access Management (IAM), +# set your access key and secret key in "BYTEPLUS_ACCESSKEY" and "BYTEPLUS_SECRETKEY" +# or specify ak&sk by Ark.byteplus(ak="${YOUR_AK}", sk="${YOUR_SK}"). +client = Ark.byteplus() +MODEL = os.environ.get("ARK_MODEL", "seed-2-0-lite-260428") + + +if __name__ == "__main__": + # Non-streaming: + print("----- standard request -----") + completion = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "system", "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手"}, + {"role": "user", "content": "常见的十字花科植物有哪些?"}, + ], + ) + print(completion.choices[0].message.content) + + # Streaming: + print("----- streaming request -----") + stream = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "system", "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手"}, + {"role": "user", "content": "常见的十字花科植物有哪些?"}, + ], + stream=True, + ) + for chunk in stream: + if not chunk.choices: + continue + print(chunk.choices[0].delta.content, end="") + print() diff --git a/examples/chat/function_call.py b/examples/byteplus/chat/function_call.py similarity index 96% rename from examples/chat/function_call.py rename to examples/byteplus/chat/function_call.py index 423c1c6..d9c3335 100644 --- a/examples/chat/function_call.py +++ b/examples/byteplus/chat/function_call.py @@ -12,8 +12,8 @@ # Authentication # 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") +client = Ark.byteplus() +MODEL = os.environ.get("ARK_MODEL", "seed-2-0-lite-260428") WEATHER_TOOL = { "type": "function", diff --git a/examples/chat/reasoning_completions.py b/examples/byteplus/chat/reasoning_completions.py similarity index 93% rename from examples/chat/reasoning_completions.py rename to examples/byteplus/chat/reasoning_completions.py index 46e184d..ab919c9 100644 --- a/examples/chat/reasoning_completions.py +++ b/examples/byteplus/chat/reasoning_completions.py @@ -4,8 +4,8 @@ # Authentication # 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") +client = Ark.byteplus() +MODEL = os.environ.get("ARK_MODEL", "seed-2-0-lite-260428") if __name__ == "__main__": # Streaming: diff --git a/examples/byteplus/chat/structured_output.py b/examples/byteplus/chat/structured_output.py new file mode 100644 index 0000000..f625047 --- /dev/null +++ b/examples/byteplus/chat/structured_output.py @@ -0,0 +1,46 @@ +"""Structured output example: client.beta.chat.completions.parse with a Pydantic model. + +The typed ``parse()`` shortcut converts the model class to a JSON Schema, +sends it as ``response_format`` to /chat/completions, then deserialises the +response back into the model via ``choice.message.parsed``. + +For tool calls, see ``pydantic_function_tool`` for the same trick over +``tool_call.function.parsed_arguments``. +""" + +import os +from typing import List + +from pydantic import BaseModel + +from arkruntime import Ark + +# Authentication +# 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" +client = Ark.byteplus() +# The typed parse() helper uses JSON Schema, which requires a model that +# supports strict structured output on Chat Completions. +MODEL = os.environ.get("ARK_MODEL", "seed-2-0-pro-260328") + + +class MeetingInfo(BaseModel): + time: str + participants: List[str] + + +if __name__ == "__main__": + print("----- standard request -----") + completion = client.beta.chat.completions.parse( + model=MODEL, + thinking={"type": "disabled"}, + messages=[ + {"role": "system", "content": "提取会议信息"}, + {"role": "user", "content": "周三下午3点产品组会议,参加人员:张三、李四"}, + ], + response_format=MeetingInfo, + ) + + meeting = completion.choices[0].message.parsed + assert meeting is not None # populated by parse() when response_format is a model + print(f"会议时间:{meeting.time}") + print(f"参会人员:{meeting.participants}") diff --git a/examples/byteplus/chat/vision_completions.py b/examples/byteplus/chat/vision_completions.py new file mode 100644 index 0000000..681a0df --- /dev/null +++ b/examples/byteplus/chat/vision_completions.py @@ -0,0 +1,28 @@ +import os + +from arkruntime import Ark + +# Authentication +# 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" +# or specify api key by Ark.byteplus(api_key="${YOUR_API_KEY}"). +client = Ark.byteplus() +MODEL = os.environ.get("ARK_MODEL", "seed-2-0-lite-260428") + +# Image input: +response = client.chat.completions.create( + model=MODEL, + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "这是哪里?"}, + { + "type": "image_url", + "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"}, + }, + ], + } + ], +) + +print(response.choices[0]) diff --git a/examples/byteplus/content_generation_tasks.py b/examples/byteplus/content_generation_tasks.py new file mode 100644 index 0000000..ee973f7 --- /dev/null +++ b/examples/byteplus/content_generation_tasks.py @@ -0,0 +1,111 @@ +import os + +from arkruntime import Ark + +# Authentication +# 1. If you authorize your endpoint using an API key, you can set your api key +# to environment variable "ARK_API_KEY" or pass it via Ark.byteplus(api_key="..."). +# Note: API keys do not refresh — pick one with no expiration. +client = Ark.byteplus() + +# Override these env vars to point at your own model + reference image. +MODEL = os.environ.get("SEEDANCE_MODEL", "dreamina-seedance-2-0-fast-260128") +IMAGE_URL = os.environ.get( + "SEEDANCE_IMAGE_URL", + "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg", +) + + +if __name__ == "__main__": + print("----- create request (i2v) -----") + # Note: `service_tier` + `execution_expires_after` are NOT accepted by + # the default seedance-2-0-fast model (server rejects them as + # InvalidParameter). If your account has access to a model that supports + # service tiers (e.g. one of the legacy seedance 1.0 endpoints), set + # `SEEDANCE_MODEL` and uncomment the corresponding kwargs below. + create_result = client.content_generation.tasks.create( + model=MODEL, + content=[ + { + "type": "text", + "text": "龙与地下城女骑士背景是起伏的平原,目光从镜头转向平原", + }, + { + "type": "image_url", + "image_url": {"url": IMAGE_URL}, + # "role": "first_frame", + }, + ], + # callback_url="${YOUR_CALLBACK_URL}", + ) + print(create_result) + + print("----- get request -----") + get_result = client.content_generation.tasks.get(create_result.id) + print(get_result) + print("ServiceTier:", getattr(get_result, "service_tier", None)) + print("ExecutionExpiresAfter:", getattr(get_result, "execution_expires_after", None)) + + print("----- list request -----") + list_result = client.content_generation.tasks.list( + page_num=1, + page_size=10, + status="queued", # one of: queued, running, succeeded, failed, cancelled + # model=MODEL, + # task_ids=["test-id-1", "test-id-2"], + ) + print(list_result) + if list_result.items: + print("List Item ServiceTier:", getattr(list_result.items[0], "service_tier", None)) + print("List Item ExecutionExpiresAfter:", getattr(list_result.items[0], "execution_expires_after", None)) + + print("----- delete request -----") + try: + client.content_generation.tasks.delete(create_result.id) + print(create_result.id) + except Exception as e: + print(f"failed to delete task: {e}") + + # ---- text-only (t2v) flow: create + GET + LIST + DELETE ---- + # `service_tier` + `execution_expires_after` are only valid on models + # that expose service-tier billing; the default fast model rejects + # them. Uncomment if you've switched `SEEDANCE_MODEL` to one that does. + print("----- create request (t2v) -----") + create_result_flex = client.content_generation.tasks.create( + model=MODEL, + content=[ + { + "type": "text", + "text": "纯文本生成视频测试", + } + ], + # service_tier="flex", + # execution_expires_after=3600, + ) + print(create_result_flex) + + print("----- get request (flex) -----") + get_result_flex = client.content_generation.tasks.get(create_result_flex.id) + print(get_result_flex) + print("Flex ServiceTier:", getattr(get_result_flex, "service_tier", None)) + print("Flex ExecutionExpiresAfter:", getattr(get_result_flex, "execution_expires_after", None)) + + print("----- list request (flex) -----") + list_result_flex = client.content_generation.tasks.list( + page_num=1, + page_size=10, + service_tier="flex", + ) + print(list_result_flex) + if list_result_flex.items: + print("Flex List Item ServiceTier:", getattr(list_result_flex.items[0], "service_tier", None)) + print( + "Flex List Item ExecutionExpiresAfter:", getattr(list_result_flex.items[0], "execution_expires_after", None) + ) + + print("----- delete request (flex) -----") + try: + client.content_generation.tasks.delete(create_result_flex.id) + print(create_result_flex.id) + except Exception as e: + print(f"failed to delete flex task: {e}") diff --git a/examples/environments.py b/examples/byteplus/environments.py similarity index 94% rename from examples/environments.py rename to examples/byteplus/environments.py index a570423..42e7bbe 100644 --- a/examples/environments.py +++ b/examples/byteplus/environments.py @@ -23,7 +23,7 @@ def main() -> None: if not api_key: raise SystemExit("set ARK_API_KEY") - client = Ark(api_key=api_key) + client = Ark.byteplus(api_key=api_key) # 1. Create — cloud + unrestricted network. name = f"example-env-{time.time_ns()}" @@ -54,7 +54,7 @@ def main() -> None: finally: # 5. Delete deleted = client.environments.delete(created.id) - print(f"deleted: id={deleted.id} deleted={deleted.deleted}") + print(f"deleted: id={deleted.id}") if __name__ == "__main__": diff --git a/examples/byteplus/files/upload_and_wait.py b/examples/byteplus/files/upload_and_wait.py new file mode 100644 index 0000000..ae8677f --- /dev/null +++ b/examples/byteplus/files/upload_and_wait.py @@ -0,0 +1,36 @@ +"""Upload a local file, wait for processing, then list and delete.""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +from arkruntime import Ark + + +def main() -> None: + api_key = os.environ.get("ARK_API_KEY") + if not api_key: + sys.exit("set ARK_API_KEY") + + client = Ark.byteplus(api_key=api_key) + target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__) + + print(f"uploading {target}") + file = client.files.create(file=target, purpose="user_data") + print(f" -> id={file.id} status={file.status}") + + file = client.files.wait_for_processing(file.id) + print(f" ready: status={file.status} bytes={file.bytes} mime={file.mime_type}") + + page = client.files.list(limit=5, order="desc") + for f in page.data: + print(f" {f.id}\t{f.created_at}\t{f.filename}") + + deleted = client.files.delete(file.id) + print(f"deleted: {deleted.id} ({deleted.deleted})") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/image_generations.py b/examples/byteplus/image_generations.py new file mode 100644 index 0000000..f105cfe --- /dev/null +++ b/examples/byteplus/image_generations.py @@ -0,0 +1,22 @@ +"""Seedream image generation examples for the new arkruntime SDK.""" + +import os + +from arkruntime import Ark + +client = Ark.byteplus(api_key=os.environ["ARK_API_KEY"]) +# Seedream model — used for the text-to-image example. +# Override via env vars to point at your own endpoint IDs. +SEEDREAM_MODEL = os.environ.get("SEEDREAM_ENDPOINT_ID", "dola-seedream-5-0-pro-260628") + + +if __name__ == "__main__": + print("----- [Seedream] generate images -----") + result = client.images.generate( + model=SEEDREAM_MODEL, + prompt="龙与地下城女骑士背景是起伏的平原,目光从镜头转向平原", + seed=1234567890, + watermark=True, + size="1024x1024", + ) + print(result) diff --git a/examples/byteplus/memory_stores.py b/examples/byteplus/memory_stores.py new file mode 100644 index 0000000..67bb1c2 --- /dev/null +++ b/examples/byteplus/memory_stores.py @@ -0,0 +1,72 @@ +"""MemoryStore + Memory lifecycle example. + +A MemoryStore is a namespace of Memory documents keyed by path. Covers the +full CRUD on both levels: + + POST /api/v3/memory_stores (client.memory_stores.create) + GET /api/v3/memory_stores/:store_id (client.memory_stores.retrieve) + GET /api/v3/memory_stores (client.memory_stores.list) + POST /api/v3/memory_stores/:store_id (client.memory_stores.update) + POST /api/v3/memory_stores/:store_id/memories (client.memory_stores.memories.create) + GET /api/v3/memory_stores/:store_id/memories/:id (client.memory_stores.memories.retrieve) + GET /api/v3/memory_stores/:store_id/memories (client.memory_stores.memories.list) + POST /api/v3/memory_stores/:store_id/memories/:id (client.memory_stores.memories.update) + DELETE /api/v3/memory_stores/:store_id/memories/:id (client.memory_stores.memories.delete) + DELETE /api/v3/memory_stores/:store_id (client.memory_stores.delete) + + export ARK_API_KEY=... + python examples/memory_stores.py +""" + +from __future__ import annotations + +import os +import time + +from arkruntime import Ark + + +def main() -> None: + api_key = os.environ.get("ARK_API_KEY") + if not api_key: + raise SystemExit("set ARK_API_KEY") + + client = Ark.byteplus(api_key=api_key) + + # 1. Create a memory store. + store = client.memory_stores.create(name=f"example-store-{time.time_ns()}") + print(f"store: id={store.id} name={store.name}") + + mem = None + try: + # 2. Create a memory doc inside it. + path = f"/example/note-{time.time_ns()}.md" + mem = client.memory_stores.memories.create( + store.id, + path=path, + content="hello from ark-runtime-python example", + ) + print(f"memory: id={mem.id} path={mem.path} sha256={mem.content_sha256}") + + # 3. Get + list. + got = client.memory_stores.memories.retrieve(store.id, mem.id) + print(f"get: id={got.id} path={got.path}") + + listed = client.memory_stores.memories.list(store.id, limit=10) + print(f"list: {len(listed.data)} items in store") + + # 4. Update — the SHA256 should change after new content. + client.memory_stores.memories.update(store.id, mem.id, content="updated content") + got2 = client.memory_stores.memories.retrieve(store.id, mem.id) + print(f"updated: id={got2.id} new_sha256={got2.content_sha256} (was {mem.content_sha256})") + + # 5. Delete the memory (store is cleaned up in `finally`). + client.memory_stores.memories.delete(store.id, mem.id) + print(f"memory: deleted id={mem.id}") + finally: + client.memory_stores.delete(store.id) + print(f"store: deleted id={store.id}") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/multimodal_embeddings.py b/examples/byteplus/multimodal_embeddings.py new file mode 100644 index 0000000..386e808 --- /dev/null +++ b/examples/byteplus/multimodal_embeddings.py @@ -0,0 +1,13 @@ +from arkruntime import Ark + +client = Ark.byteplus() + +print("----- multimodal embeddings request -----") +resp = client.multimodal_embeddings.create( + model="skylark-embedding-vision-251215", + input=[ + {"type": "text", "text": "What is the weather like today?"}, + {"type": "image_url", "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"}}, + ], +) +print(resp.data) diff --git a/examples/byteplus/responses/async_create.py b/examples/byteplus/responses/async_create.py new file mode 100644 index 0000000..e179af2 --- /dev/null +++ b/examples/byteplus/responses/async_create.py @@ -0,0 +1,214 @@ +import asyncio + +from arkruntime import AsyncArk +from arkruntime.types.responses import ( + ItemFunctionMcpApprovalRequest, + ItemFunctionToolCall, + ResponseCompletedEvent, + ResponseOutputItemDoneEvent, +) + +""" +示例代码:演示 Responses API 的常见用法 +------------------------------------------------- +1. 多轮对话中使用缓存 (caching) +2. 调用外部函数 (function calling) +3. 使用 MCP 工具 (MCP) +""" + +client = AsyncArk.byteplus() + + +async def main(): + # ========================================================== + # 示例 1:多轮对话,开启 caching + # ========================================================== + print("Example 1: Use caching for multi-round chat") + # ---------- 第 1 轮 ---------- + # 说明:开启 caching,store=True 表示把对话存储在服务端,以便后续引用 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + input=[ + { + "role": "system", + "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手", + }, + { + "role": "user", + "content": [ + # { + # "type": "input_image", + # # local image file path, will be automatically uploaded to file + # "image_url": f"file://{image_path}" + # }, + {"type": "input_text", "text": "图里有什么内容"} + ], + }, + ], + caching={ + "type": "enabled", + }, + store=True, + stream=True, + ) + response_id = "" + async for event in stream: + print(event) + if isinstance(event, ResponseCompletedEvent): + response_id = event.response.id + + # ---------- 第 2 轮 ---------- + # 说明:通过 previous_response_id 关联上一轮的上下文 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + previous_response_id=response_id, + input=[ + {"role": "user", "content": "上一轮对话里图里的内容是"}, + ], + caching={ + "type": "enabled", + }, + store=True, + stream=True, + ) + async for event in stream: + print(event) + + # ========================================================== + # 示例 2:函数调用 (Function Calling) + # ========================================================== + print("Example 2: Use responses API for function calling") + + # ---------- 第 1 轮 ---------- + # 用户询问北京天气,模型会触发工具调用 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + input=[ + {"role": "user", "content": "请问北京今天天气怎么样"}, + ], + tools=[ + { + "type": "function", + "name": "get_current_weather", + "description": "获取当前城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "城市名称,例如北京", + }, + "unit": { + "type": "string", + "description": "温度单位,例如摄氏度", + }, + }, + "required": ["location"], + }, + } + ], + caching={ + "type": "enabled", + }, + store=True, + stream=True, + ) + call_id = "" + response_id = "" + async for event in stream: + print(event) + if isinstance(event, ResponseCompletedEvent): + response_id = event.response.id + if isinstance(event, ResponseOutputItemDoneEvent) and isinstance(event.item, ItemFunctionToolCall): + call_id = event.item.call_id + + # ---------- 第 2 轮 ---------- + # 把函数返回结果传回模型,让它继续生成最终回答 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + previous_response_id=response_id, + input=[ + { + "type": "function_call_output", + "call_id": call_id, + "output": '{"temperature": "30"}', + }, + ], + caching={ + "type": "enabled", + }, + store=True, + stream=True, + ) + async for event in stream: + print(event) + + # ========================================================== + # 示例 3:使用 MCP + # ========================================================== + # ---------- 第 1 轮 ---------- + # 用户询问repo信息,模型会触发mcp工具调用 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + input=[ + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": "查看这个 repo的文档 expressjs/express ", + } + ], + } + ], + tools=[ + { + "type": "mcp", + "server_label": "deepwiki-test", + "server_url": "https://mcp.deepwiki.com/mcp", + "require_approval": "always", + } + ], + extra_headers={"ark-beta-mcp": "true"}, + store=True, + stream=True, + ) + approval_id = "" + response_id = "" + async for event in stream: + print(event) + if isinstance(event, ResponseCompletedEvent): + response_id = event.response.id + if isinstance(event, ResponseOutputItemDoneEvent) and isinstance(event.item, ItemFunctionMcpApprovalRequest): + approval_id = event.item.id + + # ---------- 第 2 轮 ---------- + # 用户同意mcp工具调用,模型会继续生成最终回答 + stream = await client.responses.create( + model="seed-2-0-lite-260428", + input=[ + { + "type": "mcp_approval_response", + "approval_request_id": approval_id, + "approve": True, + } + ], + previous_response_id=response_id, + tools=[ + { + "type": "mcp", + "server_label": "deepwiki-test", + "server_url": "https://mcp.deepwiki.com/mcp", + "require_approval": "always", + } + ], + extra_headers={"ark-beta-mcp": "true"}, + store=True, + stream=True, + ) + async for event in stream: + print(event) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/byteplus/responses/async_video.py b/examples/byteplus/responses/async_video.py new file mode 100644 index 0000000..0810ef1 --- /dev/null +++ b/examples/byteplus/responses/async_video.py @@ -0,0 +1,104 @@ +"""Upload a video, wait for preprocessing, then run a 2-turn Responses session +referencing the uploaded file_id. + +Demonstrates: +- `client.files.create(file=..., purpose=..., preprocess_configs={"video": {"fps": ...}})` +- `client.files.wait_for_processing(file_id)` for the active/failed terminal state +- `client.responses.create(input=[..., {"type": "input_video", "file_id": ...}])` + with caching + previous_response_id for multi-turn +""" + +import asyncio +import sys +from pathlib import Path + +from arkruntime import AsyncArk +from arkruntime.types.responses import ResponseCompletedEvent + +client = AsyncArk.byteplus() + + +DEFAULT_VIDEO_URL = "https://an-test-imgs.tos-cn-beijing.volces.com/videos/test_videos/04_duration_5s.mp4" + + +async def main() -> None: + if len(sys.argv) > 1: + video_path = Path(sys.argv[1]) + if not video_path.exists(): + sys.exit(f"video file '{video_path}' not found") + else: + # No local file supplied — fall back to a small public sample so the + # example is runnable out of the box. + import urllib.request + + video_path = Path("ark_vlm_video_input.mp4") + if not video_path.exists(): + print(f"Downloading sample video from {DEFAULT_VIDEO_URL}") + urllib.request.urlretrieve(DEFAULT_VIDEO_URL, video_path) + + print(f"Uploading {video_path}") + with video_path.open("rb") as video_file: + file = await client.files.create( + file=video_file, + purpose="user_data", + preprocess_configs={ + "video": { + "fps": 0.3, # sampling fps; default is 1.0 + } + }, + ) + print(f" uploaded id={file.id} status={file.status}") + + file = await client.files.wait_for_processing(file.id) + print(f" processed status={file.status}") + if file.status != "active": + sys.exit(f"file {file.id} did not become active: status={file.status}") + + file_id = file.id + + # ========================================================== + # Turn 1: multi-modal input + caching enabled + # ========================================================== + print("\nTurn 1: ask the model to analyze the video frame-by-frame") + stream = await client.responses.create( + model="seed-2-0-lite-260428", + input=[ + {"role": "system", "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手"}, + { + "role": "user", + "content": [ + {"type": "input_video", "file_id": file_id}, + {"type": "input_text", "text": "请逐帧分析视频内容"}, + ], + }, + ], + caching={"type": "enabled"}, + store=True, + stream=True, + ) + response_id = "" + async for event in stream: + print(event) + if isinstance(event, ResponseCompletedEvent): + response_id = event.response.id + + # ========================================================== + # Turn 2: continue via previous_response_id + # ========================================================== + print("\nTurn 2: follow-up referencing prior turn's response") + stream = await client.responses.create( + model="seed-2-0-lite-260428", + previous_response_id=response_id, + input=[ + {"role": "user", "content": "上一轮对话里视频里的内容是"}, + ], + caching={"type": "enabled"}, + store=True, + stream=True, + ) + async for event in stream: + print(event) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/byteplus/responses/create.py b/examples/byteplus/responses/create.py new file mode 100644 index 0000000..960f8e8 --- /dev/null +++ b/examples/byteplus/responses/create.py @@ -0,0 +1,15 @@ +import os + +from arkruntime import Ark + +client = Ark.byteplus() + +response = client.responses.create( + model=os.environ.get("ARK_MODEL", "seed-2-0-lite-260428"), + input="Explain large language models in one sentence.", +) +for item in response.output or []: + if item.type == "message": + for content in item.content: + if content.type == "output_text": + print(content.text) diff --git a/examples/byteplus/sessions_loop.py b/examples/byteplus/sessions_loop.py new file mode 100644 index 0000000..c887382 --- /dev/null +++ b/examples/byteplus/sessions_loop.py @@ -0,0 +1,134 @@ +"""End-to-end agent-loop example — Create Agent + Environment + Session, +send a text prompt, stream events until the loop settles, print the +assistant's response. + +Exercises the smallest useful call sequence: + + POST /api/v3/agents (client.agents.create) + POST /api/v3/environments (client.environments.create) + POST /api/v3/sessions (client.sessions.create) + POST /api/v3/sessions/:id/events (client.sessions.events.send) + GET /api/v3/sessions/:id/events (stream) (client.sessions.events.stream) + +Uses typed pydantic classes end-to-end on both send and receive so callers +get IDE completion + pyright/mypy field-name checks. Mirrors the Go SDK's +examples/sessions_loop/main.go 1:1. + + export ARK_API_KEY=... + export ARK_MODEL_ID=seed-2-0-lite-260428 + python examples/sessions_loop.py +""" + +from __future__ import annotations + +import os +import threading +import time + +from arkruntime import Ark +from arkruntime.types.agent import ModelConfig, ToolItem +from arkruntime.types.environment import EnvConfig, NetworkingConfig +from arkruntime.types.session import ( + ManagedAgentsAgentMessageEvent, + ManagedAgentsSessionErrorEvent, + ManagedAgentsSessionStatusIdleEvent, + ManagedAgentsSessionStatusTerminatedEvent, + ManagedAgentsTextBlock, + ManagedAgentsUserMessageEventParams, +) + +STOP_EVENT_TYPES = ( + ManagedAgentsSessionStatusIdleEvent, + ManagedAgentsSessionStatusTerminatedEvent, + ManagedAgentsSessionErrorEvent, +) + + +def main() -> None: + api_key = os.environ.get("ARK_API_KEY") + if not api_key: + raise SystemExit("set ARK_API_KEY") + model_id = os.environ.get("ARK_MODEL_ID", "${YOUR_MODEL_ID}") + + client = Ark.byteplus(api_key=api_key) + + ag = client.agents.create( + name=f"example-loop-agent-{time.time_ns()}", + model=ModelConfig(id=model_id), + system="You are a helpful assistant. Answer the user's question briefly.", + tools=[ToolItem(type="agent_toolset_20260401")], + ) + print(f"agent: id={ag.id}") + + env = client.environments.create( + name=f"example-loop-env-{time.time_ns()}", + config=EnvConfig( + type="cloud", + networking=NetworkingConfig(type="unrestricted"), + ), + ) + print(f"env: id={env.id}") + + sess = client.sessions.create( + agent=ag.id, + environment_id=env.id, + title="ark-runtime-python example loop", + ) + print(f"session: id={sess.id}\n") + + try: + # Open the SSE stream first, then send the user message asynchronously + # so we don't race and miss the earliest events. + def _send() -> None: + time.sleep(0.5) # Warmup so SSE is fully attached before we push. + client.sessions.events.send( + sess.id, + events=[ + ManagedAgentsUserMessageEventParams( + type="user.message", + content=[ + ManagedAgentsTextBlock( + type="text", + text="What's the tallest mountain? One sentence.", + ) + ], + ) + ], + ) + + threading.Thread(target=_send, daemon=True).start() + + assistant_out: list[str] = [] + for event in client.sessions.events.stream(sess.id): + ev = event.data + if ev is None: + continue + print(f"[EVT] {event.type}") + + if isinstance(ev, ManagedAgentsAgentMessageEvent): + for block in ev.content: + if block.text: + assistant_out.append(block.text) + elif isinstance(ev, STOP_EVENT_TYPES): + break + + joined = "".join(assistant_out).strip() + if joined: + print(f"\nassistant → {joined}") + else: + print("\n(no assistant text captured — check the [EVT] trace above)") + finally: + # Cleanup — delete session → env → agent. + for delete, obj_id in ( + (client.sessions.delete, sess.id), + (client.environments.delete, env.id), + (client.agents.delete, ag.id), + ): + try: + delete(obj_id) + except Exception as exc: # noqa: BLE001 + print(f"cleanup {delete.__qualname__}({obj_id}): {exc}") + + +if __name__ == "__main__": + main() diff --git a/examples/byteplus/sparse_embeddings.py b/examples/byteplus/sparse_embeddings.py new file mode 100644 index 0000000..27d75ec --- /dev/null +++ b/examples/byteplus/sparse_embeddings.py @@ -0,0 +1,24 @@ +from arkruntime import Ark +from arkruntime.types.multimodal_embedding import MultiModalEmbeddingResponse + +client = Ark.byteplus() + +print("----- multimodal embeddings request -----") +resp: MultiModalEmbeddingResponse = client.multimodal_embeddings.create( + model="skylark-embedding-vision-251215", + input=[ + { + "type": "text", + "text": "花椰菜又称菜花、花菜,是一种常见的蔬菜。", + } + ], + sparse_embedding={"type": "enabled"}, # enable sparse embedding +) +# dense embeddings +print("---- dense embeddings ----") +print(resp.data.embedding) + +# sparse embeddings +print("---- sparse embeddings ----") +for item in resp.data.sparse_embedding: + print(item) diff --git a/examples/byteplus/tokenization.py b/examples/byteplus/tokenization.py new file mode 100644 index 0000000..2a675bd --- /dev/null +++ b/examples/byteplus/tokenization.py @@ -0,0 +1,13 @@ +import os + +from arkruntime import Ark + +client = Ark.byteplus() +MODEL = os.environ.get("ENDPOINT_ID", "seed-2-0-lite-260428") + +print("----- tokenization request -----") +resp = client.tokenization.create( + model=MODEL, + text=["花椰菜又称菜花、花菜,是一种常见的蔬菜。"], +) +print(resp) diff --git a/examples/image_generations.py b/examples/image_generations.py deleted file mode 100644 index a053669..0000000 --- a/examples/image_generations.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Image generation examples for the new arkruntime SDK. - -Mirrors the legacy `volcenginesdkarkruntime/image_generations.py` walkthrough -(Seedream / Seededit text-to-image and edit-from-image), wired against the -generated `/images/generations` surface. Streaming is omitted — see -`sequential_image_generation` for multi-image generation in one round-trip. -""" - -import os - -from arkruntime import Ark -from arkruntime.types.images.sequential_image_generation_options_param import ( - SequentialImageGenerationOptionsParam, -) - -client = Ark(api_key=os.environ["ARK_API_KEY"]) -# Seedream model — used for the text-to-image and sequential examples. -# Override via env vars to point at your own endpoint IDs. -SEEDREAM_MODEL = os.environ.get("SEEDREAM_ENDPOINT_ID", "doubao-seedream-4-0-250828") -# Seededit model — used for the image-edit example. Only Seededit supports -# `size="adaptive"` and the `image=[...]` reference-image input. -SEEDEDIT_MODEL = os.environ.get("SEEDEDIT_ENDPOINT_ID", "doubao-seededit-3-0-i2i-250628") -SEEDEDIT_INPUT_IMAGE_URL = os.environ.get( - "SEEDEDIT_INPUT_IMAGE_URL", - "https://ark-project.tos-cn-beijing.volces.com/doc_image/seedream_i2i.jpeg", -) - - -if __name__ == "__main__": - print("----- [Seedream] generate images -----") - result = client.images.generate( - model=SEEDREAM_MODEL, - prompt="龙与地下城女骑士背景是起伏的平原,目光从镜头转向平原", - seed=1234567890, - watermark=True, - size="1024x1024", - ) - print(result) - - print("----- [Seededit] generate images (with input image) -----") - result = client.images.generate( - model=SEEDEDIT_MODEL, - prompt="把背景换成黄昏的沙漠", - image=[SEEDEDIT_INPUT_IMAGE_URL], - seed=1234567890, - watermark=True, - size="adaptive", # only valid for Seededit i2i - ) - print(result) - - print("----- [Seedream] sequential image generation -----") - result = client.images.generate( - model=SEEDREAM_MODEL, - prompt="星球大战, 需要三幅图片描绘不同的战斗场景", - response_format="url", - seed=1234567890, - watermark=True, - size="1024x1024", - sequential_image_generation="auto", - sequential_image_generation_options=SequentialImageGenerationOptionsParam(max_images=3), - ) - for i, item in enumerate(result.data or []): - print(f"[{i}] size={item.size} url={item.url}") - if result.usage is not None: - print("usage:", result.usage) diff --git a/examples/agents.py b/examples/volc/agents.py similarity index 94% rename from examples/agents.py rename to examples/volc/agents.py index 7ef633f..8d094bc 100644 --- a/examples/agents.py +++ b/examples/volc/agents.py @@ -22,7 +22,7 @@ def main() -> None: raise SystemExit("set ARK_API_KEY") model_id = os.environ.get("ARK_MODEL_ID", "${YOUR_MODEL_ID}") - client = Ark(api_key=api_key) + client = Ark.volc(api_key=api_key) # 1. Create name = f"example-agent-{time.time_ns()}" @@ -57,7 +57,7 @@ def main() -> None: finally: # 6. Delete deleted = client.agents.delete(created.id) - print(f"deleted: id={deleted.id} deleted={deleted.deleted}") + print(f"deleted: id={deleted.id}") if __name__ == "__main__": diff --git a/examples/batch/async_chat_completions.py b/examples/volc/batch/async_chat_completions.py similarity index 97% rename from examples/batch/async_chat_completions.py rename to examples/volc/batch/async_chat_completions.py index 14ddaf6..7a5b7f9 100644 --- a/examples/batch/async_chat_completions.py +++ b/examples/volc/batch/async_chat_completions.py @@ -28,7 +28,7 @@ async def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "asyncio.Queue[dict]" = asyncio.Queue() - client = AsyncArk(timeout=24 * 3600) + client = AsyncArk.volc(timeout=24 * 3600) for _ in range(task_num): await requests.put( diff --git a/examples/batch/async_embeddings.py b/examples/volc/batch/async_embeddings.py similarity index 96% rename from examples/batch/async_embeddings.py rename to examples/volc/batch/async_embeddings.py index f5e3f2c..9ab8339 100644 --- a/examples/batch/async_embeddings.py +++ b/examples/volc/batch/async_embeddings.py @@ -28,7 +28,7 @@ async def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "asyncio.Queue[dict]" = asyncio.Queue() - client = AsyncArk(timeout=24 * 3600) + client = AsyncArk.volc(timeout=24 * 3600) for _ in range(task_num): await requests.put( diff --git a/examples/batch/async_multimodal_embeddings.py b/examples/volc/batch/async_multimodal_embeddings.py similarity index 97% rename from examples/batch/async_multimodal_embeddings.py rename to examples/volc/batch/async_multimodal_embeddings.py index a2160c8..9672278 100644 --- a/examples/batch/async_multimodal_embeddings.py +++ b/examples/volc/batch/async_multimodal_embeddings.py @@ -28,7 +28,7 @@ async def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "asyncio.Queue[dict]" = asyncio.Queue() - client = AsyncArk(timeout=24 * 3600) + client = AsyncArk.volc(timeout=24 * 3600) for _ in range(task_num): await requests.put( diff --git a/examples/batch/chat_completions.py b/examples/volc/batch/chat_completions.py similarity index 97% rename from examples/batch/chat_completions.py rename to examples/volc/batch/chat_completions.py index c12b129..0a76a78 100644 --- a/examples/batch/chat_completions.py +++ b/examples/volc/batch/chat_completions.py @@ -40,7 +40,7 @@ def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "queue.Queue[dict | None]" = queue.Queue() - client = Ark(timeout=24 * 3600) + client = Ark.volc(timeout=24 * 3600) # mock `task_num` tasks for _ in range(task_num): diff --git a/examples/batch/embeddings.py b/examples/volc/batch/embeddings.py similarity index 97% rename from examples/batch/embeddings.py rename to examples/volc/batch/embeddings.py index 87eea54..9504e2d 100644 --- a/examples/batch/embeddings.py +++ b/examples/volc/batch/embeddings.py @@ -33,7 +33,7 @@ def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "queue.Queue[dict | None]" = queue.Queue() - client = Ark(timeout=24 * 3600) + client = Ark.volc(timeout=24 * 3600) for _ in range(task_num): requests.put( diff --git a/examples/batch/multimodal_embeddings.py b/examples/volc/batch/multimodal_embeddings.py similarity index 97% rename from examples/batch/multimodal_embeddings.py rename to examples/volc/batch/multimodal_embeddings.py index 39ba832..aa674a0 100644 --- a/examples/batch/multimodal_embeddings.py +++ b/examples/volc/batch/multimodal_embeddings.py @@ -33,7 +33,7 @@ def main() -> None: max_concurrent_tasks, task_num = 10, 100 requests: "queue.Queue[dict | None]" = queue.Queue() - client = Ark(timeout=24 * 3600) + client = Ark.volc(timeout=24 * 3600) for _ in range(task_num): requests.put( diff --git a/examples/chat/completions.py b/examples/volc/chat/completions.py similarity index 88% rename from examples/chat/completions.py rename to examples/volc/chat/completions.py index 219b10e..526f316 100644 --- a/examples/chat/completions.py +++ b/examples/volc/chat/completions.py @@ -4,17 +4,17 @@ # Authentication # 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" -# or specify api key by Ark(api_key="${YOUR_API_KEY}"). +# or specify api key by Ark.volc(api_key="${YOUR_API_KEY}"). # Note: If you use an API key, this API key will not be refreshed. # To prevent the API from expiring and failing after some time, choose an API key with no expiration date. # 2.If you authorize your endpoint with Volcengine Identity and Access Management(IAM), # set your api key to environment variable "VOLC_ACCESSKEY", "VOLC_SECRETKEY" -# or specify ak&sk by Ark(ak="${YOUR_AK}", sk="${YOUR_SK}"). +# or specify ak&sk by Ark.volc(ak="${YOUR_AK}", sk="${YOUR_SK}"). # To get your ak&sk, please refer to this document(https://www.volcengine.com/docs/6291/65568) # For more information,please check this document(https://www.volcengine.com/docs/82379/1263279) -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") +client = Ark.volc() +MODEL = os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628") if __name__ == "__main__": diff --git a/examples/volc/chat/function_call.py b/examples/volc/chat/function_call.py new file mode 100644 index 0000000..c6b4854 --- /dev/null +++ b/examples/volc/chat/function_call.py @@ -0,0 +1,75 @@ +"""Function calling example for chat.completions. + +Mirrors the Go SDK's chat/function_call example: registers a single +get_current_weather tool, then sends the same request both +non-streaming and streaming, aggregating tool-call deltas in the +streaming branch by their index. +""" + +import os + +from arkruntime import Ark + +# Authentication +# 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" +client = Ark.volc() +MODEL = os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628") + +WEATHER_TOOL = { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location"], + }, + }, +} + +if __name__ == "__main__": + messages = [{"role": "user", "content": "What's the weather like in Boston today?"}] + + # Non-streaming: + print("----- function call request -----") + completion = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=[WEATHER_TOOL], + ) + print(completion.model_dump_json(indent=2)) + + # Streaming: + print("----- function call stream request -----") + stream = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=[WEATHER_TOOL], + stream=True, + ) + final_calls: dict[int, dict] = {} + for chunk in stream: + if not chunk.choices: + continue + delta = chunk.choices[0].delta + if delta.content: + print(delta.content, end="") + for tc in delta.tool_calls or []: + cur = final_calls.setdefault(tc.index, {"id": "", "name": "", "args": ""}) + if tc.id and not cur["id"]: + cur["id"] = tc.id + if tc.function: + if tc.function.name and not cur["name"]: + cur["name"] = tc.function.name + if tc.function.arguments: + cur["args"] += tc.function.arguments + print() + for idx, call in sorted(final_calls.items()): + print(f"tool_call[{idx}] id={call['id']} name={call['name']} args={call['args']}") diff --git a/examples/volc/chat/reasoning_completions.py b/examples/volc/chat/reasoning_completions.py new file mode 100644 index 0000000..cc8af9f --- /dev/null +++ b/examples/volc/chat/reasoning_completions.py @@ -0,0 +1,40 @@ +import os + +from arkruntime import Ark + +# Authentication +# 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" +client = Ark.volc() +MODEL = os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628") + +if __name__ == "__main__": + # Streaming: + print("----- streaming request -----") + stream = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "How many Rs are there in the word 'strawberry'?"}, + ], + thinking={"type": "enabled"}, + stream=True, + ) + for chunk in stream: + if not chunk.choices: + continue + if chunk.choices[0].delta.reasoning_content: + print(chunk.choices[0].delta.reasoning_content, end="") + else: + print(chunk.choices[0].delta.content, end="") + print() + + # Non-streaming: + print("----- standard request -----") + completion = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "How many Rs are there in the word 'strawberry'?"}, + ], + thinking={"type": "enabled"}, + ) + print(completion.choices[0].message.reasoning_content) + print(completion.choices[0].message.content) diff --git a/examples/chat/structured_output.py b/examples/volc/chat/structured_output.py similarity index 93% rename from examples/chat/structured_output.py rename to examples/volc/chat/structured_output.py index d2d907b..08d8016 100644 --- a/examples/chat/structured_output.py +++ b/examples/volc/chat/structured_output.py @@ -17,8 +17,8 @@ # Authentication # 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") +client = Ark.volc() +MODEL = os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628") class MeetingInfo(BaseModel): diff --git a/examples/chat/vision_completions.py b/examples/volc/chat/vision_completions.py similarity index 81% rename from examples/chat/vision_completions.py rename to examples/volc/chat/vision_completions.py index 0907b74..a2449a0 100644 --- a/examples/chat/vision_completions.py +++ b/examples/volc/chat/vision_completions.py @@ -4,9 +4,9 @@ # Authentication # 1.If you authorize your endpoint using an API key, you can set your api key to environment variable "ARK_API_KEY" -# or specify api key by Ark(api_key="${YOUR_API_KEY}"). -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") +# or specify api key by Ark.volc(api_key="${YOUR_API_KEY}"). +client = Ark.volc() +MODEL = os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628") # Image input: response = client.chat.completions.create( diff --git a/examples/content_generation_tasks.py b/examples/volc/content_generation_tasks.py similarity index 97% rename from examples/content_generation_tasks.py rename to examples/volc/content_generation_tasks.py index 8769c19..3ec6c6a 100644 --- a/examples/content_generation_tasks.py +++ b/examples/volc/content_generation_tasks.py @@ -4,9 +4,9 @@ # Authentication # 1. If you authorize your endpoint using an API key, you can set your api key -# to environment variable "ARK_API_KEY" or pass it via Ark(api_key="..."). +# to environment variable "ARK_API_KEY" or pass it via Ark.volc(api_key="..."). # Note: API keys do not refresh — pick one with no expiration. -client = Ark() +client = Ark.volc() # Override these env vars to point at your own model + reference image. MODEL = os.environ.get("SEEDANCE_MODEL", "doubao-seedance-2-0-fast-260128") diff --git a/examples/embeddings.py b/examples/volc/embeddings.py similarity index 69% rename from examples/embeddings.py rename to examples/volc/embeddings.py index e46ff36..600ec82 100644 --- a/examples/embeddings.py +++ b/examples/volc/embeddings.py @@ -2,8 +2,8 @@ from arkruntime import Ark -client = Ark() -MODEL = os.environ.get("ENDPOINT_ID", "doubao-embedding-text-240715") +client = Ark.volc() +MODEL = os.environ.get("ENDPOINT_ID", "doubao-embedding-large-text-250515") print("----- embeddings request -----") resp = client.embeddings.create( diff --git a/examples/volc/environments.py b/examples/volc/environments.py new file mode 100644 index 0000000..cdf578f --- /dev/null +++ b/examples/volc/environments.py @@ -0,0 +1,61 @@ +"""Environment lifecycle example — Create/Get/List/Update/Delete. + +An Environment is the sandbox (network + filesystem policy) an Agent runs +inside during a Session. This example uses the cloud environment with +unrestricted networking; production usage will typically restrict either. + + export ARK_API_KEY=... + python examples/environments.py +""" + +from __future__ import annotations + +import os +import time + +from arkruntime import Ark +from arkruntime.types.environment.env_config import EnvConfig +from arkruntime.types.environment.networking_config import NetworkingConfig + + +def main() -> None: + api_key = os.environ.get("ARK_API_KEY") + if not api_key: + raise SystemExit("set ARK_API_KEY") + + client = Ark.volc(api_key=api_key) + + # 1. Create — cloud + unrestricted network. + name = f"example-env-{time.time_ns()}" + created = client.environments.create( + name=name, + config=EnvConfig( + type="cloud", + networking=NetworkingConfig(type="unrestricted"), + ), + ) + print(f"created: id={created.id} name={created.name}") + + try: + # 2. Get + got = client.environments.retrieve(created.id) + print(f"get: id={got.id} name={got.name} type={got.type}") + + # 3. List + listed = client.environments.list(limit=5) + print(f"list: {len(listed.data)} items, next_page={listed.next_page!r}") + + # 4. Update — attach a description. + updated = client.environments.update( + created.id, + description="updated by ark-runtime-python example", + ) + print(f"updated: id={updated.id} description={updated.description!r}") + finally: + # 5. Delete + deleted = client.environments.delete(created.id) + print(f"deleted: id={deleted.id}") + + +if __name__ == "__main__": + main() diff --git a/examples/files/upload_and_wait.py b/examples/volc/files/upload_and_wait.py similarity index 96% rename from examples/files/upload_and_wait.py rename to examples/volc/files/upload_and_wait.py index e265bea..a1e9209 100644 --- a/examples/files/upload_and_wait.py +++ b/examples/volc/files/upload_and_wait.py @@ -14,7 +14,7 @@ def main() -> None: if not api_key: sys.exit("set ARK_API_KEY") - client = Ark(api_key=api_key) + client = Ark.volc(api_key=api_key) target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__) print(f"uploading {target}") diff --git a/examples/volc/image_generations.py b/examples/volc/image_generations.py new file mode 100644 index 0000000..303c8b9 --- /dev/null +++ b/examples/volc/image_generations.py @@ -0,0 +1,22 @@ +"""Seedream image generation examples for the new arkruntime SDK.""" + +import os + +from arkruntime import Ark + +client = Ark.volc(api_key=os.environ["ARK_API_KEY"]) +# Seedream model — used for the text-to-image example. +# Override via env vars to point at your own endpoint IDs. +SEEDREAM_MODEL = os.environ.get("SEEDREAM_ENDPOINT_ID", "doubao-seedream-5-0-pro-260628") + + +if __name__ == "__main__": + print("----- [Seedream] generate images -----") + result = client.images.generate( + model=SEEDREAM_MODEL, + prompt="龙与地下城女骑士背景是起伏的平原,目光从镜头转向平原", + seed=1234567890, + watermark=True, + size="1024x1024", + ) + print(result) diff --git a/examples/memory_stores.py b/examples/volc/memory_stores.py similarity index 98% rename from examples/memory_stores.py rename to examples/volc/memory_stores.py index d920899..062d01d 100644 --- a/examples/memory_stores.py +++ b/examples/volc/memory_stores.py @@ -31,7 +31,7 @@ def main() -> None: if not api_key: raise SystemExit("set ARK_API_KEY") - client = Ark(api_key=api_key) + client = Ark.volc(api_key=api_key) # 1. Create a memory store. store = client.memory_stores.create(name=f"example-store-{time.time_ns()}") diff --git a/examples/multimodal_embeddings.py b/examples/volc/multimodal_embeddings.py similarity index 84% rename from examples/multimodal_embeddings.py rename to examples/volc/multimodal_embeddings.py index 07b6ae4..9f640c6 100644 --- a/examples/multimodal_embeddings.py +++ b/examples/volc/multimodal_embeddings.py @@ -1,10 +1,10 @@ from arkruntime import Ark -client = Ark() +client = Ark.volc() print("----- multimodal embeddings request -----") resp = client.multimodal_embeddings.create( - model="doubao-embedding-vision-250615", + model="doubao-embedding-vision-251215", input=[ {"type": "text", "text": "What is the weather like today?"}, {"type": "image_url", "image_url": {"url": "https://ark-project.tos-cn-beijing.volces.com/images/view.jpeg"}}, diff --git a/examples/responses/async_create.py b/examples/volc/responses/async_create.py similarity index 97% rename from examples/responses/async_create.py rename to examples/volc/responses/async_create.py index c8f636a..4ee2fba 100644 --- a/examples/responses/async_create.py +++ b/examples/volc/responses/async_create.py @@ -17,7 +17,7 @@ 4. 使用MCP工具 (MCP) """ -client = AsyncArk() +client = AsyncArk.volc() async def main(): @@ -166,6 +166,7 @@ async def main(): }, } ], + extra_headers={"ark-beta-web-search": "true"}, store=True, stream=True, ) @@ -198,6 +199,7 @@ async def main(): "require_approval": "always", } ], + extra_headers={"ark-beta-mcp": "true"}, store=True, stream=True, ) @@ -230,6 +232,7 @@ async def main(): "require_approval": "always", } ], + extra_headers={"ark-beta-mcp": "true"}, store=True, stream=True, ) diff --git a/examples/responses/async_doubao_app.py b/examples/volc/responses/async_doubao_app.py similarity index 94% rename from examples/responses/async_doubao_app.py rename to examples/volc/responses/async_doubao_app.py index d08e0e8..594cc6f 100644 --- a/examples/responses/async_doubao_app.py +++ b/examples/volc/responses/async_doubao_app.py @@ -8,7 +8,7 @@ 示例代码:演示 Responses API + 豆包助手 built in tool """ -client = AsyncArk() +client = AsyncArk.volc() async def main(): @@ -34,6 +34,7 @@ async def main(): }, } ], + extra_headers={"ark-beta-doubao-app": "true"}, store=True, stream=True, ) @@ -64,6 +65,7 @@ async def main(): }, } ], + extra_headers={"ark-beta-doubao-app": "true"}, store=True, stream=True, ) diff --git a/examples/responses/async_video.py b/examples/volc/responses/async_video.py similarity index 99% rename from examples/responses/async_video.py rename to examples/volc/responses/async_video.py index 253072e..0b8f801 100644 --- a/examples/responses/async_video.py +++ b/examples/volc/responses/async_video.py @@ -15,7 +15,7 @@ from arkruntime import AsyncArk from arkruntime.types.responses import ResponseCompletedEvent -client = AsyncArk() +client = AsyncArk.volc() DEFAULT_VIDEO_URL = "https://an-test-imgs.tos-cn-beijing.volces.com/videos/test_videos/04_duration_5s.mp4" diff --git a/examples/volc/responses/create.py b/examples/volc/responses/create.py new file mode 100644 index 0000000..c491173 --- /dev/null +++ b/examples/volc/responses/create.py @@ -0,0 +1,15 @@ +import os + +from arkruntime import Ark + +client = Ark.volc() + +response = client.responses.create( + model=os.environ.get("ARK_MODEL", "doubao-seed-2-1-pro-260628"), + input="Explain large language models in one sentence.", +) +for item in response.output or []: + if item.type == "message": + for content in item.content: + if content.type == "output_text": + print(content.text) diff --git a/examples/sessions_loop.py b/examples/volc/sessions_loop.py similarity index 99% rename from examples/sessions_loop.py rename to examples/volc/sessions_loop.py index 9e9cb48..81d8034 100644 --- a/examples/sessions_loop.py +++ b/examples/volc/sessions_loop.py @@ -50,7 +50,7 @@ def main() -> None: raise SystemExit("set ARK_API_KEY") model_id = os.environ.get("ARK_MODEL_ID", "${YOUR_MODEL_ID}") - client = Ark(api_key=api_key) + client = Ark.volc(api_key=api_key) ag = client.agents.create( name=f"example-loop-agent-{time.time_ns()}", diff --git a/examples/sparse_embeddings.py b/examples/volc/sparse_embeddings.py similarity index 91% rename from examples/sparse_embeddings.py rename to examples/volc/sparse_embeddings.py index c287c60..33e5c39 100644 --- a/examples/sparse_embeddings.py +++ b/examples/volc/sparse_embeddings.py @@ -1,11 +1,11 @@ from arkruntime import Ark from arkruntime.types.multimodal_embedding import MultiModalEmbeddingResponse -client = Ark() +client = Ark.volc() print("----- multimodal embeddings request -----") resp: MultiModalEmbeddingResponse = client.multimodal_embeddings.create( - model="doubao-embedding-vision-250615", + model="doubao-embedding-vision-251215", input=[ { "type": "text", diff --git a/examples/tokenization.py b/examples/volc/tokenization.py similarity index 93% rename from examples/tokenization.py rename to examples/volc/tokenization.py index cf5c329..b7f7226 100644 --- a/examples/tokenization.py +++ b/examples/volc/tokenization.py @@ -2,7 +2,7 @@ from arkruntime import Ark -client = Ark() +client = Ark.volc() MODEL = os.environ.get("ENDPOINT_ID", "doubao-seed-2-1-pro-260628") print("----- tokenization request -----") diff --git a/src/arkruntime/_utils/_transform.py b/src/arkruntime/_utils/_transform.py index 8826e79..5d99284 100644 --- a/src/arkruntime/_utils/_transform.py +++ b/src/arkruntime/_utils/_transform.py @@ -272,9 +272,10 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): - if not is_given(value): - # we don't need to include `NotGiven` values here as they'll - # be stripped out before the request is sent anyway + if value is None or not is_given(value): + # Optional request parameters default to None in generated methods. + # Omit both None and NotGiven values so the wire payload contains + # only fields the caller actually supplied. continue type_ = annotations.get(key) @@ -439,9 +440,10 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): - if not is_given(value): - # we don't need to include `NotGiven` values here as they'll - # be stripped out before the request is sent anyway + if value is None or not is_given(value): + # Optional request parameters default to None in generated methods. + # Omit both None and NotGiven values so the wire payload contains + # only fields the caller actually supplied. continue type_ = annotations.get(key)