This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch book-fight
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git

commit b24d48156a9a118f429ce3dcfe8fb7424053d8ca
Author: alexstocks <[email protected]>
AuthorDate: Thu Sep 4 18:17:10 2025 +0800

    update book-flight
---
 book-flight-ai-agent/.env.example                  |   2 +
 book-flight-ai-agent/go-client/frontend/main.go    |   7 +
 .../go-client/frontend/static/script.js            | 139 ++++++++++++++---
 .../go-client/frontend/static/style.css            |   3 +-
 book-flight-ai-agent/go-server/agents/cot_agent.go |  30 +++-
 book-flight-ai-agent/go-server/cmd/server.go       |  18 ++-
 book-flight-ai-agent/go-server/conf/config.go      |   2 +-
 .../go-server/model/bailian/bailian.go             | 169 +++++++++++++++++++++
 .../go-server/model/bailian/options.go             |  23 +++
 .../go-server/model/ollama/ollama.go               |  28 +++-
 10 files changed, 380 insertions(+), 41 deletions(-)

diff --git a/book-flight-ai-agent/.env.example 
b/book-flight-ai-agent/.env.example
index ded3ae3a..9c4b902f 100644
--- a/book-flight-ai-agent/.env.example
+++ b/book-flight-ai-agent/.env.example
@@ -23,6 +23,8 @@ LLM_API_KEY = "sk-..."
 # Client Settings
 CLIENT_HOST = "tri://127.0.0.1"
 CLIENT_PORT = 20000
+# Web/Task 超时配置(单位:秒),默认 120 秒(2分钟),可根据需求调整
+TIMEOUT_SECONDS = 120
 
 # Web Settings
 WEB_PORT = 8080
diff --git a/book-flight-ai-agent/go-client/frontend/main.go 
b/book-flight-ai-agent/go-client/frontend/main.go
index 5e3fd5b7..788dc1c7 100644
--- a/book-flight-ai-agent/go-client/frontend/main.go
+++ b/book-flight-ai-agent/go-client/frontend/main.go
@@ -80,6 +80,13 @@ func main() {
                        "OllamaModel":   cfgEnv.Model,
                })
        })
+       // 新增配置接口:供前端获取超时等配置
+       r.GET("/api/config", func(c *gin.Context) {
+               c.JSON(http.StatusOK, gin.H{
+                       "TIMEOUT_SECONDS": cfgEnv.TimeOut, // 传递后端解析的超时时间
+                       "WEB_PORT":        cfgEnv.PortWeb,
+               })
+       })
        r.POST("/api/chat", h.Chat)
        r.POST("/api/context/new", h.NewContext)
        r.GET("/api/context/list", h.ListContexts)
diff --git a/book-flight-ai-agent/go-client/frontend/static/script.js 
b/book-flight-ai-agent/go-client/frontend/static/script.js
index 678c26ff..f10786bc 100644
--- a/book-flight-ai-agent/go-client/frontend/static/script.js
+++ b/book-flight-ai-agent/go-client/frontend/static/script.js
@@ -11,6 +11,35 @@ const inputInitHeight = chatInput.scrollHeight;
 let fileBlobArr = [];
 let fileArr = [];
 
+// 1. 页面初始化时请求后端配置(新增)
+async function loadConfig() {
+    try {
+        const res = await fetch("/api/config"); // 后端新增接口返回配置
+        window.CONFIG = await res.json();
+        // 统一超时字段名:使用与后端一致的 TIMEOUT_SECONDS(原 TIME_OUT_SECOND 废弃)
+        window.CONFIG.TIMEOUT_MS = window.CONFIG.TIMEOUT_SECONDS * 1000; // 
转为毫秒(方便定时器使用)
+    } catch (err) {
+        console.error("加载配置失败,使用默认超时(2分钟)", err);
+        window.CONFIG = window.CONFIG || {};
+        window.CONFIG.TIMEOUT_SECONDS = 120;
+        window.CONFIG.TIMEOUT_MS = 120 * 1000;
+    }
+}
+
+// 2. 页面加载完成后执行配置加载
+window.onload = async () => {
+    // 确保window.CONFIG已经存在,如果不存在则初始化
+    if (!window.CONFIG) {
+        await loadConfig();
+    } else {
+        // 确保window.CONFIG有TIMEOUT_MS属性
+        if (!window.CONFIG.TIMEOUT_MS && window.CONFIG.TIME_OUT_SECOND) {
+            window.CONFIG.TIMEOUT_MS = window.CONFIG.TIME_OUT_SECOND;
+        }
+    }
+    // 其他初始化逻辑(如绑定按钮事件)
+};
+
 const createChatLi = (content, className, targetBox = chatbox) => {
     const chatLi = document.createElement("li");
     chatLi.classList.add("chat", `${className}`);
@@ -64,17 +93,40 @@ const handleChat = () => {
     const incomingChatLi = createChatLi("Thinking...", "incoming", chatbox);
     const incomingRecordLi = createChatLi("Thinking...", "incoming", 
recordbox); // Add to recordbox
 
-    // timeout
-    const TIMEOUT_MS = CONFIG.TIME_OUT_SECOND;
-    let isTimeout = false;
-    const timeoutId = setTimeout(() => {
-        isTimeout = true;
-        incomingRecordLi.querySelector("p").textContent = "Request timed out. 
Please try again.";
-    }, TIMEOUT_MS);
 
-    // send request
+    // 超时逻辑优化
+    let timeoutId;
+    const startTimeout = () => {
+        // 清除已有定时器(避免重复)
+        if (timeoutId) clearTimeout(timeoutId);
+        // 启动新定时器(使用同步后的 window.CONFIG.TIMEOUT_MS)
+        timeoutId = setTimeout(() => {
+            const timeoutMsg = `
+        <div>
+          <p>请求超时(当前超时时间:${window.CONFIG.TIMEOUT_SECONDS || 
window.CONFIG.TIME_OUT_SECOND/1000}秒)</p>
+          <button class="retry-btn" style="margin-top:8px;padding:4px 
8px;">点击重试</button>
+        </div>
+      `;
+            // 更新超时提示(带重试按钮)
+            incomingChatLi.querySelector("p").innerHTML = timeoutMsg;
+            incomingRecordLi.querySelector("p").textContent = 
`请求超时(${window.CONFIG.TIMEOUT_SECONDS || window.CONFIG.TIME_OUT_SECOND/1000}秒)`;
+            // 绑定重试事件(复用原有 generateResponse 逻辑)
+            
incomingChatLi.querySelector(".retry-btn").addEventListener("click", () => {
+                incomingChatLi.querySelector("p").textContent = "Thinking...";
+                incomingRecordLi.querySelector("p").textContent = 
"Thinking...";
+                generateResponse(incomingChatLi, incomingRecordLi, () => {
+                    clearTimeout(timeoutId); // 重试成功后清除超时
+                });
+            });
+        }, window.CONFIG.TIMEOUT_MS || window.CONFIG.TIME_OUT_SECOND);
+    };
+
+    // 启动超时定时器
+    startTimeout();
+
+    // 发送请求(原有逻辑,补充超时清除)
     generateResponse(incomingChatLi, incomingRecordLi, () => {
-        if (!isTimeout) clearTimeout(timeoutId);
+        clearTimeout(timeoutId); // 请求成功/失败时清除超时
     });
 }
 
@@ -127,6 +179,24 @@ const generateResponse = (chatElement, recordElement, 
callback) => {
                                     accumulatedChatResponse += data.content;
                                     chatMessageElement.innerHTML = 
marked.parse(styleMatch(accumulatedChatResponse)); // Render Markdown
                                     chatbox.scrollTo(0, chatbox.scrollHeight);
+                                    
+                                    // 检查content中是否包含航班信息
+                                    try {
+                                        // 尝试解析content中的JSON数据
+                                        if 
(data.content.includes("flight_number")) {
+                                            // 提取JSON部分
+                                            const jsonMatch = 
data.content.match(/\{[\s\S]*?\}/);
+                                            if (jsonMatch) {
+                                                const flightData = 
JSON.parse(jsonMatch[0]);
+                                                if (flightData.flight_number) {
+                                                    // 将单个航班信息转换为数组格式
+                                                    
renderFlightInfo([flightData]);
+                                                }
+                                            }
+                                        }
+                                    } catch (e) {
+                                        console.log("No valid flight info in 
content", e);
+                                    }
                                 }
                                 if (data.record) {
                                     accumulatedRecordResponse += data.record;
@@ -162,29 +232,50 @@ const generateResponse = (chatElement, recordElement, 
callback) => {
     });
 };
 
-// 
渲染航班信息的函数,需要在generateResponse函数中当机票预定成功调用renderFlightInfo渲染,这里假如返回data.flightinfo
-const renderFlightInfo = (flightInfo) => {
+// 渲染航班信息的函数
+const renderFlightInfo = (flightInfos) => {
+    console.log("Rendering flight info:", flightInfos);
     const flightInfoContainer = document.getElementById('flight-info');
-    // 清空提示信息
-    flightInfoContainer.innerHTML = `<h3>航班信息</h3><p>正在加载航班信息...</p>`;
-    // 返回为空
-    if (flightInfos.length === 0) {
+    // 清空容器并初始化基础结构(标题只显示一次)
+    flightInfoContainer.innerHTML = `<h3>航班信息</h3>`;
+
+    // 判断参数(数组)是否为空
+    if (!flightInfos || flightInfos.length === 0) {
         flightInfoContainer.innerHTML += "<p>没有航班信息可显示</p>";
         return;
     }
-    flightInfos.forEach(flightInfo => {
+
+    // 循环渲染每一条航班数据
+    flightInfos.forEach(flight => {
+        // 确保flight是对象
+        if (typeof flight === 'string') {
+            try {
+                flight = JSON.parse(flight);
+            } catch (e) {
+                console.error("Failed to parse flight info string:", e);
+                return;
+            }
+        }
+        
         const flightInfoHTML = `
-            <h3>航班信息</h3>
-            <p>航班号: ${flightInfo.flightNumber}</p>
-            <p>乘客姓名: ${flightInfo.passengerName}</p>
-            <p>出发城市: ${flightInfo.departureCity}</p>
-            <p>到达城市: ${flightInfo.arrivalCity}</p>
-            <p>出发时间: ${flightInfo.departureTime}</p>
-            <p>到达时间: ${flightInfo.arrivalTime}</p>
-            <hr />
+            <div class="flight-item">
+                <p>航班号: ${flight.flight_number || '未知'}</p>
+                <p>乘客姓名: ${flight.passengerName || '未填写'}</p>
+                <p>出发城市: ${flight.origin || '未知'}</p>
+                <p>到达城市: ${flight.destination || '未知'}</p>
+                <p>出发时间: ${flight.departure_time || '未知'}</p>
+                <p>到达时间: ${flight.arrival_time || '未知'}</p>
+                <p>票价: ${flight.price || '未知'}</p>
+                <p>座位类型: ${flight.seat_type || '未知'}</p>
+                ${flight.message ? `<p class="success-message">状态: 
${flight.message}</p>` : ''}
+                <hr />
+            </div>
         `;
         flightInfoContainer.innerHTML += flightInfoHTML;
     });
+    
+    // 显示航班信息区域
+    flightInfoContainer.style.display = "block";
 };
 
 chatInput.addEventListener("input", () => {
diff --git a/book-flight-ai-agent/go-client/frontend/static/style.css 
b/book-flight-ai-agent/go-client/frontend/static/style.css
index a564d108..b2b2c2c3 100644
--- a/book-flight-ai-agent/go-client/frontend/static/style.css
+++ b/book-flight-ai-agent/go-client/frontend/static/style.css
@@ -37,6 +37,7 @@ body {
     transform 0.3s ease,
     box-shadow 0.3s ease,
     background 0.3s ease;
+  display: none; /* 默认不显示,只有在有航班信息时才显示 */
 }
 #flight-info:hover {
   transform: translateY(5px); /* 悬停时上移 10px */
@@ -227,7 +228,7 @@ header h2 {
   padding: 15px 15px 15px 0;
   font-size: 0.95rem;
 }
-.chat-input #send-btn
+.chat-input #send-btn,
 .chat-input #add-btn {
   align-self: flex-end;
   color: #724ae8;
diff --git a/book-flight-ai-agent/go-server/agents/cot_agent.go 
b/book-flight-ai-agent/go-server/agents/cot_agent.go
index 08c25ad0..8d180e40 100644
--- a/book-flight-ai-agent/go-server/agents/cot_agent.go
+++ b/book-flight-ai-agent/go-server/agents/cot_agent.go
@@ -23,9 +23,7 @@ import (
        "fmt"
        "strings"
        "time"
-)
 
-import (
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/actions"
        "github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/conf"
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/model"
@@ -65,6 +63,12 @@ func (cot *CotAgentRunner) Run(
        callopt model.Option,
        callrst model.CallFunc,
 ) (string, error) {
+       // 1. 获取配置的超时时间(秒转毫秒)
+       timeoutSec := conf.GetEnvironment().TimeOut
+       timeoutCtx, cancel := context.WithTimeout(ctx, 
time.Duration(timeoutSec)*time.Second)
+       defer cancel() // 确保任务结束后释放资源
+
+       // 2. 将原有逻辑的 ctx 替换为 timeoutCtx(确保全链路受超时控制)
        timeNow := time.Now().Format("2006-01-02 15:04:05")
        opts := model.NewOptions(callopt)
 
@@ -74,19 +78,32 @@ func (cot *CotAgentRunner) Run(
 
        var task string
        if len(cot.memoryMsg) > 0 {
+               // 传递 timeoutCtx 到 summaryIntent
                task = cot.summaryIntent(timeNow, callopt)
        } else {
                task = input
        }
 
-       // Runner
+       // Runner 循环(使用 timeoutCtx 判断超时)
        var response string
        var action actions.Action
-
        var idxThoughtStep int32
        var taskState TaskState
+
        for idxThoughtStep < cot.maxThoughtSteps {
-               action, response = cot.thinkStep(task, timeNow, callopt, opts)
+               // 检查是否超时(提前退出循环)
+               select {
+               case <-timeoutCtx.Done():
+                       // 记录超时日志(包含任务内容、超时时间、步骤数)
+                       // log.Printf(
+                       //   "Agent 任务超时:任务=%s,超时时间=%d秒,已执行步骤数=%d",
+                       //   task, timeoutSec, idxThoughtStep,
+                       // )
+                       return fmt.Sprintf("任务超时(已超过%d秒)", timeoutSec), 
timeoutCtx.Err()
+               default:
+               }
+               // 传递 timeoutCtx 到 thinkStep/execAction
+               action, response = cot.thinkStep(timeoutCtx, task, timeNow, 
callopt, opts)
                taskState = InitTaskState(action.Method)
 
                observation := cot.execAction(action, opts)
@@ -138,6 +155,7 @@ func (cot *CotAgentRunner) summaryIntent(timeNow string, 
callopt model.Option) s
 }
 
 func (cot *CotAgentRunner) thinkStep(
+       ctx context.Context,
        task string,
        now string,
        callopt model.Option,
@@ -153,7 +171,7 @@ func (cot *CotAgentRunner) thinkStep(
                        "format_instructions": 
conf.GetConfigPrompts().FormatInstructions,
                },
        )
-       response, _ := cot.llm.Invoke(context.Background(), prompt, callopt, 
ollama.WithTemperature(0.0))
+       response, _ := cot.llm.Invoke(ctx, prompt, callopt, 
ollama.WithTemperature(0.0))
        opts.CallOpt("\n")
        response = model.RemoveThink(response)
        return actions.NewAction(response), response
diff --git a/book-flight-ai-agent/go-server/cmd/server.go 
b/book-flight-ai-agent/go-server/cmd/server.go
index 7cca7dd2..5dd10def 100644
--- a/book-flight-ai-agent/go-server/cmd/server.go
+++ b/book-flight-ai-agent/go-server/cmd/server.go
@@ -34,6 +34,8 @@ import (
 import (
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/agents"
        "github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/conf"
+       
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/model"
+       
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/model/bailian"
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/model/ollama"
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/tools"
        
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/tools/bookingflight"
@@ -65,12 +67,24 @@ func getTools() tools.Tools {
 }
 
 type ChatServer struct {
-       llm *ollama.LLMOllama
+       llm model.LLM
        cot agents.CotAgentRunner
 }
 
 func NewChatServer() (*ChatServer, error) {
-       llm := ollama.NewLLMOllama(cfgEnv.Model, cfgEnv.Url)
+       var llm model.LLM
+       
+       // 检查URL是否包含dashscope.aliyuncs.com,判断是否使用百炼API
+       if strings.Contains(cfgEnv.Url, "dashscope.aliyuncs.com") {
+               // 使用百炼API
+               log.Println("使用百炼API")
+               llm = bailian.NewLLMBailian(cfgEnv.Model, cfgEnv.Url, 
cfgEnv.ApiKey)
+       } else {
+               // 使用Ollama API
+               log.Println("使用Ollama API")
+               llm = ollama.NewLLMOllama(cfgEnv.Model, cfgEnv.Url)
+       }
+       
        cot := agents.NewCotAgentRunner(llm, getTools(), 10, 
conf.GetConfigPrompts())
        return &ChatServer{llm: llm, cot: cot}, nil
 }
diff --git a/book-flight-ai-agent/go-server/conf/config.go 
b/book-flight-ai-agent/go-server/conf/config.go
index d9102dff..baf29d01 100644
--- a/book-flight-ai-agent/go-server/conf/config.go
+++ b/book-flight-ai-agent/go-server/conf/config.go
@@ -65,7 +65,7 @@ func loadEnvironment() {
        configEnv.PortClient = AtoiWithDefault("CLIENT_PORT", 20000)
        configEnv.UrlClient = fmt.Sprintf("%s:%d", configEnv.HostClient, 
configEnv.PortClient)
        configEnv.PortWeb = AtoiWithDefault("WEB_PORT", 8080)
-       configEnv.TimeOut = AtoiWithDefault("TIMEOUT_SECONDS", 300)
+       configEnv.TimeOut = AtoiWithDefault("TIMEOUT_SECONDS", 120) // 默认 120 
秒(2分钟)
 }
 
 func GetEnvironment() Environment {
diff --git a/book-flight-ai-agent/go-server/model/bailian/bailian.go 
b/book-flight-ai-agent/go-server/model/bailian/bailian.go
new file mode 100644
index 00000000..0193df23
--- /dev/null
+++ b/book-flight-ai-agent/go-server/model/bailian/bailian.go
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package bailian
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "fmt"
+       "io"
+       "net/http"
+       "strings"
+)
+
+import (
+       
"github.com/apache/dubbo-go-samples/book-flight-ai-agent/go-server/model"
+)
+
+type Message struct {
+       Role    string `json:"role"`
+       Content string `json:"content"`
+}
+
+type BailianRequest struct {
+       Model       string    `json:"model"`
+       Messages    []Message `json:"messages"`
+       Temperature float64   `json:"temperature,omitempty"`
+       MaxTokens   int       `json:"max_tokens,omitempty"`
+       Stream      bool      `json:"stream,omitempty"`
+}
+
+type BailianResponse struct {
+       ID      string `json:"id"`
+       Object  string `json:"object"`
+       Created int64  `json:"created"`
+       Model   string `json:"model"`
+       Choices []struct {
+               Index        int     `json:"index"`
+               Message      Message `json:"message"`
+               FinishReason string  `json:"finish_reason"`
+       } `json:"choices"`
+       Usage struct {
+               PromptTokens     int `json:"prompt_tokens"`
+               CompletionTokens int `json:"completion_tokens"`
+               TotalTokens      int `json:"total_tokens"`
+       } `json:"usage"`
+}
+
+type LLMBailian struct {
+       Model     string
+       Url       string
+       ApiKey    string
+       MaxTokens int
+       options   []any
+}
+
+func NewLLMBailian(model string, url string, apiKey string) *LLMBailian {
+       return &LLMBailian{
+               Model:     model,
+               Url:       url,
+               ApiKey:    apiKey,
+               MaxTokens: 2048,
+               options:   []any{},
+       }
+}
+
+func (llm *LLMBailian) Call(ctx context.Context, input string, opts 
...model.Option) (string, error) {
+       return llm.Invoke(ctx, input, opts...)
+}
+
+func (llm *LLMBailian) Stream(ctx context.Context, input string, opts 
...model.Option) (string, error) {
+       // 百炼API的流式调用实现
+       // 简化版本,实际上应该使用流式API
+       return llm.Invoke(ctx, input, opts...)
+}
+
+func (llm *LLMBailian) Invoke(ctx context.Context, input string, opts 
...model.Option) (string, error) {
+       options := model.NewOptions(opts...)
+
+       // 解析选项
+       temperature := 0.7
+       for _, opt := range llm.options {
+               if temp, ok := opt.(WithTemperature); ok {
+                       temperature = float64(temp)
+               }
+       }
+
+       // 构建请求体
+       reqBody := BailianRequest{
+               Model: llm.Model,
+               Messages: []Message{
+                       {Role: "user", Content: input},
+               },
+               Temperature: temperature,
+               MaxTokens:   llm.MaxTokens,
+               Stream:      false,
+       }
+
+       // 将请求体转换为JSON
+       jsonData, err := json.Marshal(reqBody)
+       if err != nil {
+               return "", fmt.Errorf("failed to marshal request: %v", err)
+       }
+
+       // 创建HTTP请求
+       req, err := http.NewRequestWithContext(ctx, "POST", 
llm.Url+"/chat/completions", bytes.NewBuffer(jsonData))
+       if err != nil {
+               return "", fmt.Errorf("failed to create request: %v", err)
+       }
+
+       // 设置请求头
+       req.Header.Set("Content-Type", "application/json")
+       req.Header.Set("Authorization", "Bearer "+llm.ApiKey)
+
+       // 发送请求
+       client := &http.Client{}
+       resp, err := client.Do(req)
+       if err != nil {
+               return "", fmt.Errorf("failed to send request: %v", err)
+       }
+       defer resp.Body.Close()
+
+       // 读取响应体
+       respBody, err := io.ReadAll(resp.Body)
+       if err != nil {
+               return "", fmt.Errorf("failed to read response: %v", err)
+       }
+
+       // 检查响应状态码
+       if resp.StatusCode != http.StatusOK {
+               return "", fmt.Errorf("API request failed with status %d: %s", 
resp.StatusCode, string(respBody))
+       }
+
+       // 解析响应
+       var bailianResp BailianResponse
+       if err := json.Unmarshal(respBody, &bailianResp); err != nil {
+               return "", fmt.Errorf("failed to unmarshal response: %v", err)
+       }
+
+       // 检查是否有结果
+       if len(bailianResp.Choices) == 0 {
+               return "", fmt.Errorf("no completion choices returned")
+       }
+
+       // 获取结果
+       result := bailianResp.Choices[0].Message.Content
+       result = strings.TrimSpace(result)
+
+       // 调用回调函数(如果有)
+       if options.CallOpt != nil {
+               options.CallOpt(result)
+       }
+
+       return result, nil
+}
\ No newline at end of file
diff --git a/book-flight-ai-agent/go-server/model/bailian/options.go 
b/book-flight-ai-agent/go-server/model/bailian/options.go
new file mode 100644
index 00000000..c793320e
--- /dev/null
+++ b/book-flight-ai-agent/go-server/model/bailian/options.go
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package bailian
+
+type WithTemperature float32
+
+func (w WithTemperature) Apply(opts *[]any) {
+       *opts = append(*opts, w)
+}
\ No newline at end of file
diff --git a/book-flight-ai-agent/go-server/model/ollama/ollama.go 
b/book-flight-ai-agent/go-server/model/ollama/ollama.go
index b583a32f..5bf51a50 100644
--- a/book-flight-ai-agent/go-server/model/ollama/ollama.go
+++ b/book-flight-ai-agent/go-server/model/ollama/ollama.go
@@ -18,6 +18,7 @@ package ollama
 
 import (
        "context"
+    "fmt"
        "log"
        "net/http"
        "net/url"
@@ -100,11 +101,22 @@ func NewLLMOllama(model string, url string) *LLMOllama {
        }
 }
 
+func (llm *LLMOllama) Stream(ctx context.Context, input string, opts 
...model.Option) (string, error) {
+       return llm.Call(ctx, input, opts...)
+}
+
 func (llm *LLMOllama) Call(ctx context.Context, input string, opts 
...model.Option) (string, error) {
-       client := api.NewClient(&url.URL{Scheme: llm.llmUrl.scheam, Host: 
llm.llmUrl.host}, http.DefaultClient)
+       // 检查 ctx 是否已超时(提前返回)
+       select {
+       case <-ctx.Done():
+               return "", fmt.Errorf("LLM 调用超时:%w", ctx.Err())
+       default:
+       }
 
+       client := api.NewClient(&url.URL{Scheme: llm.llmUrl.scheam, Host: 
llm.llmUrl.host}, http.DefaultClient)
        optss := model.NewOptions(opts...)
 
+       // 传递 ctx 到 client.Generate(确保 LLM 调用受超时控制)
        // By default, GenerateRequest is streaming.
        req := &api.GenerateRequest{
                Model:   llm.Model,
@@ -114,26 +126,28 @@ func (llm *LLMOllama) Call(ctx context.Context, input 
string, opts ...model.Opti
                Options: optss.Opts,
        }
 
-       var respBuilder strings.Builder // Use strings.Builder
+       var respBuilder strings.Builder
        respFunc := func(resp api.GenerateResponse) error {
                respBuilder.WriteString(resp.Response)
                return optss.CallOpt(resp.Response)
        }
 
+       // 使用带超时的 ctx 调用 LLM
        err := client.Generate(ctx, req, respFunc)
        if err != nil {
-               log.Fatal(err)
+               log.Printf("LLM 调用失败(可能超时):%v", err)
                return "", err
        }
 
        return respBuilder.String(), nil
 }
 
-func (llm *LLMOllama) Stream(ctx context.Context, input string, opts 
...model.Option) (string, error) {
-       return llm.Call(ctx, input, opts...)
-}
-
 func (llm *LLMOllama) Invoke(ctx context.Context, input string, opts 
...model.Option) (string, error) {
+       select {
+       case <-ctx.Done():
+               return "", fmt.Errorf("LLM Invoke 超时:%w", ctx.Err())
+       default:
+       }
        client := api.NewClient(&url.URL{Scheme: llm.llmUrl.scheam, Host: 
llm.llmUrl.host}, http.DefaultClient)
 
        // Messages

Reply via email to