CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
sdk-python.md512 linesDownload Raw Back to developers
1# Python SDK2 3## `qwen-code-sdk`4 5`qwen-code-sdk` is an experimental Python SDK for Qwen Code. v1 targets the6existing `stream-json` CLI protocol and keeps the transport surface small and7testable.8 9## Scope10 11- Package name: `qwen-code-sdk`12- Import path: `qwen_code_sdk`13- Runtime requirement: Python `>=3.10`14- CLI dependency: external `qwen` executable is required in v115- Transport scope: process transport only16- Not included in v1: ACP transport, SDK-embedded MCP servers17 18## Install19 20```bash21pip install qwen-code-sdk22```23 24For preview releases:25 26```bash27pip install --pre qwen-code-sdk28```29 30If `qwen` is not on `PATH`, pass `path_to_qwen_executable` explicitly.31 32Before writing SDK code, make sure the CLI works in the same shell:33 34```bash35qwen --version36```37 38## Quick Start39 40```python41import asyncio42 43from qwen_code_sdk import (44    is_sdk_assistant_message,45    is_sdk_result_message,46    query,47)48 49 50def extract_text(message):51    content = message.get("message", {}).get("content", [])52    if not isinstance(content, list):53        return repr(content)54    texts = [55        block.get("text", "")56        for block in content57        if isinstance(block, dict) and block.get("type") == "text"58    ]59    return "".join(texts) if texts else "[no text content]"60 61 62def print_result(message):63    if message.get("is_error"):64        error = message.get("error") or {}65        print(f"Error: {error.get('message', 'Unknown error')}")66        return67    print(message.get("result", ""))68 69 70async def main() -> None:71    async with query(72        "Explain the repository structure.",73        {74            "cwd": "/path/to/project",75            "path_to_qwen_executable": "qwen",76        },77    ) as result:78        async for message in result:79            if is_sdk_assistant_message(message):80                print(extract_text(message))81            elif is_sdk_result_message(message):82                print_result(message)83 84 85asyncio.run(main())86```87 88`asyncio.run()` is appropriate for standalone scripts. If your application89already runs an event loop, such as Jupyter, FastAPI, or pytest-asyncio, call90`await main()` instead.91 92## Sync Usage93 94Use `query_sync` when your host application is not async:95 96```python97from qwen_code_sdk import is_sdk_result_message, query_sync98 99 100with query_sync(101    "Summarize this repository in one paragraph.",102    {103        "cwd": "/path/to/project",104        "path_to_qwen_executable": "qwen",105    },106) as result:107    for message in result:108        if is_sdk_result_message(message):109            if message.get("is_error"):110                error = message.get("error") or {}111                print(f"Error: {error.get('message', 'Unknown error')}")112            else:113                print(message.get("result", ""))114```115 116## API Surface117 118### Top-level entry points119 120- `query(prompt, options=None) -> Query`121- `query_sync(prompt, options=None) -> SyncQuery`122 123`prompt` supports either:124 125- `str` for single-turn requests126- `AsyncIterable[SDKUserMessage]` for multi-turn streams127 128### `Query`129 130- Async iterable over SDK messages131- `close()`132- `interrupt()`133- `set_model(model)`134- `set_permission_mode(mode)`135- `supported_commands()`136- `mcp_server_status()`137- `get_session_id()`138- `is_closed()`139 140### `QueryOptions`141 142| Option                     | Type / values                                              | Description                                                                                                     |143| -------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |144| `cwd`                      | `str`                                                      | Working directory for the CLI process.                                                                          |145| `model`                    | `str`                                                      | Model override for this SDK session.                                                                            |146| `path_to_qwen_executable`  | `str`                                                      | `qwen`, an explicit binary path, or a `.js` CLI bundle.                                                         |147| `permission_mode`          | `default`, `plan`, `auto-edit`, `yolo`                     | Tool execution approval mode. `yolo` auto-approves all tools; use it only in trusted or sandboxed environments. |148| `can_use_tool`             | async callback                                             | Custom permission callback for tool requests.                                                                   |149| `env`                      | `dict[str, str]`                                           | Extra environment variables passed to the CLI process.                                                          |150| `system_prompt`            | `str`                                                      | Override the system prompt.                                                                                     |151| `append_system_prompt`     | `str`                                                      | Append extra instructions to the system prompt.                                                                 |152| `debug`                    | `bool`                                                     | Forward CLI stderr to stderr when no `stderr` hook exists.                                                      |153| `max_session_turns`        | `int`                                                      | Maximum turns before the CLI ends the session.                                                                  |154| `core_tools`               | `list[str]`                                                | Restrict the available tool set.                                                                                |155| `exclude_tools`            | `list[str]`                                                | Exclude matching tools.                                                                                         |156| `allowed_tools`            | `list[str]`                                                | Allow matching tools without callback approval.                                                                 |157| `auth_type`                | `openai`, `anthropic`, `qwen-oauth`, `gemini`, `vertex-ai` | Authentication mode passed to the CLI.                                                                          |158| `include_partial_messages` | `bool`                                                     | Emit partial assistant stream events.                                                                           |159| `resume`                   | UUID string                                                | Resume a known session id.                                                                                      |160| `continue_session`         | `bool`                                                     | Continue the latest CLI session.                                                                                |161| `session_id`               | UUID string                                                | Start or correlate a session with a known id.                                                                   |162| `timeout`                  | mapping                                                    | Timeouts in seconds.                                                                                            |163| `stderr`                   | callable                                                   | Receives CLI stderr lines.                                                                                      |164 165Use only one of `resume`, `continue_session`, or `session_id` in a request. The166SDK raises `ValidationError` if these session options are combined.167 168Unsupported in v1:169 170- `mcp_servers`171 172### Common Configuration173 174```python175options = {176    "cwd": "/path/to/project",177    "path_to_qwen_executable": "qwen",178    "model": "qwen-plus",179    "permission_mode": "plan",180    "max_session_turns": 1,181    "env": {182        "OPENAI_MODEL": "qwen-plus",183    },184    "timeout": {185        "control_request": 60,186        "can_use_tool": 60,187        "stream_close": 60,188    },189}190```191 192Timeout values are seconds. `env` is merged on top of the parent process193environment, so you only need to pass variables that should differ for this SDK194session. Set secrets such as `OPENAI_API_KEY` in the parent environment or a195secrets manager rather than hardcoding them in source.196 197## Permission Handling198 199When the CLI emits a `can_use_tool` control request, the SDK routes it through200`can_use_tool(tool_name, tool_input, context)`.201 202- Default behavior: deny203- Default timeout: 60 seconds, configurable with `timeout.can_use_tool`204- Timeout fallback: deny205- Callback exceptions: converted to deny with an error message206- Callback context: `cancel_event`, `suggestions`, and `blocked_path`207- Callback contract: `can_use_tool` must be async with 3 positional arguments;208  `stderr` must accept 1 positional string argument209 210Example:211 212```python213import asyncio214from pathlib import Path215 216from qwen_code_sdk import is_sdk_result_message, query217 218PROJECT_ROOT = Path("/path/to/project").resolve()219 220 221def project_path(tool_name, tool_input):222    key = "path" if tool_name == "list_directory" else "file_path"223    raw_path = tool_input.get(key)224    if not isinstance(raw_path, str) or not raw_path:225        return None226 227    resolved = (PROJECT_ROOT / raw_path).resolve()228    try:229        resolved.relative_to(PROJECT_ROOT)230    except ValueError:231        return None232    return resolved233 234 235async def can_use_tool(tool_name, tool_input, context):236    if tool_name in {"read_file", "list_directory", "write_file"}:237        resolved = project_path(tool_name, tool_input)238        if resolved is None:239            return {240                "behavior": "deny",241                "message": "Only project-local paths are allowed",242            }243 244        if tool_name == "write_file" and resolved.suffix != ".md":245            return {"behavior": "deny", "message": "Only .md files can be written"}246 247        return {"behavior": "allow", "updatedInput": tool_input}248 249    return {250        "behavior": "deny",251        "message": f"{tool_name} is not allowed by this application",252    }253 254 255async def main():256    async with query(257        "Update README.md with a short summary.",258        {259            "cwd": str(PROJECT_ROOT),260            "path_to_qwen_executable": "qwen",261            "can_use_tool": can_use_tool,262        },263    ) as result:264        async for message in result:265            if is_sdk_result_message(message):266                if message.get("is_error"):267                    error = message.get("error") or {}268                    print(f"Error: {error.get('message', 'Unknown error')}")269                else:270                    print(message.get("result", ""))271 272 273asyncio.run(main())274```275 276If you do not pass `can_use_tool`, the SDK denies permission requests by277default.278 279## Multi-Turn Sessions280 281For multi-turn sessions, pass an async iterable of `SDKUserMessage` objects:282 283```python284import asyncio285 286from qwen_code_sdk import SDKUserMessage, is_sdk_result_message, query287 288SESSION_ID = "123e4567-e89b-12d3-a456-426614174000"289 290 291async def prompts():292    first: SDKUserMessage = {293        "type": "user",294        "session_id": SESSION_ID,295        "message": {296            "role": "user",297            "content": "Create a concise project summary.",298        },299        "parent_tool_use_id": None,300    }301    yield first302 303    second: SDKUserMessage = {304        "type": "user",305        "session_id": SESSION_ID,306        "message": {307            "role": "user",308            "content": "Also list the test files.",309        },310        "parent_tool_use_id": None,311    }312    yield second313 314 315async def main():316    async with query(317        prompts(),318        {319            "cwd": "/path/to/project",320            "path_to_qwen_executable": "qwen",321            "session_id": SESSION_ID,322        },323    ) as result:324        async for message in result:325            if is_sdk_result_message(message):326                if message.get("is_error"):327                    error = message.get("error") or {}328                    print(f"Error: {error.get('message', 'Unknown error')}")329                else:330                    print(message.get("result", ""))331 332 333asyncio.run(main())334```335 336All messages in the async iterable must be known upfront. The SDK sends them337sequentially to the CLI but cannot feed a prior response back into the generator.338If you need conversational turn-taking, manage each turn as a separate `query()`339call.340 341## Runtime Controls342 343The returned `Query` object can control the running CLI process:344 345```python346import asyncio347 348from qwen_code_sdk import is_sdk_result_message, query349 350 351async def main():352    async with query(353        "Inspect this repository and explain the test layout.",354        {355            "cwd": "/path/to/project",356            "path_to_qwen_executable": "qwen",357        },358    ) as result:359        commands = await result.supported_commands()360        print(commands)361 362        await result.set_permission_mode("plan")363        await result.set_model("qwen-plus")364 365        async for message in result:366            if is_sdk_result_message(message):367                if message.get("is_error"):368                    error = message.get("error") or {}369                    print(f"Error: {error.get('message', 'Unknown error')}")370                else:371                    print(message.get("result", ""))372 373 374asyncio.run(main())375```376 377Use `interrupt()` to cancel the current operation, `close()` to clean up the378underlying process, and `get_session_id()` to persist a session id for later.379 380## Session Resume381 382```python383import asyncio384 385from qwen_code_sdk import is_sdk_result_message, query386 387 388async def main():389    # Resume a known session by its id.390    async with query(391        "Continue from this session.",392        {393            "path_to_qwen_executable": "qwen",394            "resume": "123e4567-e89b-12d3-a456-426614174000",395        },396    ) as known:397        async for message in known:398            if is_sdk_result_message(message):399                if message.get("is_error"):400                    error = message.get("error") or {}401                    print(f"Error: {error.get('message', 'Unknown error')}")402                else:403                    print(message.get("result", ""))404 405 406asyncio.run(main())407```408 409To continue the latest session instead:410 411```python412import asyncio413 414from qwen_code_sdk import is_sdk_result_message, query415 416 417async def main():418    async with query(419        "Continue the latest session.",420        {421            "path_to_qwen_executable": "qwen",422            "continue_session": True,423        },424    ) as latest:425        async for message in latest:426            if is_sdk_result_message(message):427                if message.get("is_error"):428                    error = message.get("error") or {}429                    print(f"Error: {error.get('message', 'Unknown error')}")430                else:431                    print(message.get("result", ""))432 433 434asyncio.run(main())435```436 437`resume` is useful when your application stores session ids. `continue_session`438delegates the selection of the latest session to the CLI.439 440## Error Model441 442- `ValidationError`: invalid options, invalid UUIDs, unsupported combinations443- `ControlRequestTimeoutError`: initialize, interrupt, or other control request444  timed out445- `ProcessExitError`: CLI exited non-zero446- `AbortError`: control request or session was cancelled447 448```python449from qwen_code_sdk import (450    ProcessExitError,451    ValidationError,452    is_sdk_result_message,453    query_sync,454)455 456try:457    with query_sync("Say hello", {"path_to_qwen_executable": "qwen"}) as result:458        for message in result:459            if is_sdk_result_message(message):460                if message.get("is_error"):461                    error = message.get("error") or {}462                    print(f"Error: {error.get('message', 'Unknown error')}")463                else:464                    print(message.get("result", ""))465except ValidationError as exc:466    print(f"Invalid SDK options: {exc}")467except ProcessExitError as exc:468    print(f"qwen exited with {exc.exit_code}: {exc}")469```470 471## Troubleshooting472 473If the SDK cannot start the CLI:474 475- Verify `qwen --version` works in the target environment476- Pass `path_to_qwen_executable` if your shell uses `nvm`, `pyenv`, or other477  non-standard PATH setup478- Use `debug=True` or `stderr=print` to surface CLI stderr while debugging479 480If session control calls time out:481 482- Check that the target `qwen` version supports `--input-format stream-json`483- Increase `timeout.control_request`484- Verify that no wrapper script is swallowing stdout/stderr485 486## Repository Integration487 488Repository-level helper commands:489 490- `npm run test:sdk:python`491- `npm run lint:sdk:python`492- `npm run typecheck:sdk:python`493- `npm run smoke:sdk:python -- --qwen qwen`494 495## Real E2E Smoke496 497For a real runtime check (actual `qwen` process + real model call), run from498the repository root. The npm helper uses `python3`, so ensure it resolves to a499Python `>=3.10` interpreter:500 501```bash502npm run smoke:sdk:python -- --qwen qwen503```504 505This script runs:506 507- async single-turn query508- async control flow (`supported_commands`, permission mode updates)509- sync `query_sync` query510 511It prints JSON and returns non-zero on failure.512 
basant307/AI_Governance_Project · CoolFace