# aardio 调用 AI 大模型

## 一. 基本用法

[本节的完整范例源代码](../../../../examples/AI/aiChat.html)

1. 创建 AI 客户端

    ```aardio
    import web.rest.aiChat;
    var aiChat = web.rest.aiChat({
        key = '这里指定 API 密钥';
        url = "这里指定大模型接口地址";
        model = "这里指定模型名称"; //llama.cpp 本地模型可以省略
        temperature = 0.1; //可选指定温度 
        maxTokens = 16384; //可选指定最大回复长度
        protocol = null; //可选指定 API 接口协议类型
        extraParameters = {}; //可选增加的`自定义参数`
   } )
   ```

   - 构造参数表字段说明：
    
        web.rest.aiChat 的构造参数只能指定上面列出的字段，并且都是按小驼峰风格命名。调用时会自动转换为 API 接口的对应字段名，例如 topP 会转换为 top_p , 而 maxTokens 字段会根据接口不同改转换字段名为 max_tokens 或者 max_completion_tokens。

        maxTokens 通常包含推理思考时的消耗，个别模型的默认 maxTokens 比较小，如果在思考时达到固定长度就终止输出则应显式指定更大的 maxTokens 。

        topP 字段是可选的，一般建议指定 temperature 的值而不是指定 topP 的值。请参考：[关于 temperature 参数](../../../../guide/ide/ai.html#temperature)

        > 支持思考的推理模型通常要求将 temperature 设为 1。
        > 例如 Gemini 3.x Flash 或者 Gemini 3.x Pro 必须设为 1,设为其他值生成质量会显著下降。
        > 这是因为思考类模型需要获取更多可能性以生成最优结果。
        > OpenAI 的 GPT5.x 在开启思考模式时强制要求设为 1 。
        > Claude 4.x 开启思考时 temperature 只能设为 1，Opus 4.7 开始不允许用户指定 temperature 参数（aardio 会自动处理）
        > DeepSeek 4 在开启思考时会忽略 temperature 的值。

        url 字段用于指定接口地址。
        OpenAI 或 Anthropic 接口 URL 通常以 "/v1" 结尾。
        如果接口 URL 的路径部分为空或 `/anthropic` 并且尾部不是 `/` 则会默认添加 `/v1` 后缀。
        
        因此在 aardio 中填写以下格式的接口地址都是允许的：

        ```txt
        https://api.deepseek.com
        https://api.deepseek.com/v1
        https://api.deepseek.com/anthropic
        https://api.deepseek.com/anthropic/v1
        https://generativelanguage.googleapis.com/v1beta/openai
        https://api.deepseek.com/v1/chat/completions#
        ```

        网址后的 `#` 可显示指定精确的对话接口地址，阻止调用 `message` 方法时自动追加 `chat/completions` 等路径后缀。

        protocol 字段一般不必指定，默认会自动选择接口类型。

        protocol 可指定的值：
        - "openai" 使用 OpenAI Chat Completions 兼容协议，这是默认值
        - "responses" 使用 OpenAI Responses API 的 HTTP/SSE 协议
        - "anthropic" 使用 Anthropic( Claude ) 接口协议
        - "google" 使用 Google( Gemini ) 接口协议
        - "vertex" 使用 Vertex 接口协议，支持 [GCP 密钥](#vertex-key)
       
        第三方平台提供的 Claude 模型基本上都已转换为了 openai 兼容接口。
        
        如果不指定 protocol （或为 null 值）则会根据接口 URL 自动设置 protocol 。
        如果接口 URL 中包含单词 "anthropic" 则 protocol 的默认值也会设为 "anthropic" 

       
    - 自定义接口请求参数： <a id="extraParameters" href="#extraParameters">💡</a>

        如果在 web.rest.aiChat 的构造参数中添加可选的 extraParameters 字段则会设为 aiChat 对象 extraParameters 属性的初始值。aiChat.extraParameters 属性可选指定一个表对象（ table ），表中的键值对将作为`自定义参数`原样附加到所有请求参数中。

        >  AI 接口设置窗口（ web.rest.aiChat.settingForm ）的 `自定义参数` 对应的就是这里的 `extraParameters` 字段。

        可选使用 aiChat.extraUrlParameters 指定一个表，表中的键值对将作为 URL 参数添加到所有请求网址中。

        示例：

        ```aardio
        aiChat.extraParameters = {
            enable_thinking = true;
            thinking_budget = 1024;
        }
        ```

        注意 extraParameters 或 extraUrlParameters 里的字段名会保持原样发送给服务器，aardio 不会转换字段的命名风格。 

        自定义参数仅合并表遵守以下规则：
        - 如果 web.rest.aiChat 未指定同名字段则直接添加（不覆盖已存在的值）
        - 如果已存在同名字段，但两个字段值都是表则进行合并（不覆盖已存在的值），如果自定义的字段值是纯数组则调用 table.append 函数追加新数组。

        例如在 AA（aadio autos）中使用 GPT 5.6 ，如果在自定义参数中添加 `{"tools": [{"type": "web_search"}]}` 则会自动合并到 AA 原来的 tools 定义中。


        > 使用 `protocol="responses"` 时，`input`、`instructions`、`store`、`stream`、`background`、`conversation`、`previous_response_id` 属于当前 HTTP 事务的内部状态字段，由 `messages()` 内部构造，不能通过 extraParameters 覆盖。其中每次请求都固定使用 `store=false`，并在本地重放当前工作线程中的完整临时链。

    - 自定义 HTTP 请求头： 

        在 web.rest.aiChat 的构造参数中可以使用 extraHeaders 字段指定 extraHeaders 属性的初始值。
        也可以在创建 web.rest.aiChat 对象后再指定 extraHeaders 属性。

        web.rest.aiChat 的值应当是一个表对象，使用名值对指定 HTTP 请求头的一个或多个名值对。
        在 web.rest.aiChat.settingForm 创建的设置对话框中，可以使用 JSON 语法自定义 extraHeaders 的值。

    - 推理参数

        reasoning 字段用于指定推理选项，最常用的是使用 `reasoning.effort` 指定 `推理强度`，例如 `reasoning = { effort = "high" }` 。不同协议指定推理选项的实际参数名与格式并不一致，web.rest.aiChat 会根据选择的协议转换为合适的格式，对于 OpenAI 接口这 `reasoning.effort` 将被转换为请求参数中的 `reasoning_effort` 。

        更多细节请参考后面的 [设置推理强度](#reasoning)


2. 设置超时

    web.reat.aiChat 默认的超时设置为 `连接超时=15000,请求超时=30000,接收超时=600000`，
    这适合一般的非流式调用。

    对于流式调用，需要减少接收超时，示例：

    ```aardio    
    //连接超时=15000,请求超时=30000,接收超时=60000 单位毫秒
    aiChat.setTimeouts(15000,30000,60000)
    ```
    
    流式接口接收间隔较短，并且通常有心跳保活措施，接收超时不宜过大。
    但部分模型将加密思考内容仅发送摘要则间隔较慢，因此接收超时也不宜过小。
    因此建议将发送超时设为 60000 毫秒（60 秒）。

3. 创建聊天消息对列，保存对话上下文。

    ```aardio
    var msg = web.rest.aiChat.messages();

    //可调用 msg.system() 函数添加系统提示词。
    msg.system("你是桌面智能助手。");

    //添加用户提示词
    msg.prompt( "请输入问题:" );
    ```

    也可以用模拟 AI 的角色添加回复到消息对列

    ```aardio
    //模拟 AI 角色
    msg.assistant("请输入问题:" );
    ```

    这样自问自答的历史消息可以起到小样本学习的作用，让 AI 后面的回复更符合要求，小样本学习的效果有时候会非常好。


4. 向 AI 服务器发送请求，接收 AI 回复

    ```aardio
    var resp,err,errCode = aiChat.messages(msg,
        function(deltaText,reasoning,reserved){

            /*
            reasoning 可能为 null 或字符串。
            推理模型会首先通过 reasoning 参数输出推理过程（同样是增量字符串），同时 deltaText 为空字符串 "" 。
            reserved 是保留参数，必须忽略。
            */
		    if(reasoning) return;
            
            //回复完成则 deltaText 为 null , 参数为 null 时应保证幂等性（连续调用不重复执行/无副作用）
            console.writeText(deltaText)
            
            //如果需要输入增量输入到目标窗口
            //key.sendString(deltaText)
            
            //显示为屏幕汽泡提示，支持增量文本。
            //winex.tooltip.popupDelta(deltaText) 
        }
    );
    ```

   调用 ai.messages 就开始对话，如果参数 @2 指定了 SSE 流式回调函数，就自动切换到 SSE 流式调用( 打字效果, 逐步渐进式回复增量文本 )，服务器每次发送增量文本都会传入 deltaText 参数，当 AI 回复结束时 deltaText 为 null 。

   `aiChat.messages` 有两种不同的用法，返回值也有所不同：
   - 流式请求: 如果参数 @2 指定了 SSE 回调函数，则 `aiChat.messages` 调用成功时返回值 `resp` 为 true ，失败则为 false 或 null 值。
        
        流式请求时如果第一个返回值为 null，
        第二个返回值（err） 为字符串，并且第三个返回值（errCode）为 0 ，
        这表示 AI 回复未完成，但没有发生其他错误。
        如果 err 是空字符串 "" 则表示没有收到有效的回复正文（思考内容与工具调用除外，也就是只有过程没有结果）。
   
   - 非流式请求: 如果未指定 SSE 回调函数，则禁用 SSE 流式回复并直接获取最终结果。请求成功时返回值 `resp` 为解析`服务器回复 JSON 数据`的表对象。
   
        非流式回复返回的 `resp` 对象示例：

        ```json
        {
            "choices":[
                {
                    "finish_reason":"stop",
                    "message":{
                        "content":"AI 最终回复内容",
                        "role":"assistant"
                    }
                }
            ]
        }
        ```
       
        `choices[1].message.content` 为 AI 回复的最终内容，其他字段则因不同的接口可能会不一样（一般也用不上）。

   如果只需要非流式回复的正文，可以调用 `ai.getText(msg)`。此函数内部仍调用 `messages(msg)`，但能够自动兼容所有支持的协议，并直接提取与返回可见正文：

   ```aardio
   var text,err,errCode = ai.getText(msg);
   if(text !== null) print(text);
   ```

   如果请求失败则返回值 `resp` 为 `null` 或 `false` 等非真值，而返回值 `err` 包含可能的错误字符串。web.rest.aiChat 继承自 web.rest.client，所以也可以用 `aiChat.lastResponseError()` 获取错误对象（解析 JSON 格式错误信息得到的对象），用 `aiChat.lastResponseString()` 获取服务器的原始错误信息（字符串对象），或者用 `aiChat.lastStatusCode` 获取 HTTP 响应状态码。更多用法请参考 [使用 web.rest 客户端](client.html)

[本节的完整范例源代码](../../../../examples/AI/aiChat.html)

## 推理强度 <a id="reasoning" href="#reasoning">&#x23;</a>

主流大模型可选的推理强度如下：

- `auto` ⇒ `默认`（不指定思考强度 / 默认设置）
- `none`  ⇒ `不思考` （直接回复）
- `minimal`  ⇒`几乎不思考`（最弱推理强度）
- `low`  ⇒`浅思考`
- `medium`  ⇒`平衡`模式（中等推理强度）
- `high` ⇒`深度思考`
- `xhigh` ⇒`增强深度思考`
- `max`  ⇒ `最大强度`

具备深度推理能力的现代大模型通常都可以指定推理强度，但并非所有模型都支持以上全部设置值。个别模型会将一些推理强度映射到其他强度，也有一些模型仅支持思考开关而但不能自定义推理强度。一些第三方平台会自动转换这个参数以兼容不同模型，web.rest.aiChat 也会进行兼容性转换（并不保证支持所有模型）。

### 在调用 AI 接口的 aardio 代码中指定推理强度

```aardio
import web.rest.aiChat;
var aiChat = web.rest.aiChat({
    key = '这里指定 API 密钥';
    url = "这里指定大模型接口地址";
    model = "openai/gpt-5.6-sol"; 
    reasoning = {
        effort = "high"
    }
} )
```

web.rest.aiChat 会根据当前选择的接口协议自动转换参数。对于 OpenAI 接口， `reasoning.effort` 会被转换为转换为请求参数中的 `reasoning_effort` 。

> 个别模型可选指定 `reasoning.maxTokens` 以限制推理消耗的最大 tokens 。   但这种明确限制最大 token 数量的方式已逐渐被淘汰，现代模型已不建议使用。  

> 个别模型使用 `thinking` 字段设置思考选项，aardio 接受此参数但不会做任何转换，并将其直接发送给服务器。

### 在 AI 设置界面指定推理强度

aardio 编写的程序（例如 aardio autos）通常使用标准库 web.rest.aiChat.settingForm 创建 AI 接口设置接面。这个设置界面的 `推理强度` 选项会被转换为上述的 `reasoning.effort` 的字段。

<img src="images/reasoning_effort.jpg" width="458" heigh="208" alt="设置推理强度">

> 在 AI 设置界面的 API 配置右键菜单项点`复制分享链接`可获得 [ai+ 格式链接](../../../../specs/ai-plus-url-scheme.html)。在 aardio 代码编辑器右键菜单点击 `粘贴与更正` 可识别 ai+ 链接并自动生成调用 API 接口的示例代码。

`medium` 这个名词错误地暗示了它适合大多数任务，但这通常是一个误解。  
**对于 AA(aardio autos) 这类支持交错思考的智能体（Agent），推理强度至少要设为 `high` 或者更大的 `xhigh`，`max` 。** 

也可以通过 AI 设置对话框的 `自定义参数` 用 JSON 指定推理强度，例如 DeepSeek Pro 4 可以将 `自定义参数` 指定为:`{"reasoning_effort":"high","thinking":{"type":"enabled"}}`。这相当于在代码中修改 web.rest.aiChat 的 [extraParameters 参数](#extraParameters)，aardio 会将自定义参数直接发送给服务端（不会进行任何转换）。要使自定义 JSON 里的 reasoning_effort 生效，则必须将它上面的推理强度选项设为`auto` （也就是忽略这个选项）。 

### 选择合适的推理强度

如何选择：

1. 难度较高在相对聚焦的攻艰任务，建议设为最大推理强度 `max` 或 `xhigh`
2. 难度一般但相对发散的常规任务，推理强度建议设为 `high`
3. 简单并且需要快速响应的任务，可以选择更低的推理强度。中低强度通常并不适合智能体工具。

根据 AI 的表现调整推理强度：

1. 如果 AI 思考时草率粗心或者偷工减料，可尝试调高推理强度。
2. 如果 AI 原地磨洋工、拖拖拉拉，执行速度太慢；或者跑偏方向过度设计，抓不住重点（顾此失彼），在一些无意义的地方反复挖坑填坑；甚至因为思维链太长而不堪重负长时间卡住不动。这时候就很有可能是推理强度太高，可以调低试试。

一句话总结就是：强度越小越擅长做减法（更`懒`），强度越大越擅长做加法（更`努力`）。高强度推理消耗的 tokens 也会更多。

我们应当根据 AI 的表现与具体任务选择与调整合适的推理强度，具体情况具体对待。

使用最大推理强度时可以适度关注 AI 输出的思考内容，避免 AI 跑偏与过度设计。例如在 AA(aardio autos) 里可以`停止`思考过程，然后输入临时纠偏的用户提示词（仅注入交错思考），再点击`继续`，这种`人机合作`的模式通常会带来非常好的效果。 

## 三. 兼容接口

### 1. OpenAI 兼容接口。

web.rest.aiChat 默认使用  OpenAI 兼容接口。
基本上大部分大模型都使用 OpenAI 兼容接口，例如 DeepSeek 。

调用 OpenAI 流式接口示例：

```aardio
import console.int; 
console.showLoading(" Thinking "); 

//1. 创建 AI 客户端
//---------------------------------------------------------------------
import web.rest.aiChat;
var aiChat = web.rest.aiChat(
	key =   'YOUR_API_KEY';
	url = "https://api.deepseek.com/v1";
	model = "deepseek-v4-pro";
	temperature = 1;
    reasoning = {effort: "high"}; // "none" 关闭思考
	//maxTokens = 16384;
)

//2. 创建消息队列
//---------------------------------------------------------------------
var msg = web.rest.aiChat.messages();
msg.prompt( "请介绍你自己" );

//3. 第三步：发送请求。
var resp,err = aiChat.messages(msg,
	function(deltaText,reasoning){
			
		if(reasoning) {
			return console.writeColorText(reasoning,0xA);
		}
		
		//回复完成则 为 null 。
		console.writeText(deltaText) 
	}
);

console.error(err);
```

#### OpenAI GPT 5.6 服务端联网搜索工具 <a id="openai_web_search" href="#openai_web_search">&#x23;</a>


OpenAI GPT 5.6 提供了一些服务端工具，以调用搜索工具为例：

- 在 aardio 代码中只要在 web.rest.aiChat 的构造参数表中直接添加 `"tools": [{"type": "web_search"}]` 即可。使用 `tools` 字段即可以指定本地工具也可以指定服务端工具，参考 [调用本地函数](#function-calling)

    示例：

    ```aardio 
    var aiClient = web.rest.aiChat({
        key="API_KEY";
        model="gpt-5.6-sol";
        protocol="responses";
        reasoning={
            effort="xhigh"		
        };
        tools=[{"type":"web_search"}];
        url="https://api.openai.com/v1"	
    })
    ```

- 如果使用 aardio autos 等工具，可以在 AI 设置界面的`自定义参数`中指定 `{"tools": [{"type": "web_search"}]}` 

    即使这些 AI 工具已经有默认在请求参数中定义了 tools 字段，aardio 仍然可以自动合并 `自定义参数` 指定的 tools ，前提是 tools 的值必须是纯数组（也就是用`[]`构造数组）


#### OpenAI 缓存透传 <a id="prompt_cache_key" href="#prompt_cache_key">&#x23;</a>


OpenAI 支持自动前缀缓存，通常不需要额外设置参数。

但是，通过中转服务商请求 OpenAI 接口可能出现缓存功能失效，这是因为中转服务商可能轮换了不同的 key 发送请求。这会导致费用大幅增加。

解决方法是在自定义参数中显式指定  `prompt_cache_key` 字段。

示例：

```aardio 
var aiChat = web.rest.aiChat(
	key =  'API_KEY';
	url = "https://openrouter.ai/api/v1";
	model = "openai/gpt-5.6-terra";
	temperature = 1;
	reasoning = {effort = "high"};
    extraParameters  = {
		prompt_cache_key = "your_unique_session_id_12345"; 
	};
)
```

注意如果多用户共享相同的 prompt_cache_key 会被路由到固定的 GPU 机器，如果同一缓存键并发请求过多（通常超 15次/分钟），系统会触发溢出路由，从而降低缓存效果。因此应当避免多用户使用相同的 prompt_cache_key。

对于中转服务商，可以考虑对 API key 加盐然后计算 sha256 哈希作为 prompt_cache_key，例如：
`prompt_cache_key = crypt.sha256( config.key ++ io._exepath )`

GPT5.5 的提示缓存（Prompt Caching）是完全免费写入的，便 GPT 5.6 写缓存会收费。总的来说指定 prompt_cache_key 通常都会大幅降低成本。

> 注意 GPT5.6 已经废弃 `prompt_cache_retention` ，缓存时间建议保值默认即可。

#### OpenAI Responses API <a id="responses" href="#responses">&#x23;</a>

将构造参数 `protocol` 指定为 `"responses"` 即可使用 Responses API：

```aardio
import web.rest.aiChat;
var ai = web.rest.aiChat(
    key = 'YOUR_API_KEY';
    url = "https://api.openai.com/v1";
    model = "gpt-5";
    protocol = "responses";
    reasoning = {effort = "high"};
)

var msg = web.rest.aiChat.messages();
msg.system("你是桌面智能助手。");
msg.prompt("请介绍你自己");

var result,err = ai.messages(msg);
if(result) print(result.output_text);
```

Responses 分支支持普通 HTTP 请求与 HTTP SSE，不实现 WebSocket / Realtime API。每个请求固定发送 `store=false`，不使用 `previous_response_id`，而是完整重放本地消息。

在单次连续工具调用链中，服务端返回的原生 reasoning、`function_call` 以及本地生成的 `function_call_output` 会临时保留在调用方传入的工作消息副本中，以支持无状态续接和中止后继续。工具链正常结束后，界面或持久历史只应保存标准 user/assistant 可见消息；不要把这份临时原生链写入普通会话存档。

当配置了本地工具时，客户端会请求 `reasoning.encrypted_content` 并原样回放服务端返回的原生 output Items。工具回传请求仍会重新收集当前 system/developer 消息作为顶层 `instructions`，因此动态增加的系统提示词可在下一次请求生效。

### 2. Vertex / Gemini 接口 <a id="google" href="#google">&#x23;</a>


支持 OpenAI 兼容接口：

- AI Studio: https://generativelanguage.googleapis.com/v1beta/openai
- Vertex: https://aiplatform.googleapis.com/v1beta1/projects/{project_id}/locations/global/endpoints/openapi/

也可以支持 Google 自家的协议接口：

- AI Studio: https://generativelanguage.googleapis.com/v1beta
- Vertex: https://aiplatform.googleapis.com/v1beta1/projects/{project_id}/locations/global/publishers/google/

**Vertex 密钥设置：** <a id="vertex-key" href="#vertex-key">&#x23;</a>

使用 Vertex 接口时 web.rest.aiChat 构造参数表中的 key 字段需要指定 GCP 密钥数据。
> 打开「 Vertex AI 控制台 » 主菜单 » IAM 和管理 » 服务账号」 创建并下载 JSON 格式密钥 。

GCP 密钥数据应当是一个表对象或者 JSON 格式字符串（ 密钥的第一个字符必须是 `{` ）。  

GCP 密钥数据主要字段说明：

* `token_uri` - 必须指定为 URL,GCP 秘钥数据中自带。
* `client_email` - GCP 秘钥数据中自带。
* `private_key` - PEM 格式的私钥。
* `request_uri` - 如果存在这个字段，则以其值作为请求访问令牌的 URL，否则请求 `token_uri`。
* `project_id` - 如果存在这个字段，并且接口 URL 的域名为 `generativelanguage.googleapis.com` 
则会重新合成正确的接口 URL。
* `region` - 可选指定此字段用于合成新的接口 URL，不指定则默认为 "global"。 

如果 key 指定 GCP 密钥数据则 web.rest.aiChat 会自动获取 GCP 访问令牌，并且默认会跨线程缓存访问令牌以避免重复获取令牌。也可以自行调用标准库 web.rest.gcp.jwtBearerToken 提前获取访问令牌。

**自定义思考配置：** 

示例：

```aardio 
import web.rest.aiChat;
var aiChat = web.rest.aiChat(
	key =   "密钥";
	url = "https://generativelanguage.googleapis.com/v1beta/";
	model = "gemini-3-flash-preview";
	reasoning = {effort = "high"};
)
```

如果 reasoning.exclude 不为 true 则输出思考过程。 

**示例：**

例如用 aardio 向 AI Studio 接口 `https//:generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent?key=YOUR_API_KEY` 发送请求的代码如下：

```aardio
import web.rest.aiChat;

// 1. 创建 AI 客户端。
var aiChat = web.rest.aiChat(
    key = "YOUR_API_KEY"; 
    url = "https://generativelanguage.googleapis.com/v1beta"; 
    model = "gemini-3.6-flash"; 
	//proxy = "socks=127.0.0.1:1081"; //代理服务器 
	//protocol = "google"; //generativelanguage.googleapis.com/v1beta 默认使用 Google 协议
);

// 2. 创建消息队列
var msg = web.rest.aiChat.messages();
msg.prompt("你好,请用中文介绍一下你自己。");
 
// 3. 发送请求，如果没有提供第二个回调函数参数,则会禁用流式回复并等待服务器返回完整结果
var resp, err = aiChat.messages(msg); 
print(resp.candidates[1].content.parts[1].text); //Google 协议返回的数据结构与 OpenAI 不同
```

> Gemini 3.x 系列模型的 temperature 只能设为 1，不建议改动

### 3. Anthropic 接口

```aardio
import web.rest.aiChat;
var aiChat = web.rest.aiChat(    
    key = '密钥';
    url = "https://api.anthropic.com/v1";
    model = "claude-opus-4-8";
    protocol = "anthropic";//指定使用 Anthropic 接口协议，也就是 Claude 大模型官网接口
    reasoning = { // 不指定 reasoning 则使用默认值
        effort = "max"; //设为 "none" 关闭思考
        // exclude = true; //不显示思考过程
    }
)
```

如果接口网址包含 `anthropic` 也会自动切换为 Anthropic 协议（可以省略 `protocol` 字段）。

`reasoning.exclude` 默认值为 false ， 即使 Claude Opus 4.7+ 也一样。

Anthropic 官方接口可在 [自定义参数](#extraParameters) 中添加 `{"cache_control":{ "type":"ephemeral"}}` 以开启全局自动缓存。需要注意 Anthropic 官方接口写入缓存是收费的，缓存机制也有点坑。尤其是第三方中转站通常无法正常支持缓存，添加 `{"cache_control":{ "type":"ephemeral"}}` 不但不能节省费用，反而可能会导致费用增加。

### 4. OpenRouter 接口 <a id="openrouter" href="#openrouter">&#x23;</a>

OpenRouter 使用的是 OpenAI 接口，所以不需要指定 protocol。

OpenRouter 的思考模型可通过 reasoning 字段指定推理参数，示例：

```aardio 
var aiChat = web.rest.aiChat({
	key = "api-key";
	url = "https://openrouter.ai/api/v1";
	model ="z-ai/glm-5.2";
	temperature = 1;
	reasoning = { 
		effort: "high" //推理强度
		//exclude = true; //不回显思考过程
	};
    extraParameters = {
        provider={ //自定义模型供应商
            allow_fallbacks=false; //不允许降级到备份供应商
            only=["novita"]; //明确限定一个或多个供应商 Slug (在模型展示页点供应商链接取网址尾部的供应商标识符）
            quantizations=["fp8"] //量化精度限制		
        }	
    };
})
```

如果 OpenRouter 上同一模型不同供应商的价格与延迟有较大差别，
那么可以通过自定义参数 extraParameters.provider 指定供应商。

在 aardio autos 的 AI 设置对话框中，可在`自定义参数`设置项如下输入 JSON ：

```json 
{
    "provider":{
        "allow_fallbacks":false,
        "only":["novita"],
        "quantizations":[ "fp8"]
    }
}
```

这等价于在代码中指定 extraParameters 参数。

OpenRouter 可以在附加参数中指定 session_id 以优化缓存命中率。

例如:

```aardio 
aiClient.extraParameters = {
	session_id = "my-agent-session-2.1";
}
```

OpenRouter 会将 session_id 与用户与模型锁定，因此对于单用户+固定的系统提示词缓存优化，通常只需要指定简单的字符串。

OpenRouter 的 session_id 并不能代替大模型的缓存 key 参数 。
虽然 OpenRouter 实际可能做了某些适配和优化，但更稳的做法是添加这些参数。
OpenRouter 对于不支持的参数只会静默丢弃而不会报错，因此这是有利无弊的做法。
例如同时指定 GPT 的 [prompt_cache_key](#prompt_cache_key) 或者 Grok 的 HTTP 请求头 `x-grok-conv-id` 。

### 5. Ollama 接口

Ollama 本地模型在 aardio 或  ImTip 中的接口地址写以下任何一个都可以：

```txt
http://localhost:11434
http://localhost:11434/v1/
http://localhost:11434/api/
```

Ollama 只要填网址和模型名称，key不需要指定。

请参考： [自动部署本地 Ollama 模型](../../../../examples/AI/ollama.html)

### 6. 七牛云 AI 大模型推理接口

示例：

```aardio
var aiChat = web.rest.aiChat(
	key = '密钥';
    url = "https://api.qnaigc.com/v1"; 
    model = "z-ai/glm-5";
    temperature = 0.5;
    thinking = { type = "disabled" } 
    //protocol = "anthropic" //可选切换为 anthropic 协议
)
```

七牛云的接口有多个：
- `https://api.qnaigc.com/v1"`  兼容 OpenAI / Anthropic 协议
- `https://api.qnaigc.com/bypass/openai/v1` 无转换原厂接口（bypass）OpenAI 协议
- `https://api.qnaigc.com/bypass/anthropic/v1` 无转换原厂接口（bypass）Anthropic 协议
- `https://api.qnaigc.com/bypass/vertex/v1` 无转换原厂接口（bypass）Google Vertex（Gemini 模型） 协议
- `https://openai.qiniu.com/v1` OpenAI 协议

> bypass 接口通常更稳定，避免了不必要且可能带来兼容性问题的协议转换过程。默认 aardio 会自动识别接口网址中的 `anthropic`、`vertex` 关键词并转换为合适的协议（也可以使用参数中的 `protocol` 字段显式指定接口协议）。

七牛云接口部分模型支持用 reasoning.effort 或  thinking.type , thinking.budget_tokens 自定义思考模型或强度。或者直接用 `extraParameters.thinking` 或 `extraParameters.reasoning_effort` 指定附加参数也可以。


### 7. 智谱接口

示例：

```aardio 
var aiChat = web.rest.aiChat( {
	key = "密钥";
	url = "https://open.bigmodel.cn/api/paas/v4";
	model = "glm-5.2";
	temperature = 0.5;
	reasoning = {effort = "high"}; //推理强度
})	
```

GLM 5.2 已经支持通用的推理强度参数，
`reasoning.effort`（ OpenAI 接口的 reasoning_effort ）兼容 max xhigh high medium low minimal none 等参数。

GLM 在思考模式下并未像其他思考模型那样锁定或限制 temperature 参数， 但最好不要过大过小（一般建议 0.3~0.5）。

### 8. 魔搭接口

魔搭使用 OpenAI 接口，示例：

```aardio 
aiChat = web.rest.aiChat(    
    key =  "密钥";
	url = "https://api-inference.modelscope.cn/v1";
	model = "deepseek-ai/DeepSeek-V4-Pro";
	temperature = 1;
	reasoning:{
        "effort":"high"
    }
)	

// 可显式声明要记录的 HTTP 响应头
aiClient.responseHeaders = {
    "modelscope-ratelimit-requests-limit"=true; //用户当天限额
    "modelscope-ratelimit-requests-remaining"=true; //用户当天剩余调用次数
    "modelscope-ratelimit-model-requests-limit"=true; //模型当天限额
    "modelscope-ratelimit-model-requests-remaining"=true; //模型当天剩余调用次数
}
```

魔搭文档虽然有写`例如对于reasoning模型，调用的方式与标准 LLM 会有一些细微区别`，
但未写明到底有什么区别，这可能是指之前需要用 `extraParameters = {enable_thinking = true}` 开启思考模式。
但当前魔搭的 GLM-5.2，DeepSeek-V4-Pro 等多个模型，都实际兼容更通用的 reasoning.effort 参数（即 OpenAI 参数: reasoning_effort）。reasoning.effort 指定为 `none` 可以关闭思考，指定为  `high` 等参数可以打开思考。

### 9. 火山平台大模型与智能体接口

火山方舟平台大模型（豆包、DeepSeek 等）的接口以及智能体接口都兼容 OpenAI 接口。火山智能体的接口地址为 `https://ark.cn-beijing.volces.com/api/v3/bots`，大模型接口地址为 `https://ark.cn-beijing.volces.com/api/v3`，模型参数可以填模型 ID 或智能体应用 ID。

```aardio
import web.rest.aiChat;
var aiChat = web.rest.aiChat(    
    key = '密钥';
    url = "https://ark.cn-beijing.volces.com/api/v3";
    model = "doubao-seed-evolving";
    temperature = 1;
    reasoning = {effort="high"}; //自动转换为 reasoning_effort
    // thinking = {type="enabled"}; 
    // maxTokens = 32768; //方舟 Chat Completions API 自动转换为 max_completion_tokens
     protocol:"responses"
)

```

对于火山方舟的 `doubao-seed-evolving` 模型，请使用 `responses` 协议。
如果使用 `openai`协议（）

如调用火山智能体，将上例网址改为 `https://ark.cn-beijing.volces.com/api/v3/bots`，并将 model 改为智能体应用 ID。

### 10. 阿里云 AI 接口


示例：

```aardio
import web.rest.aiChat;
var aiChat = web.rest.aiChat(   
    key = '密钥';
    url = "https://dashscope.aliyuncs.com/compatible-mode/v1";
    model = "qwen-coder-plus";
)
```

## 四. AI 调用本地函数（ Function calling ） <a id="function-calling" href="#function-calling">&#x23;</a>

### 1. 交错思考（Interleaved Thinking） <a id="interleaved_thinking" href="#interleaved_thinking">&#x23;</a>

所谓交错思考指的是大模型在开启思考模式以后，一边思考（推理）一边调用工具，在一次用户对话中思考与调用工具可以交错执行多次。
实际上在一次用户对话过程中可能会向服务器自动回传多次请求。

需要明确几个概念：

1. 一轮用户对话：指用户向 AI 服务端发送用户提示词并发起对话请求，直到 AI 结束本轮对话。
2. 交错思考+工具调用：这是指在一轮对话内 AI 连续地、交错地执行思考并发起工具调用，Agent 容器执行工具并返回结果（由智能体容器自动向服务端发起请求并回传工具调用结果）。
3. 多轮对话：指在请求中包含之前的对话记录，在相同的上下文内进行连续的多轮对话。在 AI 设置中的最大 `上下文轮数` 指的就是上下文可以容纳的最大对话次数（这通常是一个近似值，因为执行环境可能会为一些自动插入的上下文预留位置）

在交错思考的过程中自动发起多次请求时需要回传思考内容（推理过程）,而在一轮用户对话（包含交错思考与工具调用自动发起的多次请求）结束后，通常应当丢弃这些思考内容（推理过程）。在多轮对话的上下文中仅保留 AI 回复的正文（不应包含推理过程），即使用户继续对话，上一次的思考过程也应当被丢弃（否则上下文会迅速膨胀，多轮对话难以正常继续下去）。

交错思考的实现并没有统一的标准，各家的实现都不一样，web.rest.aiChat 则尽可能地兼容了不同的实现。

> 一些模型旧版本的交错思考功能并不完善，尽量使用它们的最新版本以避坑

最佳实践是：对于使用 web.form.chat 等 AI 对话前端界面，建议由界面线程的 web.form.chat 保存对话记录。
而在工作线程中单独创建新的 web.rest.aiChat 发送对话请求。对于支持交错思考的接口，web.rest.aiChat 会自行维护对话记录，并在对话记录中保存推理过程（思考过程）。因为 aardio 的线程隔离特性，这些推理过程并不会被自动添加到界面线程（例好 web.form.chat 对象的 `chatMessages` 属性），在工作线程中我们可以手动调用 web.form.chat 的 assistant 方法存储一些必要的工具调用结果到界面线程。在一轮用户对话结束后退出工作线程，这些思考过程将被自然丢弃。

完整的实现请参考 [Autos 源码](https://www.aardio.com/zh-cn/docs/examples/AI/autos.html) 。Autos 的工具实际是由标准库 autos.tools.handlers(工具实现),autos.tools.schemas(工具定义) 提供。而 autos.tools.handlers 中几个关键的代码编辑工具则基于标准库 string.patch, string.editor 。

### 2. AI 用工具（本地函数）的步骤

> 注意不是所有大模型接口都支持 function calling 。如果服务端报错缺少 content 字段，这通常是因为接口不支持 function calling，只能处理包含 content 的普通消息。 

首先在创建 AI 客户端时，在参数中使用 tools 字段指定允许被 AI 调用（function calling）的本地函数。  
tools 应当指定一个数组，数组的每个成员指定一个函数定义，细节可参考调用的大模型相关文档。

示例：

```aardio
var aiChat = web.rest.aiChat(
	key = "密钥";
	url = "https://api.*****.net/v1";//接口地址
	model = "模型名称"; 
    temperature = 1; 
	tools = { //关键在于增加 tools 字段声明可以调用的本地函数，细节请参考 API 文档。
        {
            "type": "function",
            "function": {
                "name": "getWeather",
                "description": "获取给定地点的天气",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "地点的位置信息，比如北京"
                        },
                        "unit": {
                            "type": "string",
                            "enum": {
                                "摄氏度",
                                "华氏度"
                            }
                        }
                    },
                    "required": {
                        "location"
                    }
                }
            }
        }
    }
)

```
 
然后我们需要在 aiChat.external 表里定义允许 AI 调用的同名函数，与前面的 tools 里声明的函数名称与原型说明要匹配。

示例：

```aardio
//导出允许 AI 调用的函数
aiChat.external = {
	getWeather = function(args){ 
		
		/*
        要特别注意修 AI 可以看到你对 args 参数的改动。
        如果对 args 的修改会让 AI 困惑，则应使用 args = table.clone(args) 复制副本然后仅修改副本。
        */

		console.log("正在调用函数，参数：",args.location,args.unit)
		
		// 如果直接返回文本，可用自然语言描述清楚。
		return  args.location + "天气晴，24~30 度，以自然语言回复不要输出 JSON"

        // 也可以返回其他数据类型，例如对象或数组。
        return {
            // result,succeeded,error 这些常用的字段无需解释
            result = "返回值";
            succeeded = true;
            error = err;
            comment = "可选在这里添加说明"
        }
	} 
}
```

然后创建对话消息队列，示例如下：

```aardio
//单独 创建 AI 会话消息队列以保存聊天上下文。
var chatMsg = web.rest.aiChat.messages();

//添加用户提示词
chatMsg.prompt("杭州天气如何？" );
```

最后发送请求启动对话：

```aardio
console.showLoading(" Thinking "); 

//调用聊天接口。
var ok,err = ai.messages(chatMsg,console.writeText);
```

[完整版范例源码](../../../../examples/AI/function-calling.html)

## 五. AI 续写与补全应用

如果需要更好的效果，则建议在 AI 提示词中添加更多的信息，例如让 AI 知道目标进程的文件名，并要求 AI 根据不同的程序给出更合适的解答，完整示例请参考： [范例 - 超级热键调用 AI 大模型自动续写补全](../../../examples/AI/aiHotkey.html) 

aardio 基于上面的范例已经内置了 F1 键 AI 助手，运行效果：

![F1 键助手](http://imtip.aardio.com/screenshots/fim.gif)

利用 F1 键还可以在 aardio 中调用 AI 写其他编程语言的代码，例如写 Python 代码：

![F1 键助手写 Python 代码](http://www.aardio.com/zh-cn/docs/images/fim-py.gif)

在调用 AI 续写补全时，清晰的提示很重要。例如上面我们简明扼要地通过变量命名与注释让 AI 明确  pyCode 里放的是 Python 代码。在编码补全时，在清晰的注释提示后面补全有更好的效果。 注意用法，那么在 aardio 环境中调用 AI 写前端代码、Python 代码、 Go 语言的代码的效果会很好，利用 AI 可以更好地利用 aardio 在混合语言编程上的优势。

## 六. AI 搜索 <a id="search" href="#search">&#x23;</a>

如果是用于 aardio 编程的 AI 助手推荐使用 aardio 提供的 <a href="http://aardio.com/vip" >VIP 专属接口</a>，aardio 提供了专业版知识库，匹配速度更快，也更加智能与准确。

### 调用大模型服务端内置的搜索工具

请参考 [GPT 5.6 服务端联网搜索工具](#openai_web_search)

### 调用 Tavily 搜索接口 <a id="exa" href="#exa">&#x23;</a>

Tavily 搜索质量不错，而且只要注册账号就可以获取每月搜索 1000 次的免费额度，一般够用（响应比 Exa 慢）。

示例：

```aardio

//导入 Tavily 搜索接口
import web.rest.jsonClient;
var http = web.rest.jsonClient();
http.setAuthToken("接口密钥");
var tavily = http.api("https://api.tavily.com");

//搜索，不建议指定 include_raw_content 参数（ 返回的 raw_content 可能有乱码 ）.
var resp = tavily.search(
	query = "aardio 如何读写 JSON",
	max_results = 3, //限制返回结果数，默认值为 5。
	//topic = "news", //限定返回最新数据
    //time_range = "month", //搜索最近一个月发布或更新的内容
	include_domains = ["www.aardio.com"] //可选用这个字段限定搜索的域名数组
)

//创建对话消息队列
import web.rest.aiChat; 
var msg = web.rest.aiChat.messages();
 
//将搜索结果添加到系统提示词
msg.url(resp[["results"]])

//添加用户提示词
msg.prompt( "DeepSeek 有哪些成就" );
```

请参考[tavily 文档](https://docs.tavily.com/documentation/api-reference/endpoint/search)

### 调用 Exa 搜索接口 <a id="exa" href="#exa">&#x23;</a>

一般需要根据用户的最后一个提示词进行搜索，并将搜索结果添加到最后一个用户提示词之前。

```aardio
//导入 Exa 搜索接口
import web.rest.jsonClient; 
var exaClient = web.rest.jsonClient(); 
exaClient.setHeaders({ "x-api-key":"接口密钥"} )
var exa = exaClient.api("https://api.exa.ai/");

//搜索
var searchData,err = exa.search({
    query:"DeepSeek 有哪些成就", 
    contents={text= true}
    numResults:2,
    includeDomains:{"www.aardio.com"},//可以在指定网站内搜索
    type:"keyword" //一般 keyword 搜索就够了（价格低一些）
})

//创建对话消息队列
import web.rest.aiChat; 
var msg = web.rest.aiChat.messages();
 
//将搜索结果添加到系统提示词
msg.url(searchData[["results"]])

//添加用户提示词
msg.prompt( "DeepSeek 有哪些成就" );
```

exa.ai 的搜索质量不错。

### 调用博查搜索接口 <a id="bocha" href="#bocha">&#x23;</a>

```aardio
import web.rest.aiChat;
var msg = web.rest.aiChat.messages();

var bochaClient = web.rest.jsonClient(); 
bochaClient.setAuthToken("接口密钥");

//导入博查搜索接口
var bocha = bochaClient.api("https://api.bochaai.com/v1/{method}-search");

//搜索
var searchData,err = bocha.web({ 
    "query": "DeepSeek 最近有哪些新闻事件",
    "freshness": "noLimit",
    "answer": false,
    "stream": false,
    "count": 2; 
})

//添加到系统提示词
msg.url(searchData[["data"]][["webPages"]][["value"]])

msg.prompt( "DeepSeek 最近有哪些新闻事件" );
```

## 七. 在 HTTP 服务端开发 AI 中转接口

HTTP 服务端关键代码如下：

```aardio 
var apiKey = request.headers["authorization"]
if(apiKey) apiKey = string.match(authorization,"\S+\s+(\S+)");

if(!apiKey){
	response.errorStatus(401);
	return;
}

// ...... 其他代码省略

import web.rest.aiChat;

// 获取客户端请求的参数
var requestData = request.postJson()

var aiChat = web.rest.aiChat(    
    key =  'API_KEY'; //上游接口的 APK key
    url = "https://generativelanguage.googleapis.com/v1beta";
    model =  "gemini-3.6-flash";
    temperature = requestData.temperature;
    maxTokens = requestData.max_tokens; 
    tools = requestData.tools;
    reasoning = {
        effort = "high";
        maxTokens = -1;
    }
    protocol = "google";
)

var resp,err = aiChat.messages(requestData.messages,
function(deltaText,reasoning){ 
    
    if(!(deltaText || #reasoning) ){ 
        response.eventStream({data = {"DONE"}});
        return; 
    }   
        
    response.eventStream({
        data = {
            choices = {{
                delta = {
                    content = deltaText;
                    reasoning_content = reasoning;
                }
            }}
        }
    });  
});

if(err){
    response.error(err)
}
```