# aardio 调用 AI 大模型

## 一. 基本用法

[本节的完整范例源代码](../../../../example/AI/aiChat.html)

1. 创建 AI 客户端

    ```aardio
    import web.rest.aiChat;
    var aiClient = web.rest.aiChat({
        key = '这里指定 API 密钥';
        url = "这里指定大模型接口地址";
        model = "这里指定模型名称"; //llama.cpp 本地模型可以省略
        temperature = 0.1; //可选指定温度 
        maxTokens = 1024; //可选指定最大回复长度
        protocol = null; //可选指定 API 接口协议类型
        extraParameters = {}; //可选指定 extraParameters 属性值
   } )
   ```

   - 构造参数表字段说明：
    
        web.rest.aiChat 的构造参数只能指定上面列出的字段，并且都是按小驼峰风格命名。调用时会自动转换为 API 接口的对应字段名，例如 topP 会转换为 top_p , 而 maxTokens 字段会根据接口不同改转换字段名为 max_tokens 或者 max_completion_tokens。

        topP 字段是可选的，一般建议指定 temperature 的值而不是指定 topP 的值。请参考：[关于 temperature 参数](../../../../guide/ide/ai.html#temperature)

        > 支持思考的推理模型通常要求将 temperature 设为 1。
        > 最典型的就是 Gemini 3.x Flash 或者 Gemini 3.x Pro 必须设为 1,设为其他值生成质量会显著下降。
        > 这是因为思考类模型需要获取更多可能性以生成最优结果。

        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 接口协议，这是默认值
        - "anthropic" 使用 Anthropic( Claude )  接口协议。
        - "google" 使用 Googel( Gemini ) 接口协议
        - "vertex" 使用 Vertex 接口协议，支持 [GCP 密钥](#vertex-key)
       
        第三方平台提供的 Claude 模型基本上都已转换为了 openai 兼容接口。
        
        如果不指定 protocol （或为 null 值）则会根据接口 URL 自动设置 protocol 。
        如果接口 URL 中包含单词 "anthropic" 则 protocol 的默认值也会设为 "anthropic" 

       
    - 自定义接口请求参数： <a id="extraParameters" href="#extraParameters">💡</a>

        可选使用 aiClient.extraParameters 属性指定一个表，表中的键值对将作为参数添加到所有请求数据中。也可以在 web.rest.aiChat 的构造参数中添加 extraParameters 字段指定 aiClient.extraParameters 的初始值。

        可选使用 aiClient.extraUrlParameters 指定一个表，表中的键值对将作为 URL 参数添加到所有请求网址。

        示例：

        ```aardio
        aiClient.extraParameters = {
            enable_thinking = true;
            thinking_budget = 1024;
        }
        ```

        注意 extraParameters 或 extraUrlParameters 里的字段名会保持原样发送给服务器，aardio 不会转换字段的命名风格。 

        如果指定了 `extraBody` 字段，aardio 会将参数名转换为 `extra_body` 发送，个别接口支持这个字段。

    - 推理参数 <a id="reasoning" href="#reasoning">&#x23;</a>

        如果使用 aardio 标准库 web.rest.aiChat.settingForm 创建的标准 AI 接口设置对话框，   
        则“推理强度”默认可选择 `auto,none,minimal,low,medium,high,xhigh,max` 等值。

        >  aardio ，AA( aardio autos)， ImTip 的 AI 设置都是基于 web.rest.aiChat.settingForm

        * 多数具备深度推理能力的大模型可指定推理强度，但可能并不支持以上所有的设置值。
            
            不同厂商的推理参数以及参数名称可能略有不同，aardio 将会进行兼容性转换。
            推理强度如果为 `none` 则 aardio 尝试转换为关闭思考模式的设置（仅个别模型支持）或者不指定推理强度。

        * 推理强度越高则 AI 会进行更深入的思考，消耗的 tokens 也会更多

            将推理强度调低会加快速度减少 token 消耗。推理强度太低或接近不思考时通常会导致 AI 能力严重下降。
            但要注意的是：并非所有大模型将推理强度设为最大能力就一定是最强的，有时候更深的思考会导致 AI “钻牛角尖”（想得太多）甚至产生幻觉并降低整体效率，甚至是过多的挖坑填坑迷失方向。

            深层推理（高强度思维链）极其擅长“边界清晰、逻辑链条长、需要严密推导”的 **深度问题**（如复杂算法、疑难Bug排查），但是需要头脑风暴、创意发散、或者范围极其宽泛的 **广度问题**，过度推理往往会限制模型的思维，甚至强行给无关紧要的信息建立逻辑关联。
            
            对于 AA( aardio autos ) 这样的工具型智能体（Autonomous Agent），GPT 5+、Gemini 3+ 等模型建议设为 `high` 模式，GPT 5+ 建议仅在遇到 `high` 模式无法解决的难点，且问题相对聚焦时临时切换到 `xhigh` 模式尝试是否有更好的效果。
            
            对于 DeepSeek 4 模型，在工程实测中，它在低推理强度下仍然存在基座响应的局限性。为了在 AA（aardio autos）这类复杂的 Agent 场景中保障任务的成功率，我们不得不采取**“以时间换质量”**的妥协策略——建议使用 DeepSeek Pro 并将推理强度设为 `max` 模式。需要注意的是，高强度思考带来的弊端依然存在。

            Gemini 的 high 模式实际是自动模式，可以智能地自动调整推理强度，对于 Gemini 来说 high 指的是推理强度上限。
            
            Gemini 3.x Flash 支持 `minimal`、`low`、`medium"` 和 `high`，建议设为 `medium`（平衡的思维能力，可处理大多数任务）。 如果不指定则会使用默认值 `minimal`（接近“不思考”），`minimal` 的速度最快但能力最弱，生成内容比较粗糙。如果追求速度可以指定为 `low`，`low` 模式有一定思考能力适合处理简单任务，而且比 `medium` 模式快。 `high`模式则可以最大限度地提高推理深度，但这是按需调整的动态模式，对于简单任务仍会快速回复。
            
            Gemini 3.x Pro 仅支持 `low`、`medium"` 或 `high` 模式，默认值 `high`。

        设置界面上的推理强度会转换为 web.rest.aiChat 的 reasoning.effort 参数。  
        在界面上选择 `auto` 时将不会指定 reasoning.effort 的值（保持默认）。

       >  web.rest.aiChat.settingForm 设置对话框也可以在 `自定义参数` 里用 JSON 指定推理参数，例如 DeepSeek 4 可以指定为:`{"reasoning_effort":"high","thinking":{"type":"enabled"}}`。这相当于在代码中修改 web.rest.aiChat 的 [extraParameters 参数](#extraParameters)，aardio 会将自定义参数直接发送给服务端（不会进行任何转换）。
        
        在代码中指定推理强度的示例：

        ```aardio
        import web.rest.aiChat;
        var aiClient = web.rest.aiChat({
                key = '这里指定 API 密钥';
                url = "这里指定大模型接口地址";
                model = "gemini-3.5-flash"; 
                maxTokens = 2048,//可选指定最大回复长度
                reasoning = {
                    effort = "medium"
                }
        } )
        ```

        个别模型可选指定 `reasoning.maxTokens` 以限制推理消耗的最大 tokens 。   
        但这种明确限制最大 token 数量的方式已逐渐被淘汰，现代模型已不建议使用。  

        一些推理模型使用 `thinking` 字段设置思考选项，aardio 接受此参数但不会做任何转换，并将其直接发送给服务器。


2. 创建聊天消息对列，保存对话上下文。

    ```aardio
    var msg = web.rest.aiChat.message();

    //可调用 msg.system() 函数添加系统提示词。
    msg.system("你是桌面智能助手。");

    //添加用户提示词
    msg.prompt( "请输入问题:" );
    ```

    也可以用模拟 AI 的角色添加回复到消息对列

    ```aardio
    //模拟 AI 角色
    msg.assistant("请输入问题:" );
    ```

    这样自问自答的历史消息可以起到小样本学习的作用，让 AI 后面的回复更符合要求，小样本学习的效果有时候会非常好。


3. 向 AI 服务器发送请求，接收 AI 回复

    ```aardio
    var resp,err = aiClient.messages(msg,
        function(deltaText,reasoning,reasoningDetailsType){

            /*
            reasoning 可能为 null 或字符串。
            推理模型会首先通过 reasoning 参数输出推理过程（同样是增量字符串），同时 deltaText 为空字符串 "" 。
            reasoningDetailsType 默认为 null，如果 reasoning 来自 reasoning_details，则 reasoningDetailsType 的值为 "text" 或 "summary" 之一。范例代码中通常会省略 reasoningDetailsType 参数，一般不必处理。
            在工具调用后 reasoning_details 会自动回传到服务器。
            */
		    if(reasoning) return;
            
            //回复完成则 为 null 。
            console.writeText(deltaText)
            
            //如果需要输入增量输入到目标窗口
            //key.sendString(deltaText)
            
            //显示为屏幕汽泡提示，支持增量文本。
            //winex.tooltip.popupDelta(deltaText) 
        }
    );
    ```

   调用 ai.messages 就开始对话，如果参数 @2 指定了 SSE 流式回调函数，就自动切换到 SSE 流式调用( 打字效果, 逐步渐进式回复增量文本 )，服务器每次发送增量文本都会传入 deltaText 参数，当 AI 回复结束时 deltaText 为 null 。

   `aiClient.messages` 有两种不同的用法，返回值也有所不同：
   - 流式请求: 如果参数 @2 指定了 SSE 回调函数，则 `aiClient.messages` 调用成功时返回值 `resp` 为 true ，失败则为 false 或 null 值。
   - 非流式请求: 如果未指定 SSE 回调函数，则禁用 SSE 流式回复并直接获取最终结果。请求成功时返回值 `resp` 为解析`服务器回复 JSON 数据`的表对象。
   
        非流式回复返回的 `resp` 对象示例：

        ```aardio
        {
            "choices":[
                {
                    "finish_reason":"stop",
                    "message":{
                        "content":"AI 最终回复内容",
                        "role":"assistant"
                    }
                }
            ]
        }
        ```
       
        `choices[1].message.content` 为 AI 回复的最终内容，其他字段则因不同的接口可能会不一样（一般也用不上）。

   如果请求失败则返回值 `resp` 为 `null` 或 `false` 等非真值，而返回值 `er`r 包含可能的错误字符串。web.rest.aiChat 继承自 web.rest.client，所以也可以用 `aiClient.lastResponseError()` 获取错误对象（解析 JSON 格式错误信息得到的对象），用 `aiClient.lastResponseString()` 获取服务器的原始错误信息（字符串对象），或者用 `aiClient.lastStatusCode` 获取 HTTP 响应状态码。更多用法请参考 [使用 web.rest 客户端](client.html)

[本节的完整范例源代码](../../../../example/AI/aiChat.html)

## 二. 兼容接口

### 1. OpenAI 兼容接口。

web.rest.aiChat 默认使用  OpenAI 兼容接口。
基本上大部分大模型都使用 OpenAI 兼容接口，例如 DeepSeek 。

调用 OpenAI 流式接口示例：

```aardio
import console.int; 
console.showLoading(" Thinking "); 

//1. 创建 AI 客户端
//---------------------------------------------------------------------
import web.rest.aiChat;
var aiClient = web.rest.aiChat(
	key =   'YOUR_API_KEY';
	url = "https://api.deepseek.com/v1";
	model = "deepseek-v4-pro";
	temperature = 1;
    reasoning = {effort: "high"}; // "none" 关闭思考
	//maxTokens = 4096;
)

//2. 创建消息队列
//---------------------------------------------------------------------
var msg = web.rest.aiChat.message();
msg.prompt( "请介绍你自己" );

//3. 第三步：发送请求。
var resp,err = aiClient.messages(msg,
	function(deltaText,reasoning){
			
		if(reasoning) {
			return console.writeColorText(reasoning,0xA);
		}
		
		//回复完成则 为 null 。
		console.writeText(deltaText) 
	}
);

console.error(err);
```

OpenAI 的 GPT5.x 系列通常不会主动输出思考与调用工具的过程，可以在系统提示词中添加内容：`在深度思考与每次调用工具前要输出 1-2 句可显示的“前导语”（Preamble），告知用户你接下来你准备干什么。`

#### OpenAI 缓存透传

OpenAI 支持自动前缀缓存，通常不需要额外设置参数。

但是，通过中转服务商请求 OpenAI 接口可能出现缓存功能失效，这是因为中转服务商可能轮换了不同的 key 发送请求。这会导致费用大幅增加。

解决方法是在自定义参数中显式指定  `prompt_cache_key` 与 `prompt_cache_retention` 字段。

示例：

```aardio 
var aiClient = web.rest.aiChat(
	key =  'API_KEY';
	url = "https://api.fenno.ai/v1";
	model = "gpt-5.5";
	temperature = 0.2;
	reasoning = {effort = "high"};
    extraParameters  = {
		prompt_cache_key = "your_unique_session_id_12345";
		prompt_cache_retention =  "24h";
	};
)
```

注意如果多用户共享相同的 prompt_cache_key 会被路由到固定的 GPU 机器，如果同一缓存键并发请求过多（通常超 15次/分钟），系统会触发溢出路由，从而降低缓存效果。因此应当避免多用户使用相同的 prompt_cache_key。

对于中转服务商，可以考虑对 API key 加盐然后计算 sha256 哈希作为 prompt_cache_key，例如：
`prompt_cache_key = crypt.sha256( config.key ++ io._exepath )`

OpenAI 的提示缓存（Prompt Caching）是完全免费写入的，因此将 prompt_cache_retention 设为 "24h" 不会增加额外费用，反而可大幅降低成本与延迟。

### 2. Vertex / Gemini 接口

支持 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 aiClient = web.rest.aiChat(
	key =   "密钥";
	url = "https://generativelanguage.googleapis.com/v1beta/";
	model = "gemini-3-flash-preview"
	reasoning = {effort = "high"}
)
```

如果 reasoning.exclude 不为 true 则输出思考过程。  

Gemini 2.5 可使用 reasoning.maxTokens 设置推理时允许消耗的 tokens 上限，设 为 0 则关闭思考，设为 -1 则按需动态设置。
Gemini 3.x 则应使用  reasoning.effort 替代 reasoning.maxTokens 。

**示例：**

例如用 aardio 向 AI Studio 接口 `https//:generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=YOUR_API_KEY` 发送请求的代码如下：

```aardio
import web.rest.aiChat;

// 1. 创建 AI 客户端。
var aiClient = web.rest.aiChat(
    key = "YOUR_API_KEY"; 
    url = "https://generativelanguage.googleapis.com/v1beta"; 
    model = "gemini-3.5-flash"; 
	//proxy = "socks=127.0.0.1:1081"; //代理服务器 
	//protocol = "google"; //generativelanguage.googleapis.com/v1beta 默认使用 Google 协议
);

// 2. 创建消息队列
var msg = web.rest.aiChat.message();
msg.prompt("你好,请用中文介绍一下你自己。");
 
// 3. 发送请求，如果没有提供第二个回调函数参数,则会禁用流式回复并等待服务器返回完整结果
var resp, err = aiClient.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 aiClient = web.rest.aiChat(    
    key = '密钥';
    url = "https://api.deepseek.com/anthropic";
    model = "deepseek-v4-flash";
    protocol = "anthropic";//指定使用 Anthropic 接口协议，也就是 Claude 大模型官网接口
    reasoning = { // 不指定 reasoning 则使用默认值
        effort = "max"; //设为 "none" 关闭思考
    }
)
```

如果接口网址包含 `anthropic` 也会自动切换为 Anthropic 协议（可以省略 `protocol` 字段）。

### 4. Ollama 接口

Ollama 本地模型在 aardio 或  ImTip 中的接口地址写以下任何一个都可以：

```txt
http://localhost:11434
http://localhost:11434/v1/
http://localhost:11434/api/
```

Ollama 只要填网址和模型名称，key不需要指定。

请参考： [自动部署本地 Ollama 模型](../../../../example/AI/ollama.html)

### 5. OpenRouter 接口

OpenRouter 使用的是 OpenAI 接口，所以不需要指定 protocol。

OpenRouter 的思考模型可通过 reasoning 字段指定推理参数，示例：

```aardio 
var aiClient = web.rest.aiChat({
	key = "api-key";
	url = "https://openrouter.ai/api/v1";
	model ="x-ai/grok-code-fast-1";
	temperature = 0.1;
	reasoning = { 
		effort: "high"
		//exclude = true;
	}
})
```

如果指定了 `exclude = true` 则不会回显思考过程，一些模型通过 reasoning.effort 指定思考强度（例如 grok-code-fast-1 ）, 一些模型则可以通过 reasoning.maxTokens 控制思考强度。

### 6. 魔搭接口

魔搭使用 OpenAI 接口，示例：

```aardio 
aiClient = web.rest.aiChat(    
    key =  "密钥";
	url = "https://api-inference.modelscope.cn/v1";
	model = "deepseek-ai/DeepSeek-V3.1";
	temperature = 0.1;
	extraParameters = {
		enable_thinking = true;
	};
)	
```

使用 extraParameters.enable_thinking 开始思考模式。

### 7. 七牛云 AI 大模型推理接口

示例：

```aardio
var aiClient = 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 协议

七牛云接口部分模型支持用 reasoning.effort 或  thinking.type , thinking.budget_tokens 自定义思考模型或强度。或者直接用 `extraParameters.thinking` 或 `extraParameters.reasoning_effort` 指定附加参数也可以。


### 8. 智谱接口

示例：

```aardio 
var aiClient = web.rest.aiChat( {
	key = "密钥";
	url = "https://open.bigmodel.cn/api/paas/v4";
	model = "glm-5.1";
	temperature = 0.5;
	thinking = { type = "disabled" } 
})	
```

参数指定 `thinking = { type = "disabled" } ` 关闭思考。  
如果不指定 `thinking` 字段则默认开启思考（或指定为  `thinking = { type = "enabled" } ` 显式启用思考）。

> 智谱 GLM 5.1 的 temperature 参数不能设置得太小，例如设为 0.1 能力会显著退化


### 9. 火山平台大模型与智能体接口

火山方舟平台大模型（豆包、DeeepSeek 等）的接口以及智能体接口都兼容 OpenAI 接口。火山智能体的的接口地址为 `https://ark.cn-beijing.volces.com/api/v3/bots`，大模型接口地址为 `https://ark.cn-beijing.volces.com/api/v3`， 模型 ID 参数可以填模型 ID 也可以填智能体应用的 ID。
示例：

```aardio
import web.rest.aiChat;
var aiClient = web.rest.aiChat(    
    key = '密钥';
    url = "https://ark.cn-beijing.volces.com/api/v3/bots"; //如不是智能体就去掉 "bots"
    model = "bot-20250115093718-r9gcj"; //模型或智能体 ID
    temperature = 0.1; //温度 
)
```

### 10. 阿里云 AI 模型与智能体接口

使用 web.rest.aiChat 可以兼容阿里通义千问的大模型接口以及智能体接口。

调用阿里大模型与智能体，API 接口网址只要写 `https://dashscope.aliyuncs.com` 就可以，然后 model 参数写模型 ID 或者智能体应用 ID。当然你也可以直接写阿里提供的接口网址。

```aardio
import web.rest.aiChat;
var aiClient = web.rest.aiChat(   
    key = '密钥';
    url = "https://dashscope.aliyuncs.com";
    model = "qwen-coder-plus"; //这里写模型 ID 或者智能体应用 ID，aardio 会自动兼容
    temperature = 0.1;
    maxTokens = 1024,
)
```


## 三. AI 调用本地函数（ Function calling ） <a id="function-calling" href="#function-calling">&#x23;</a>

### 1. 交错思考（Interleaved Thinking） <a id="interleaved_thinking" href="#interleaved_thinking">&#x23;</a>

所谓交错思考指的是大模型在开启思考模式以后，一边思考（推理）一边调用工具，在一次用户对话中思考与调用工具可以交错执行多次。
实际上在一次用户对话过程中会向服务器回传多次请求，而在多次请求时需要回传思考内容（推理过程）,而在一次用户对话结束后，通常应当丢弃这些思考内容（推理过程），在对话历史中仅保留 AI 回复的正文，即使用户继续对话上一次的思考过程通常会被丢弃。

而交错思考的实现并没有统一的标准，各家的实现都不一样，web.rest.aiChat 则尽可能地兼容了不同的实现。

最佳实践是：对于使用 web.form.chat 等 AI 对话前端界面，建议由界面线程的 web.form.chat 保存对话记录。
而在工作线程中单独创建新的 web.rest.aiChat 发送对话请求。对于支持交错思考的接口，web.rest.aiChat 会自行维护对话记录，并在对话记录中保存推理过程（思考过程）。因为 aardio 的线程隔离特性，这些推理过程并不会被自动添加到界面线程（例好 web.form.chat 对象的 `chatMessage` 属性），在工作线程中我们可以手动调用 web.form.chat 的 assistant 方法存储一些必要的工具调用结果到界面线程。在一轮用户对话结束后退出工作线程，这些思考过程将被自然丢弃。

完整的实现请参考 [Autos 源码](https://www.aardio.com/zh-cn/doc/example/AI/autos.html)

### 2. AI 用工具（本地函数）的步骤

> 注意不是所有大模型接口都支持 function calling 。如果服务端报错缺少 content 字段，这通常是因为接口不支持 function calling，只能处理包含 content 的普通消息。 

首先在创建 AI 客户端时，在参数中使用 tools 字段指定允许被 AI 调用（function calling）的本地函数。  
tools 应当指定一个数组，数组的每个成员指定一个函数定义，细节可参考调用的大模型相关文档。

示例：

```aardio
var aiClient = web.rest.aiChat(
	key = "密钥";
	url = "https://api.*****.net/v1";//接口地址
	model = "模型名称"; 
    temperature = 0.5; 
	tools = { //关键在于增加 tools 字段声明可以调用的本地函数，细节请参考 API 文档。
        {
            "type": "function",
            "function": {
                "name": "getWeather",
                "description": "获取给定地点的天气",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "地点的位置信息，比如北京"
                        },
                        "unit": {
                            "type": "string",
                            "enum": {
                                "摄氏度",
                                "华氏度"
                            }
                        }
                    },
                    "required": {
                        "location"
                    }
                }
            }
        }
    }
)

```
 
然后我们需要在 aiClient.external 表里定义允许 AI 调用的同名函数，与前面的 tools 里声明的函数名称与原型说明要匹配。

示例：

```aardio
//导出允许 AI 调用的函数
aiClient.external = {
	getWeather = function(args){ 
		
		//如果重复调用相同的函数，是因为模型实际并不支持 function calling
		console.log("正在调用函数，参数：",args.location,args.unit)
		
		//尽量用自然语言描述清楚
		return  args.location + "天气晴，24~30 度，以自然语言回复不要输出 JSON"
	} 
}
```

然后创建对话消息队列，示例如下：

```aardio
//单独 创建 AI 会话消息队列以保存聊天上下文。
var chatMsg = web.rest.aiChat.message();

//添加用户提示词
chatMsg.prompt("杭州天气如何？" );
```

最后发送请求启动对话：

```aardio
console.showLoading(" Thinking "); 

//调用聊天接口。
var ok,err = ai.messages(chatMsg,console.writeText);
```

[完整版范例源码](../../../../example/AI/function-calling.html)

## 四. AI 续写与补全应用

如果需要更好的效果，则建议在 AI 提示词中添加更多的信息，例如让 AI 知道目标进程的文件名，并要求 AI 根据不同的程序给出更合适的解答，完整示例请参考： [范例 - 超级热键调用 AI 大模型自动续写补全](../../../example/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/doc/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 提供了专业版知识库，匹配速度更快，也更加智能与准确。

### 调用 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.message();
 
//将搜索结果添加到系统提示词
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.message();
 
//将搜索结果添加到系统提示词
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.message();

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 aiClient = web.rest.aiChat(    
    key =  'API_KEY'; //上游接口的 APK key
    url = "https://generativelanguage.googleapis.com/v1beta";
    model =  "gemini-3.5-flash";
    temperature = requestData.temperature;
    maxTokens = requestData.max_tokens; 
    tools = requestData.tools;
    reasoning = {
        effort = "high";
        maxTokens = -1;
    }
    protocol = "google";
)

var resp,err = aiClient.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)
}
```