# RustGLM **Repository Path**: BlueVale/RustGLM ## Basic Information - **Project Name**: RustGLM - **Description**: High-performance, Reliable ChatGLM SDK natural language processing in Rust-Lang - **Primary Language**: Rust - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-02-22 - **Last Updated**: 2026-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RustGLM [English](README.md) | 简体中文 [![CI](https://github.com/blueokanna/rustglm/actions/workflows/ci.yml/badge.svg)](https://github.com/blueokanna/rustglm/actions/workflows/ci.yml) [![Release](https://github.com/blueokanna/rustglm/actions/workflows/release.yml/badge.svg)](https://github.com/blueokanna/rustglm/actions/workflows/release.yml) [![Crates.io](https://img.shields.io/crates/v/rustglm.svg)](https://crates.io/crates/rustglm) [![Documentation](https://docs.rs/rustglm/badge.svg)](https://docs.rs/rustglm) [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) RustGLM 是面向智谱 AI 开放平台的非官方异步 Rust SDK,提供强类型 GLM-5 请求、SSE 和 ToolStream 聚合、双向 Realtime WebSocket 会话、Batch API 操作、知识库管理,以及基于官方 Rust MCP SDK 的 MCP 客户端。 本 crate 面向生产后端。网络策略、持久化、凭据、重试和客户端生命周期均由应用显式控制。 ## 快速开始 添加默认 SDK 与 Tokio 运行时: ```toml [dependencies] rustglm = "1.0.0" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` 设置凭据并运行补全示例: ```powershell $env:ZHIPU_API_KEY = "key_id.secret" cargo run --example chat_completion ``` ```rust,no_run use rustglm::{ChatCompletionRequest, ChatMessage, ZhipuClient}; # async fn run() -> rustglm::Result<()> { let client = ZhipuClient::new("key_id.secret")?; let request = ChatCompletionRequest::new("glm-5.2") .message(ChatMessage::user("用一段话解释 Rust 所有权。")); let response = client.chat_completion(&request).await?; println!("{}", response.text().unwrap_or_default()); # Ok(()) # } ``` ## 要求 - Rust 1.88 或更高版本 - Rust 2024 Edition - 用于异步执行的 Tokio - 智谱 API Key,或已签发的智谱 Bearer token Cargo 包名和 Rust crate 名均为 `rustglm`。 ## 副作用约定 RustGLM 不会进行隐式磁盘 I/O。 - 库不会创建目录、查找配置文件、写入日志、缓存响应、持久化对话或保存音视频。 - 文件与 RAG 上传 API 接收调用方拥有的字节数据,内部绝不打开文件路径。 - 响应、SSE 帧、Realtime 媒体、内存快照和工具事件始终保留在内存或异步流中。 - 库不会从环境变量读取 API Key;凭据由构造函数传入。`EnvironmentSecretResolver` 是显式启用的 Agent 工具。 - 库从不访问 NTP、元数据、遥测或模型发现服务;JWT 签名仅使用本机系统时钟。 - HTTP 默认重试次数为零;MCP SSE 重试和过期会话自动初始化默认关闭。 - 构造配置值不产生网络 I/O。智谱请求只会在等待端点方法时发出;MCP 或 Realtime 连接只会在等待 `connect` 时建立。 示例可能显式读取环境变量或本地文件。这些属于应用层行为,并非 SDK 执行。 ## Feature flags 默认 feature 保留广泛的智谱 API 能力,同时使独立 MCP 协议客户端保持按需启用。 | Feature | 默认启用 | API 能力 | | --- | ---: | --- | | `agents` | 是 | 官方 Agent、Assistant 端点和本地 Agent 运行时 | | `audio` | 是 | GLM-4-Voice、转录、语音和音色操作 | | `batch` | 是 | 强类型 Batch API 创建、列表、查询和取消 | | `files` | 是 | 文件上传、下载、删除、解析、OCR 和版面分析 | | `images` | 是 | 图像生成 | | `mcp` | 否 | 基于 `rmcp` 的独立 Streamable HTTP MCP 客户端 | | `rag` | 是 | Retrieval Agent、知识库与文档管理 | | `realtime` | 是 | 强类型双向 WebSocket 客户端 | | `tools` | 是 | 托管工具类型、Web 操作和 ToolStream 聚合 | | `video` | 是 | 视频生成 | | `full` | 否 | 启用包括 `mcp` 在内的全部 feature | 最小 HTTP 聊天客户端: ```toml [dependencies] rustglm = { version = "1.0.0", default-features = false } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` 选择部分企业 API: ```toml [dependencies] rustglm = { version = "1.0.0", default-features = false, features = ["batch", "mcp", "rag", "realtime", "tools"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` 全部 API: ```toml [dependencies] rustglm = { version = "1.0.0", features = ["full"] } ``` ## 认证 `ZhipuClient::new`、`ZhipuConfig::new` 和 `RealtimeConfig::new` 均直接接收凭据。 - `key_id.secret` 被视为智谱组合 API Key,并签名为 HS256 JWT。 - 任何其他非空值均被视为不透明 Bearer token。 - 自动选择不合适时,可使用 `ZhipuAuthentication::jwt` 或 `ZhipuAuthentication::bearer`。 请勿提交凭据。应用应自行从进程环境、密钥管理器或工作负载身份提供方读取密钥。 ## 强类型 GLM-5 聊天 标记类型、封闭能力 trait 和请求 typestate 会阻止通过强类型 API 发送不支持的操作。请求在包含用户或工具输入前,无法传给强类型补全方法。 ```rust,no_run use rustglm::{Glm52, ReasoningEffort, Thinking, TypedChatRequest, ZhipuClient}; # async fn run() -> rustglm::Result<()> { let client = ZhipuClient::new("key_id.secret")?; let request = TypedChatRequest::::new() .system("Answer with evidence.") .thinking(Thinking::enabled()) .reasoning_effort(ReasoningEffort::High) .user("Summarize the incident report."); let response = client.typed_chat_completion(&request).await?; println!("{}", response.text().unwrap_or_default()); # Ok(()) # } ``` ### 支持的聊天模型 下表描述编译期强类型 API。新发布或私有模型 ID 仍可通过原始 `ChatCompletionRequest` 使用。 | 文本模型 | 标记类型 | Thinking | Reasoning effort | ToolStream | | --- | --- | :---: | :---: | :---: | | `glm-5.2` | `Glm52` | 是 | 是 | 是 | | `glm-5.1` | `Glm51` | 是 | 否 | 是 | | `glm-5.1-highspeed` | `Glm51Highspeed` | 是 | 否 | 是 | | `glm-5-turbo` | `Glm5Turbo` | 是 | 否 | 是 | | `glm-5` | `Glm5` | 是 | 否 | 是 | | `glm-4.7` | `Glm47` | 是 | 否 | 是 | | `glm-4.7-flash` | `Glm47Flash` | 是 | 否 | 否 | | `glm-4.7-flashx` | `Glm47FlashX` | 是 | 否 | 否 | | `glm-4.6` | `Glm46` | 是 | 否 | 是 | | `glm-4.5-air` | `Glm45Air` | 是 | 否 | 否 | | `glm-4.5-airx` | `Glm45AirX` | 是 | 否 | 否 | | `glm-4.5-flash` | `Glm45Flash` | 是 | 否 | 否 | | `glm-4-flash-250414` | `Glm4Flash250414` | 否 | 否 | 否 | | `glm-4-flashx-250414` | `Glm4FlashX250414` | 否 | 否 | 否 | | 视觉模型 | 标记类型 | Thinking | ToolStream | | --- | --- | :---: | :---: | | `glm-5v-turbo` | `Glm5vTurbo` | 是 | 否 | | `autoglm-phone` | `AutoGlmPhone` | 否 | 否 | | `glm-4.6v` | `Glm46v` | 是 | 否 | | `glm-4.6v-flash` | `Glm46vFlash` | 是 | 否 | | `glm-4.6v-flashx` | `Glm46vFlashX` | 是 | 否 | | `glm-4v-flash` | `Glm4vFlash` | 否 | 否 | | `glm-4.1v-thinking-flash` | `Glm41vThinkingFlash` | 是 | 否 | | `glm-4.1v-thinking-flashx` | `Glm41vThinkingFlashX` | 是 | 否 | `ReasoningEffort`、`Thinking`、ToolStream、工具和视觉输入只会暴露给声明相应能力的标记类型,从而阻止不受支持的字段通过强类型 API 到达传输层。 当新发布字段尚未获得强类型 builder 时,`ChatCompletionRequest` 仍可作为前向兼容的原始请求使用。 ## ToolStream ToolStream 将碎片化 SSE 函数调用增量合并为完整的强类型调用,同时保留文本、推理、用量和流错误。 ```rust,no_run use futures_util::StreamExt; use rustglm::{Glm52, ToolStreamEvent, TypedChatRequest, ZhipuClient}; # async fn run() -> rustglm::Result<()> { let client = ZhipuClient::new("token")?; let request = TypedChatRequest::::new().tool_stream().user("Check the deployment status."); let mut stream = client.typed_chat_tool_stream(&request).await?; while let Some(event) = stream.next().await { if let ToolStreamEvent::ToolCallCompleted(call) = event? { println!("{} {}", call.name, call.arguments); } } # Ok(()) # } ``` ## Batch API `batch` feature 提供强类型补全窗口和状态。Batch 输入文件通过 `files` API 显式上传。 ```rust,no_run use rustglm::{BatchCreateRequest, ZhipuClient}; # async fn run() -> rustglm::Result<()> { let client = ZhipuClient::new("token")?; let request = BatchCreateRequest::new("input-file-id", "/v4/chat/completions"); let batch = client.create_batch(&request).await?; let current = client.batch(&batch.id).await?; println!("{:?}", current.status); # Ok(()) # } ``` 可用方法为 `create_batch`、`batches`、`batch` 和 `cancel_batch`。不在 `1..=100` 范围内的列表限制会在网络 I/O 前返回 `BatchError::InvalidLimit`。 ## 知识库与 RAG `rag` feature 遵循官方知识库 OpenAPI 路径,涵盖知识库 CRUD、容量、检索、文档列表和详情、内存文件上传、URL 摄取、删除、文档图片和重新嵌入。`RagDocumentUpload::from_bytes` 有意不提供基于路径的构造函数;调用方控制文件读取、大小限制、加密、租户边界和保留策略。 ```rust,no_run use rustglm::{KnowledgeCreateRequest, KnowledgeEmbeddingModel, ZhipuClient}; # async fn run() -> rustglm::Result<()> { let client = ZhipuClient::new("token")?; let created = client.create_knowledge_base(&KnowledgeCreateRequest::new( "engineering-runbooks", KnowledgeEmbeddingModel::Embedding3Pro, )).await?; println!("{}", created.data.expect("successful response").id); # Ok(()) # } ``` ## MCP 客户端 `mcp` feature 是独立的 Model Context Protocol 客户端,与在模型请求中配置托管 MCP 工具的 `McpTool` 不同。协议帧、初始化、工具、资源、提示词和 Streamable HTTP 传输由官方 Rust MCP SDK(`rmcp`)提供。 ```rust,no_run use rustglm::McpClientConfig; # async fn run() -> rustglm::Result<()> { let mut client = McpClientConfig::new("https://mcp.example.com/mcp") .bearer_token("tenant-token").header("x-tenant-id", "acme")?.connect().await?; for tool in client.list_tools().await? { println!("{}", tool.name); } client.close().await?; # Ok(()) # } ``` 安全默认值:仅接受绝对 `http` 和 `https` 端点;授权显式配置并从 `Debug` 输出中脱敏;SDK 创建的 HTTP 客户端禁用重定向;SSE 重试和过期会话自动初始化默认关闭;可注入调用方配置的 `reqwest::Client` 控制代理、TLS、DNS、超时和策略。 ## Realtime WebSocket `realtime` feature 通过双向 WebSocket 提供强类型客户端请求和服务器事件。音视频作为调用方拥有的字节切片传入,并在内存中编码。 ```rust,no_run use rustglm::{RealtimeConfig, RealtimeRequest, TypedRealtimeSession}; # async fn run() -> rustglm::Result<()> { let mut connection = RealtimeConfig::new("token").connect().await?; let session = TypedRealtimeSession::default().instructions("Be concise.").server_vad(); connection.send_request(&RealtimeRequest::session_update(session)?).await?; connection.send_request(&RealtimeRequest::append_audio(&[0_u8; 320])?).await?; while let Some(event) = connection.next_typed_event().await { if let Some(text) = event?.delta_text() { print!("{text}"); } } # Ok(()) # } ``` 该 API 还支持强类型会话工具、函数调用输出、响应选项、转录会话、客户端/服务端 VAD、取消、音频提交/清空、视频帧和显式关闭连接。 ## 错误 `SdkError` 是公共错误封装。领域错误是显式枚举,可直接匹配,无需解析展示字符串。 ```rust use rustglm::{BatchError, SdkError}; fn classify(error: SdkError) { match error { SdkError::Batch(BatchError::InvalidLimit(limit)) => eprintln!("invalid batch limit: {limit}"), SdkError::Api(api) => eprintln!("HTTP {} request_id={:?}", api.status, api.request_id), other => eprintln!("{other}"), } } ``` 该封装区分配置、校验、传输、超时、API、解码、流、WebSocket、不支持能力、Agent、工具、Batch、RAG 和 MCP 失败。`ApiError` 保留 HTTP 状态、厂商代码、消息、请求 ID 和原始响应体。 ## HTTP 策略 `HttpConfig` 控制请求超时、连接超时、连接池空闲超时、user agent、默认请求头、重试策略,以及可选的调用方构建 `reqwest::Client`。 重试默认关闭。启用 `RetryPolicy` 是应用的显式决定;只有配置的状态码及连接/超时失败会被重试。 ## API 覆盖范围 下表是公开 SDK 操作的索引,依据公开客户端接口整理,而非假定服务商能力。接受 `serde_json::Value` 的方法有意保留与快速变化的服务商 Schema 的兼容性。 | 能力领域 | Feature | 公开方法 | | --- | --- | --- | | 聊天与流 | 核心;ToolStream 需 `tools` | `chat_completion`、`chat_completion_stream`、`chat_tool_stream`、`typed_chat_completion`、`typed_chat_completion_stream`、`typed_chat_tool_stream` | | 异步与向量 API | 核心 | `async_chat`、`async_result`、`embedding`、`rerank`、`tokenizer` | | 图像与视频 | `images`、`video` | `create_image`、`create_image_async`、`create_video` | | 音频与音色 | `audio` | `glm_4_voice`、`transcribe`、`speech`、`clone_voice`、`voices`、`delete_voice` | | 托管工具 | `tools` | `web_search`、`read_web_page`、`moderate` | | 文件与文档处理 | `files` | `upload_file`、`files`、`file_content`、`delete_file`、`create_file_parse_task`、`file_parse_result`、`parse_file_sync`、`ocr`、`parse_layout` | | Batch | `batch` | `create_batch`、`batches`、`batch`、`cancel_batch` | | 官方 Agent 与 Assistant | `agents` | `official_agent`、`official_agent_stream`、`official_agent_async_result`、`official_agent_conversation`、`assistant`、`assistants`、`assistant_conversations` | | 知识库与检索 | `rag` | `create_knowledge_base`、`knowledge_bases`、`knowledge_base`、`update_knowledge_base`、`delete_knowledge_base`、`knowledge_capacity`、`retrieve_knowledge`、`knowledge_documents`、`upload_knowledge_document`、`upload_knowledge_urls`、`knowledge_document`、`delete_knowledge_document`、`knowledge_document_images`、`reembed_knowledge_document`、`retrieval_agent_stream` | | 通用协议入口 | 核心 | `ZhipuClient` 与 `OpenAiCompatibleClient` 上的 `request_json` | | 独立 MCP | `mcp` | `McpClientConfig::connect`,以及由 `rmcp` 提供的强类型工具、资源、提示词和 Streamable HTTP 操作 | | Realtime | `realtime` | `RealtimeConfig::connect`、强类型请求/事件、VAD、媒体缓冲、函数调用输出、取消与显式关闭 | 服务商已发布字段尚未获得强类型 builder 时,使用 `ChatCompletionRequest`。只有在配置好的服务商 Base URL 下需要新相对路径时才使用 `request_json`;它会拒绝绝对 URL 与父级路径段。 > RustGLM 则提供服务商无关的本地 Agent 运行时、OpenAI 兼容客户端、通用 `rmcp` 协议客户端和支持视频的 Realtime 会话。 ## 示例 仓库包含 36 个可运行示例。当前每个 HTTP 端点领域都有聚焦示例;通常一起使用的操作会放进同一个生命周期示例。`cargo check --all-targets --all-features` 可在不联系服务商的情况下编译检查全部示例。 ### 聊天、模型与向量 | 示例 | 演示的公开 API | | --- | --- | | [`chat_completion`](examples/chat_completion.rs) | `chat_completion` | | [`chat_stream`](examples/chat_stream.rs) | `chat_completion_stream` | | [`typed_chat`](examples/typed_chat.rs) | `typed_chat_completion`、Thinking、推理强度 | | [`multimodal_chat`](examples/multimodal_chat.rs) | 视觉内容片段与图片 URL 输入 | | [`function_calling`](examples/function_calling.rs) | 函数 Schema 与 `Tool::function` | | [`tool_stream`](examples/tool_stream.rs) | `typed_chat_tool_stream` 与聚合后的函数调用增量 | | [`async_chat`](examples/async_chat.rs) | `async_chat`、`async_result` | | [`embedding`](examples/embedding.rs) | `EmbeddingRequest`、`embedding` | | [`rerank`](examples/rerank.rs) | `RerankRequest`、`rerank` | | [`tokenizer`](examples/tokenizer.rs) | `TokenizerRequest`、`tokenizer` | | [`openai_compatible`](examples/openai_compatible.rs) | `OpenAiCompatibleConfig`、`ChatProvider` | ### 媒体、文件与文档处理 | 示例 | 演示的公开 API | | --- | --- | | [`image_generation`](examples/image_generation.rs) | `create_image`、`create_image_async` | | [`video_generation`](examples/video_generation.rs) | `create_video`、异步任务 ID | | [`speech`](examples/speech.rs) | `SpeechRequest`、`speech` | | [`transcription`](examples/transcription.rs) | `TranscriptionRequest`、`transcribe` | | [`glm_4_voice`](examples/glm_4_voice.rs) | GLM-4-Voice 输入与 WAV 输出 | | [`voice_management`](examples/voice_management.rs) | `clone_voice`、`voices`、`delete_voice` | | [`file_management`](examples/file_management.rs) | `upload_file`、`files`、`file_content`、`delete_file` | | [`file_parsing`](examples/file_parsing.rs) | `create_file_parse_task`、`file_parse_result`、`parse_file_sync` | | [`document_understanding`](examples/document_understanding.rs) | `ocr`、`parse_layout` | ### Batch、托管工具与 RAG | 示例 | 演示的公开 API | | --- | --- | | [`web_search`](examples/web_search.rs) | `web_search` | | [`hosted_tools`](examples/hosted_tools.rs) | `read_web_page`、`moderate` | | [`file_batch`](examples/file_batch.rs) | 上传 JSONL 并调用 `create_batch` | | [`batch_management`](examples/batch_management.rs) | Batch 创建、列表、查询和取消 | | [`knowledge_base`](examples/knowledge_base.rs) | `create_knowledge_base` | | [`knowledge_management`](examples/knowledge_management.rs) | 知识库列表、详情、更新、容量与删除 | | [`knowledge_documents`](examples/knowledge_documents.rs) | 文档列表、上传、URL 导入、详情、图片、重嵌入与删除 | | [`knowledge_retrieval`](examples/knowledge_retrieval.rs) | `retrieve_knowledge` | | [`retrieval_agent`](examples/retrieval_agent.rs) | `retrieval_agent_stream` | ### Agent、MCP 与 Realtime | 示例 | 演示的公开 API | | --- | --- | | [`official_agent`](examples/official_agent.rs) | 强类型官方 Agent v1 调用 | | [`official_agent_lifecycle`](examples/official_agent_lifecycle.rs) | Agent 流、异步结果与会话操作 | | [`assistants`](examples/assistants.rs) | Assistant 调用、列表与会话 | | [`custom_agent`](examples/custom_agent.rs) | 带应用工具的本地 Agent 运行时 | | [`interactive_chat`](examples/interactive_chat.rs) | 多轮运行时与可选语义记忆 | | [`mcp_client`](examples/mcp_client.rs) | MCP 工具、资源、提示词与关闭连接 | | [`realtime_audio_video`](examples/realtime_audio_video.rs) | Realtime PCM/WAV、可选 JPEG 帧与强类型事件 | 通过 `cargo run --example -- <参数>` 运行示例。MCP 客户端为按需 feature,请使用 `cargo run --example mcp_client --features mcp -- `。大多数智谱示例需要 `ZHIPU_API_KEY`;`openai_compatible` 使用 `OPENAI_COMPATIBLE_BASE_URL` 与 `OPENAI_COMPATIBLE_API_KEY`。运行示例可能消耗额度、创建远程资源,或删除命令行中明确指定的资源。 ## CI 与发布 CI 工作流会验证: - 格式化; - 将警告视为错误的 Clippy; - 无默认 feature、默认 feature、全部 feature 和单独企业 feature 构建; - 测试和 doctest; - 将警告视为错误的文档构建; - 从已提交 lockfile 构建包; - 全 feature 行覆盖率不低于 90%,并上传 LCOV 与文本摘要。 发布工作流会在 `v*` tag 推送时运行。手动运行时,请在 Actions 页面选择要发布的提交或分支,并在 `tag` 输入中填写 `v`。工作流会检出页面所选版本,不再假定 tag 已经存在;它会拒绝版本不匹配,执行全部发布门禁,构建 `.crate`、写入 `SHA256SUMS`,然后创建缺失的附注 tag。已有 tag 只有在指向本次验证的提交时才会被接受。最后,工作流会在配置 `CARGO_REGISTRY_TOKEN` 时可选发布到 crates.io,并创建或更新 GitHub Release。 发布步骤: ```bash # 请先更新 Cargo.toml 与发布说明;Cargo.toml 当前版本为 1.0.0。 git tag -s v1.0.0 -m "RustGLM v1.0.0" git push origin v1.0.0 ``` 也可以在 `main` 分支上手动运行 `Release` 工作流,并将 `tag` 填为 `v1.0.0`,无需预先创建 tag。tag 使用普通的 `v1.0.0` 格式,而不是 `RustGLM v1.0.0`。 仓库中不保存 API Key 或 registry token。仅在需要发布 crates.io 时,将 `CARGO_REGISTRY_TOKEN` 配置为 GitHub Actions secret。 ## 测试与覆盖率 ```bash cargo fmt --all -- --check cargo test --all-targets --no-default-features cargo test --all-targets cargo test --all-targets --all-features cargo clippy --all-targets --all-features -- -D warnings RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps cargo package --locked ``` 仓库提供统一覆盖率命令,并由 CI 强制执行最低门槛: ```bash cargo coverage # 输出摘要并检查 90% 行覆盖率门槛 cargo coverage-lcov # 生成 target/rustglm-lcov.info,并检查相同门槛 ``` 最近一次工作区实测快照(2026-07-24): | 测试 | Regions | Functions | Lines | 行覆盖率门槛 | | ---: | ---: | ---: | ---: | ---: | | 94 个通过、2 个真实服务测试忽略 | 92.72% | 88.33% | 94.02% | 90.00% | 全 feature 测量包含所有库模块,包括可选的 MCP 与 Realtime。知识库/RAG 行覆盖率为 96.63%;成功的 MCP 协议操作需要已初始化的对端,因此其离线行覆盖率为 51.60%。完整模块表、指标解释与 HTML 报告命令见 [docs/COVERAGE.md](docs/COVERAGE.md)。 覆盖率命令会运行离线单元测试与集成测试,并编译全部 36 个示例,但不会执行示例的 `main` 函数,也不会运行被忽略的真实服务测试。请显式运行需要凭据的检查: ```powershell $env:ZHIPU_API_KEY = "key_id.secret" cargo test --test live_zhipu -- --ignored --nocapture cargo test --test live_realtime -- --ignored --nocapture ``` CI 会重新生成数据,并将 `lcov.info` 与 `coverage-summary.txt` 发布为构建产物;评估具体提交时应以该产物为准,不应把上面的快照视为永久承诺。 ## 许可证 Apache License 2.0。参见 [LICENSE](LICENSE)。