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

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


The following commit(s) were added to refs/heads/main by this push:
     new 6c49cca8 Add flight booking AI Agent demo #817 (#818)
6c49cca8 is described below

commit 6c49cca8c5ae24d1b4adb55f13638e433c92fa46
Author: DBU <[email protected]>
AuthorDate: Thu May 15 22:07:18 2025 +0800

    Add flight booking AI Agent demo #817 (#818)
    
    * Add flight booking AI Agent demo
    
    * Modify the description error in the prompt word
    
    * Added Readme file for ticket booking operation instructions
    
    * Delete useless comments
    
    * Add interface and service to bookflight
    
    1. Modify and add callback function to handle streaming output and other 
returns of returned content
    2. Modify the implementation of the Invoke function and call the Chat 
function in Ollama to implement it
    
    * fix: go.mod, go.sum
    
    * fix CI/ubuntu-latest-Go 1.23; golangci-lint / lint(1.23)
    
    * fix CI/ubuntu-latest-Go 1.23; golangci-lint / lint(1.23)
    
    * fix:llm/book-flight/go-server/main.go:59:20: not enough arguments in call 
to cot.Run
    
    * Delete llm/book-flight/go-server/main.go
    
    * Add environment variable files and perform unified verification to avoid 
repeated loading.
    
    * Fixed the problem that the Ollama model configuration parameter loading 
logic error caused the function callback to fail correctly.
    
    * Modify the prompt word loading method and divide the functions.
    
    * Add proto in llm/book-flight and no longer depend on llm/proto.
    
    * Modify frontend
    
    * Modify the front-end file so that the content and record contents are 
displayed in different dialog boxes respectively.
    
    * Modify the agent callback function to stream the agent's thinking process 
to the recording box.
    
    * Fix yaml version issue
    
    * Modify the sample document for booking a flight
    
    * The intelligent agent adds the ability to have multi-round conversations.
    
    * Fix CI-lint
    
    * add: chatbot and record gap
    
    * add: fight book successful message
    
    * Remove the .env file, which is a private file for the user and should not 
be submitted to the code base.
    
    * Add *.env to .gitignore to prevent developers from accidentally 
committing the file.
    
    * Fixed the issue that current_content is nil caused the system to not run 
normally.
    
    * Added 8 task states, six of which are derived from the definition of task 
states in A2A.
    
    * Formatting import packages
    
    * Optimize prompt words
    
    Combine TaskState proposed by A2A, and differentiate prompt word templates.
    
    * Modify the Action
    
    Modify the Action parameters to comply with JSON-RPC2.0 to prepare for 
docking with MCP.
    
    * Add toolkit management to manage a set of tools.
    
    Provides the QueryTool interface to find tools in a toolkit, the 
Description interface to return an overview of the toolkit, and the 
ToolsDescription interface to return descriptions of all tools in the toolkit.
    
    * Adjust the agent process for the addition of new modules
    
    * Fix: ineffectual assignment to rst (ineffassign)
    
    * fixed: {content} not being correctly replaced in summary prompts.
    
    * Add CreateTaskToolkit parameters
    
    Pass the Toolkit documentation through parameters.
    
    * Add CreateTool method to create tools uniformly.
    
    * Fixed: Dependent packages are not imported in groups
    
    * Fixed: Memory error causing missions to not complete properly.
    
    * Fixed:  use panic
    
    * Fixed: use strings.builder or other ways to reuse malloc memory
    
    * Fixed: history will keep growing, would it be better to limit the length
    
    * Fixed: maxContextCount is a default value, make it to const
    
    * Fixed: It appears there is a typo in the comment ('shot' should be 
'short'). Consider updating the comment to 'A String providing a short 
description of the error.'
    
    * Fixed: The 'TaskUnrelatedTool' is appended unconditionally and then is 
potentially appended again if the 'TaskUnrelated' flag is set later. This could 
lead to duplicate tool entries. Consider removing the unconditional append or 
reviewing the flag logic.
    
    * Fixed: The HTML tag 'span1' is non-standard. It is recommended to change 
it to a standard tag such as 'span' for better browser compatibility.
    
    * Fixed: Use string.builder to reuse malloc memory
    
    * Fix: All "Id" in the code were changed to ID
    
    * Fix: In Chinese documents, add a half-width space between English and 
Arabic words and the Chinese characters before and after them.
    
    * Fix: If multiple var, const or type appear in succession, use var(), 
const() or type(), and then put all the contents in this bracket.
    
    * Fix: refactor(configuration): Refactor environment variable configuration 
to enhance maintainability
    
    Centralize the loading logic of environment variables into `config.go`, and 
add `AtoiWithDefault` function to handle default values. At the same time, 
update the `.env.example` file to include new client and web settings, and use 
unified configuration variables in multiple files to reduce hard coding.
    
    ---------
    
    Co-authored-by: 邓爱琪 <[email protected]>
---
 .gitignore                                         |   3 +
 go.mod                                             |   5 +-
 go.sum                                             |  13 +-
 llm/book-flight/.env.example                       |  29 ++
 llm/book-flight/README.md                          |  58 ++++
 llm/book-flight/README_zh.md                       |  58 ++++
 llm/book-flight/go-client/cmd/client.go            | 191 ++++++++++++
 .../go-client/frontend/handlers/chat.go            | 240 +++++++++++++++
 llm/book-flight/go-client/frontend/main.go         |  90 ++++++
 .../go-client/frontend/service/context.go          |  84 ++++++
 .../go-client/frontend/static/script.js            | 312 +++++++++++++++++++
 .../go-client/frontend/static/style.css            | 328 ++++++++++++++++++++
 .../go-client/frontend/templates/index.html        |  68 +++++
 llm/book-flight/go-server/actions/action.go        |  40 +++
 llm/book-flight/go-server/agents/agent.go          |  37 +++
 llm/book-flight/go-server/agents/cot_agent.go      | 223 ++++++++++++++
 llm/book-flight/go-server/agents/task.go           | 205 +++++++++++++
 llm/book-flight/go-server/cmd/server.go            | 142 +++++++++
 .../go-server/conf/bookflight_prompt.yml           |  86 ++++++
 llm/book-flight/go-server/conf/config.example.yml  |  21 ++
 llm/book-flight/go-server/conf/config.go           | 107 +++++++
 llm/book-flight/go-server/mcp/jsonrpc.go           | 107 +++++++
 llm/book-flight/go-server/model/llm.go             |  25 ++
 llm/book-flight/go-server/model/llm_utility.go     |  43 +++
 llm/book-flight/go-server/model/ollama/ollama.go   | 187 ++++++++++++
 llm/book-flight/go-server/model/ollama/options.go  | 134 +++++++++
 llm/book-flight/go-server/model/option.go          |  47 +++
 llm/book-flight/go-server/prompts/prompt.go        |  40 +++
 .../go-server/tools/bookingflight/booking_tools.go | 155 ++++++++++
 llm/book-flight/go-server/tools/tool.go            |  38 +++
 llm/book-flight/go-server/tools/tool_base.go       | 160 ++++++++++
 llm/book-flight/proto/chat.pb.go                   | 332 +++++++++++++++++++++
 llm/book-flight/proto/chat.proto                   |  42 +++
 llm/book-flight/proto/chat.triple.go               | 177 +++++++++++
 34 files changed, 3820 insertions(+), 7 deletions(-)

diff --git a/.gitignore b/.gitignore
index f96c2034..518575a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,9 @@ dist/
 *.out
 coverage.txt
 
+# .env is a user-specific environment configuration file
+*.env
+
 *.idea
 *.iml
 target/
diff --git a/go.mod b/go.mod
index e24d4287..02f0382f 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
        github.com/golang/protobuf v1.5.4
        github.com/joho/godotenv v1.3.0
        github.com/nacos-group/nacos-sdk-go/v2 v2.2.2
+       github.com/ollama/ollama v0.5.1
        github.com/opentracing/opentracing-go v1.2.0
        github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
        github.com/openzipkin/zipkin-go v0.4.0
@@ -29,6 +30,7 @@ require (
        go.opentelemetry.io/otel/sdk v1.24.0
        google.golang.org/grpc v1.64.0
        google.golang.org/protobuf v1.34.1
+       gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
@@ -54,7 +56,7 @@ require (
        github.com/coreos/go-systemd/v22 v22.3.2 // indirect
        github.com/creasty/defaults v1.5.2 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
-       github.com/dlclark/regexp2 v1.10.0 // indirect
+       github.com/dlclark/regexp2 v1.11.4 // indirect
        github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 // indirect
        github.com/dustin/go-humanize v1.0.1 // indirect
        github.com/emicklei/go-restful/v3 v3.10.1 // indirect
@@ -157,7 +159,6 @@ require (
        gopkg.in/ini.v1 v1.66.2 // indirect
        gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
        gopkg.in/yaml.v2 v2.4.0 // indirect
-       gopkg.in/yaml.v3 v3.0.1 // indirect
        skywalking.apache.org/repo/goapi v0.0.0-20220401015832-2c9eee9481eb // 
indirect
        vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 // 
indirect
 )
diff --git a/go.sum b/go.sum
index 5a9d56e9..10cebc8b 100644
--- a/go.sum
+++ b/go.sum
@@ -198,8 +198,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod 
h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod 
h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod 
h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 github.com/dlclark/regexp2 v1.7.0/go.mod 
h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dlclark/regexp2 v1.10.0 
h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
-github.com/dlclark/regexp2 v1.10.0/go.mod 
h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.11.4 
h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
+github.com/dlclark/regexp2 v1.11.4/go.mod 
h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod 
h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
 github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 
h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
 github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod 
h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
@@ -713,6 +713,8 @@ github.com/oklog/ulid v1.3.1/go.mod 
h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod 
h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 
h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod 
h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
+github.com/ollama/ollama v0.5.1 h1:Ug4y/5UZZoTgetMklZslAlEdaCnYEX9qZJ/aTsM4+xc=
+github.com/ollama/ollama v0.5.1/go.mod 
h1:wrgnDTdogU9yeFOj/Jc8BpRBJrWu+Ox4eGyHxqiaQDc=
 github.com/onsi/ginkgo v1.6.0/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod 
h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -1079,8 +1081,8 @@ golang.org/x/exp 
v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod 
h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod 
h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod 
h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
-golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 
h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
-golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod 
h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa 
h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod 
h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod 
h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod 
h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod 
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1370,8 +1372,9 @@ golang.org/x/xerrors 
v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod 
h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
 gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod 
h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod 
h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
+gonum.org/v1/gonum v0.15.0/go.mod 
h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
 gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod 
h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod 
h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod 
h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
diff --git a/llm/book-flight/.env.example b/llm/book-flight/.env.example
new file mode 100644
index 00000000..ded3ae3a
--- /dev/null
+++ b/llm/book-flight/.env.example
@@ -0,0 +1,29 @@
+# 
+# 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.
+# 
+
+# LLM Settings
+LLM_MODEL = qwq
+LLM_URL = "http://127.0.0.1:11434";
+LLM_API_KEY = "sk-..."
+
+# Client Settings
+CLIENT_HOST = "tri://127.0.0.1"
+CLIENT_PORT = 20000
+
+# Web Settings
+WEB_PORT = 8080
+TIMEOUT_SECONDS = 300
diff --git a/llm/book-flight/README.md b/llm/book-flight/README.md
new file mode 100644
index 00000000..40c54c76
--- /dev/null
+++ b/llm/book-flight/README.md
@@ -0,0 +1,58 @@
+## Book Flight example
+
+### 1. Introduction
+
+This case shows how Agent completes the airline booking process with the 
support of a large language model.
+
+### 2. Preparation
+
+#### Modify the configuration file
+
+Modify the configuration file and copy `llm/book-flight/.env.example` to 
`llm/book-flight/.env`.
+
+```ini
+# LLM Settings
+LLM_MODEL = qwq # Ollama model name
+LLM_URL = "http://127.0.0.1:11434"; # Ollama URL, fill in Ollama service address
+LLM_API_KEY = "sk-..." # API key
+
+# Client Settings
+CLIENT_HOST = "tri://127.0.0.1"
+CLIENT_PORT = 20000
+
+# Web Settings
+WEB_PORT = 8080
+TIMEOUT_SECOND = 300 # Timeout
+```
+
+**Note**: Currently only models deployed in Ollama mode
+
+### 3. Run the example
+
+First, enter the `llm/book-flight` directory.
+
+```shell
+$ cd llm/book-flight
+```
+
+#### Server operation
+
+Integrate the Ollama model in the server and call it using the RPC service 
provided by Dubbo-go.
+
+Run in the server directory:
+
+```shell
+$ go run go-server/cmd/server.go
+```
+
+#### Client operation
+
+The front-end page interacts with the client based on the Gin framework. Run 
the following command and then visit ```localhost:8080``` to use it:
+
+```shell
+$ go run go-client/frntend/main.go
+```
+
+### **Notes**
+
+The default `Record` timeout is two minutes. Please ensure that your computer 
performance can generate the corresponding response within two minutes, 
otherwise it will time out and report an error. You can also set the timeout in 
the ```.env``` file.
\ No newline at end of file
diff --git a/llm/book-flight/README_zh.md b/llm/book-flight/README_zh.md
new file mode 100644
index 00000000..977928a6
--- /dev/null
+++ b/llm/book-flight/README_zh.md
@@ -0,0 +1,58 @@
+## 订机票示例
+
+### 1. 介绍
+
+本案例展示了 Agent 如何在大语言模型的加持下,完成机票预订的过程。
+
+### 2. 准备工作
+
+#### 修改配置文件
+
+修改配置文件复制 `llm/book-flight/.env.example` 为 `llm/book-flight/.env`。
+
+```ini
+# LLM 设置
+LLM_MODEL = qwq                     # Ollama 模型名称
+LLM_URL = "http://127.0.0.1:11434";  # Ollama 的 URL,填写 Ollama 的服务地址
+LLM_API_KEY = "sk-..."              # API key
+
+# Client 设置
+CLIENT_HOST = "tri://127.0.0.1"     # 客户端主机
+CLIENT_PORT = 20000                 # 客户端端口
+
+# Web 设置
+WEB_PORT = 8080
+TIMEOUT_SECONDS = 300               # 超时时间
+```
+
+**注意**:目前仅 Ollama 方式部署的模型
+
+### 3. 运行示例
+
+首先,进入 `llm/book-flight` 目录.
+
+```shell
+$ cd llm/book-flight
+```
+
+#### 服务端运行
+
+在服务端中集成 Ollama 模型,并使用 Dubbo-go 提供的 RPC 服务进行调用。
+
+在服务端目录下运行:
+
+```shell
+$ go run go-server/cmd/server.go
+```
+
+#### 客户端运行
+
+前端页面基于 Gin 框架的客户端进行交互,运行以下命令然后访问 ```localhost:8080``` 即可使用:
+
+```shell
+$ go run go-client/frntend/main.go
+```
+
+### **注意事项**
+
+默认 `Record` 超时时间为两分钟,请确保您的电脑性能能在两分钟内生成相应的响应,否则会超时报错,您也可以在 ```.env``` 
文件中自行设置超时时间。
diff --git a/llm/book-flight/go-client/cmd/client.go 
b/llm/book-flight/go-client/cmd/client.go
new file mode 100644
index 00000000..a7c4b55d
--- /dev/null
+++ b/llm/book-flight/go-client/cmd/client.go
@@ -0,0 +1,191 @@
+/*
+ * 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 main
+
+import (
+       "bufio"
+       "context"
+       "fmt"
+       "os"
+       "strings"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/client"
+       _ "dubbo.apache.org/dubbo-go/v3/imports"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/conf"
+       chat "github.com/apache/dubbo-go-samples/llm/book-flight/proto"
+)
+
+var cfgEnv = conf.GetEnvironment()
+
+const (
+       maxHistoryLength = 20 // Example maximum history length
+       maxContextCount  = 3  // Example maximum number of contexts
+)
+
+type ChatContext struct {
+       ID      string
+       History []*chat.ChatMessage
+}
+
+var (
+       contexts     = make(map[string]*ChatContext)
+       currentCtxID string
+       contextOrder []string
+       maxID        uint8 = 0
+)
+
+func handleCommand(cmd string) (resp string) {
+       cmd = strings.TrimSpace(cmd)
+
+       var respBuilder strings.Builder // Use strings.Builder
+       switch {
+       case cmd == "/?" || cmd == "/help":
+               respBuilder.WriteString("Available commands:\n")
+               respBuilder.WriteString("/? help        - Show this help\n")
+               respBuilder.WriteString("/list          - List all contexts\n")
+               respBuilder.WriteString("/cd <context>  - Switch context\n")
+               respBuilder.WriteString("/new           - Create new context")
+       case cmd == "/list":
+               fmt.Println("Stored contexts (max 3):")
+               for _, ctxID := range contextOrder {
+                       respBuilder.WriteString(fmt.Sprintf("- %s\n", ctxID))
+               }
+       case strings.HasPrefix(cmd, "/cd "):
+               target := strings.TrimPrefix(cmd, "/cd ")
+               if ctx, exists := contexts[target]; exists {
+                       currentCtxID = ctx.ID
+                       respBuilder.WriteString(fmt.Sprintf("Switched to 
context: %s\n", target))
+               } else {
+                       respBuilder.WriteString("Context not found")
+               }
+       case cmd == "/new":
+               newID := createContext()
+               currentCtxID = newID
+               respBuilder.WriteString(fmt.Sprintf("Created new context: 
%s\n", newID))
+       default:
+               respBuilder.WriteString("Available commands:\n")
+               respBuilder.WriteString("/? help        - Show this help\n")
+               respBuilder.WriteString("/list          - List all contexts\n")
+               respBuilder.WriteString("/cd <context>  - Switch context\n")
+               respBuilder.WriteString("/new           - Create new context")
+       }
+
+       return respBuilder.String()
+}
+
+func createContext() string {
+       id := fmt.Sprintf("ctx%d", maxID)
+       maxID++
+       contexts[id] = &ChatContext{
+               ID:      id,
+               History: []*chat.ChatMessage{},
+       }
+       contextOrder = append(contextOrder, id)
+
+       if len(contextOrder) > maxContextCount {
+               delete(contexts, contextOrder[0])
+               contextOrder = contextOrder[1:]
+       }
+       return id
+}
+
+func addMessageToHistory(history []*chat.ChatMessage, newMessage 
*chat.ChatMessage) []*chat.ChatMessage {
+       history = append(history, newMessage)
+       if len(history) > maxHistoryLength {
+               // Remove the oldest message
+               history = history[1:]
+       }
+       return history
+}
+
+func main() {
+       currentCtxID = createContext()
+
+       cli, err := client.NewClient(
+               client.WithClientURL(cfgEnv.UrlClient),
+       )
+       if err != nil {
+               fmt.Printf("Error: %v\n", err)
+               return
+       }
+
+       svc, err := chat.NewChatService(cli)
+       if err != nil {
+               fmt.Printf("Error creating service: %v\n", err)
+               return
+       }
+
+       fmt.Print("\nSend a message (/? for help)")
+       scanner := bufio.NewScanner(os.Stdin)
+       for {
+               fmt.Print("\n> ")
+               scanner.Scan()
+               input := scanner.Text()
+
+               // handle command
+               if strings.HasPrefix(input, "/") {
+                       fmt.Println(handleCommand(input))
+                       continue
+               }
+
+               func() {
+                       currentCtx := contexts[currentCtxID]
+                       currentCtx.History = addMessageToHistory(
+                               currentCtx.History,
+                               &chat.ChatMessage{
+                                       Role:    "human",
+                                       Content: input,
+                                       Bin:     nil,
+                               })
+
+                       stream, err := svc.Chat(context.Background(), 
&chat.ChatRequest{
+                               Messages: currentCtx.History,
+                       })
+                       if err != nil {
+                               fmt.Printf("Error: %v\n", err)
+                               return
+                       }
+                       defer stream.Close()
+
+                       var respBuilder strings.Builder // Use strings.Builder
+                       for stream.Recv() {
+                               c := stream.Msg().Content
+                               respBuilder.WriteString(c) // Append to the 
builder
+                               fmt.Print(c)
+                       }
+                       resp := respBuilder.String() // Get the final string
+
+                       if err := stream.Err(); err != nil {
+                               fmt.Printf("Stream error: %v\n", err)
+                               return
+                       }
+
+                       currentCtx.History = append(currentCtx.History,
+                               &chat.ChatMessage{
+                                       Role:    "ai",
+                                       Content: resp,
+                                       Bin:     nil,
+                               })
+               }()
+       }
+}
diff --git a/llm/book-flight/go-client/frontend/handlers/chat.go 
b/llm/book-flight/go-client/frontend/handlers/chat.go
new file mode 100644
index 00000000..9efab288
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/handlers/chat.go
@@ -0,0 +1,240 @@
+/*
+ * 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 handlers
+
+import (
+       "context"
+       "io"
+       "log"
+       "net/http"
+       "regexp"
+       "runtime/debug"
+       "time"
+)
+
+import (
+       "github.com/gin-contrib/sessions"
+       "github.com/gin-gonic/gin"
+)
+
+import (
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-client/frontend/service"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/conf"
+       chat "github.com/apache/dubbo-go-samples/llm/book-flight/proto"
+)
+
+type ChatHandler struct {
+       svc        chat.ChatService
+       ctxManager *service.ContextManager
+}
+
+func NewChatHandler(svc chat.ChatService, mgr *service.ContextManager) 
*ChatHandler {
+       return &ChatHandler{
+               svc:        svc,
+               ctxManager: mgr,
+       }
+}
+
+func (h *ChatHandler) Index(c *gin.Context) {
+       session := sessions.Default(c)
+       ctxID := session.Get("current_context")
+       if ctxID == nil {
+               ctxID = h.ctxManager.CreateContext()
+               session.Set("current_context", ctxID)
+               session.Save()
+       }
+
+       c.HTML(http.StatusOK, "index.html", gin.H{
+               "title": "LLM Chat",
+       })
+}
+
+func (h *ChatHandler) Chat(c *gin.Context) {
+       session := sessions.Default(c)
+
+       value := session.Get("current_context")
+       if value == nil {
+               value = h.ctxManager.CreateContext()
+               session.Set("current_context", value)
+               session.Save()
+       }
+
+       ctxID, _ := session.Get("current_context").(string)
+       var req struct {
+               Message string `json:"message"`
+               Bin     string `json:"bin"`
+       }
+
+       if err := c.BindJSON(&req); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request 
payload"})
+               return
+       }
+
+       var img string
+       if len(req.Bin) > 0 {
+               re := 
regexp.MustCompile(`^data:image/([a-zA-Z]+);base64,([^"]+)$`)
+               // this regex does not support file types like svg
+               matches := re.FindStringSubmatch(req.Bin)
+
+               if len(matches) != 3 {
+                       c.JSON(http.StatusBadRequest, gin.H{"error": "invalid 
base64 data format"})
+                       return
+               }
+
+               img = matches[2]
+       }
+
+       messages := h.ctxManager.GetHistory(ctxID)
+       messages = append(messages, &chat.ChatMessage{
+               Role:    "human",
+               Content: req.Message,
+               Bin:     []byte(img),
+       })
+
+       stream, err := h.svc.Chat(context.Background(), &chat.ChatRequest{
+               Messages: messages,
+       })
+       if err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+               return
+       }
+       defer func() {
+               if err := stream.Close(); err != nil {
+                       log.Println("Error closing stream:", err)
+               }
+       }()
+
+       c.Header("Content-Type", "text/event-stream")
+       c.Header("Cache-Control", "no-cache")
+       c.Header("Connection", "close")
+
+       responseCh := make(chan string, 100) // use buffer
+       responseRc := make(chan string, 100) // use buffer
+
+       go func() {
+               defer func() {
+                       if r := recover(); r != nil {
+                               log.Printf("Recovered in stream processing: 
%v\n%s", r, debug.Stack())
+                       }
+                       close(responseCh)
+                       close(responseRc)
+               }()
+
+               for {
+                       select {
+                       case <-c.Request.Context().Done(): // client disconnect
+                               log.Println("Client disconnected, stopping 
stream processing")
+                               return
+                       default:
+                               if !stream.Recv() {
+                                       if err := stream.Err(); err != nil {
+                                               log.Printf("Stream receive 
error: %v", err)
+                                       }
+                                       return
+                               }
+                               content := stream.Msg().Content
+                               record := stream.Msg().Record
+                               if content != "" {
+                                       responseCh <- content
+                               }
+                               if record != "" {
+                                       responseRc <- record
+                               }
+                       }
+               }
+       }()
+
+       // SSE stream output
+       timeout := conf.GetEnvironment().TimeOut
+       c.Stream(func(w io.Writer) bool {
+               select {
+               case chunk, ok := <-responseCh:
+                       if !ok {
+                               return false
+                       }
+                       c.SSEvent("message", gin.H{"content": chunk})
+                       return true
+               case chunk, ok := <-responseRc:
+                       if !ok {
+                               return false
+                       }
+                       c.SSEvent("message", gin.H{"record": chunk})
+                       return true
+               case <-time.After(time.Duration(timeout) * time.Second):
+                       log.Println("Stream time out")
+                       return false
+               case <-c.Request.Context().Done():
+                       log.Println("Client disconnected")
+                       return false
+               }
+       })
+}
+
+func (h *ChatHandler) NewContext(c *gin.Context) {
+       session := sessions.Default(c)
+       newCtxID := h.ctxManager.CreateContext()
+       session.Set("current_context", newCtxID)
+       if err := session.Save(); err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": "failed 
to save session"})
+               return
+       }
+
+       c.JSON(http.StatusOK, gin.H{
+               "context_id": newCtxID,
+       })
+}
+
+func (h *ChatHandler) ListContexts(c *gin.Context) {
+       session := sessions.Default(c)
+       currentCtx := session.Get("current_context").(string)
+
+       contexts := h.ctxManager.List()
+
+       c.JSON(http.StatusOK, gin.H{
+               "current":  currentCtx,
+               "contexts": contexts,
+       })
+}
+
+func (h *ChatHandler) SwitchContext(c *gin.Context) {
+       var req struct {
+               ContextID string `json:"context_id"`
+       }
+       if err := c.BindJSON(&req); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       exists := h.ctxManager.Consists(req.ContextID)
+
+       if !exists {
+               c.JSON(http.StatusNotFound, gin.H{"error": "context not found"})
+               return
+       }
+
+       session := sessions.Default(c)
+       session.Set("current_context", req.ContextID)
+       if err := session.Save(); err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": "failed 
to save session"})
+               return
+       }
+
+       c.JSON(http.StatusOK, gin.H{
+               "message": "context switched",
+       })
+}
diff --git a/llm/book-flight/go-client/frontend/main.go 
b/llm/book-flight/go-client/frontend/main.go
new file mode 100644
index 00000000..a60f471c
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/main.go
@@ -0,0 +1,90 @@
+/*
+ * 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 main
+
+import (
+       "fmt"
+       "net/http"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/client"
+       _ "dubbo.apache.org/dubbo-go/v3/imports"
+       "github.com/gin-contrib/sessions"
+       "github.com/gin-contrib/sessions/cookie"
+       "github.com/gin-gonic/gin"
+)
+
+import (
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-client/frontend/handlers"
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-client/frontend/service"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/conf"
+       chat "github.com/apache/dubbo-go-samples/llm/book-flight/proto"
+)
+
+var cfgEnv = conf.GetEnvironment()
+
+func main() {
+       // init Dubbo
+       cli, err := client.NewClient(
+               client.WithClientURL(cfgEnv.UrlClient),
+       )
+       if err != nil {
+               fmt.Printf("Error creating Dubbo client: %v", err)
+               return
+       }
+
+       svc, err := chat.NewChatService(cli)
+       if err != nil {
+               fmt.Printf("Error creating chat service: %v", err)
+               return
+       }
+
+       // init Gin
+       r := gin.Default()
+
+       // config session
+       store := cookie.NewStore([]byte("secret"))
+       r.Use(sessions.Sessions("llm_session", store))
+
+       // register tmpl
+       r.LoadHTMLGlob("go-client/frontend/templates/*")
+       r.Static("../static", "go-client/frontend/static/")
+
+       // init service
+       ctxManager := service.NewContextManager()
+
+       // register route
+       cfgEnv := conf.GetEnvironment()
+       h := handlers.NewChatHandler(svc, ctxManager)
+       r.GET("/", func(c *gin.Context) {
+               c.HTML(http.StatusOK, "index.html", gin.H{
+                       "TimeoutSecond": cfgEnv.TimeOut,
+                       "OllamaModel":   cfgEnv.Model,
+               })
+       })
+       r.POST("/api/chat", h.Chat)
+       r.POST("/api/context/new", h.NewContext)
+       r.GET("/api/context/list", h.ListContexts)
+       r.POST("/api/context/switch", h.SwitchContext)
+
+       if err := r.Run(fmt.Sprintf(":%d", cfgEnv.PortWeb)); err != nil {
+               fmt.Printf("Failed to start server: %v", err)
+               return
+       }
+}
diff --git a/llm/book-flight/go-client/frontend/service/context.go 
b/llm/book-flight/go-client/frontend/service/context.go
new file mode 100644
index 00000000..e15d6557
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/service/context.go
@@ -0,0 +1,84 @@
+/*
+ * 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 service
+
+import (
+       "strconv"
+       "sync"
+)
+
+import (
+       chat "github.com/apache/dubbo-go-samples/llm/book-flight/proto"
+)
+
+type ContextManager struct {
+       Contexts map[string][]*chat.ChatMessage
+       Mu       sync.RWMutex
+}
+
+var nowID uint8
+
+func NewContextManager() *ContextManager {
+       return &ContextManager{
+               Contexts: make(map[string][]*chat.ChatMessage),
+       }
+}
+
+func (m *ContextManager) CreateContext() string {
+       m.Mu.Lock()
+       defer m.Mu.Unlock()
+       ctxID := nowID
+       nowID++
+       m.Contexts[strconv.Itoa(int(ctxID))] = []*chat.ChatMessage{}
+       return strconv.Itoa(int(ctxID))
+}
+
+func (m *ContextManager) GetHistory(ctxID string) []*chat.ChatMessage {
+       m.Mu.RLock()
+       defer m.Mu.RUnlock()
+       return m.Contexts[ctxID]
+}
+
+func (m *ContextManager) AppendMessage(ctxID string, msg *chat.ChatMessage) {
+       m.Mu.Lock()
+       defer m.Mu.Unlock()
+       if len(m.Contexts[ctxID]) >= 3 {
+               m.Contexts[ctxID] = m.Contexts[ctxID][1:]
+       }
+       m.Contexts[ctxID] = append(m.Contexts[ctxID], msg)
+}
+
+func (m *ContextManager) List() []string {
+       m.Mu.RLock()
+       defer m.Mu.RUnlock()
+       contexts := make([]string, 0, len(m.Contexts))
+       for ctxID := range m.Contexts {
+               contexts = append(contexts, ctxID)
+       }
+
+       return contexts
+}
+
+func (m *ContextManager) Consists(id string) bool {
+       m.Mu.RLock()
+       defer m.Mu.RUnlock()
+
+       _, ok := m.Contexts[id]
+
+       return ok
+}
diff --git a/llm/book-flight/go-client/frontend/static/script.js 
b/llm/book-flight/go-client/frontend/static/script.js
new file mode 100644
index 00000000..678c26ff
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/static/script.js
@@ -0,0 +1,312 @@
+// This file originally cloned from 
https://github.com/yotam-halperin/chatbot-static-UI
+const chatbox = document.querySelector(".chatbot .chatbox");
+const recordbox = document.querySelector(".record .chatbox");
+const chatInput = document.querySelector(".chat-input textarea");
+const sendChatBtn = document.querySelector(".chat-input #send-btn");
+
+let userMessage = null; // Variable to store user's message
+let userBin = null; // Variable to store user's message
+const inputInitHeight = chatInput.scrollHeight;
+
+let fileBlobArr = [];
+let fileArr = [];
+
+const createChatLi = (content, className, targetBox = chatbox) => {
+    const chatLi = document.createElement("li");
+    chatLi.classList.add("chat", `${className}`);
+
+    const iconSpan = document.createElement('span'); // Create icon span here
+    if (!(className === "outgoing")) {
+        iconSpan.className = "material-symbols-outlined";
+        iconSpan.innerText = "smart_toy";
+        chatLi.appendChild(iconSpan);
+    }
+
+    const contents = Array.isArray(content) ? content : [content];
+
+    contents.forEach(item => {
+        if (!item) return;
+        if (item.startsWith('data:image')) {
+            const img = document.createElement('img');
+            img.src = item;
+            chatLi.appendChild(img);
+        } else {
+            const p = document.createElement('p');
+            p.textContent = item;
+            chatLi.appendChild(p);
+        }
+    });
+
+    targetBox.appendChild(chatLi);
+    targetBox.scrollTo(0, targetBox.scrollHeight);
+    return chatLi;
+};
+
+const handleChat = () => {
+    userMessage = chatInput.value.trim();
+    userBin = fileBlobArr.length > 0 ? fileBlobArr[0] : null;
+
+    const contents = [];
+    if (userMessage) contents.push(userMessage);
+    if (userBin) contents.push(userBin);
+
+    if (contents.length === 0) return;
+
+    chatInput.value = "";
+    sendChatBtn.style.display = "none";
+    chatInput.style.height = `${inputInitHeight}px`;
+    clear();
+
+    // user's message
+    createChatLi(contents, "outgoing", chatbox);
+
+    // "Thinking..."
+    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
+    generateResponse(incomingChatLi, incomingRecordLi, () => {
+        if (!isTimeout) clearTimeout(timeoutId);
+    });
+}
+
+const generateResponse = (chatElement, recordElement, callback) => {
+    const API_URL = "/api/chat";
+    const chatMessageElement = chatElement.querySelector("p");
+    const recordMessageElement = recordElement.querySelector("p");
+
+    // Initialize stream
+    let accumulatedChatResponse = "";
+    let accumulatedRecordResponse = "";
+    chatMessageElement.textContent = "";
+    chatMessageElement.id = "chatContent";
+    recordMessageElement.textContent = "";
+    recordMessageElement.id = "recordContent";
+
+    fetch(API_URL, {
+        method: "POST",
+        headers: {
+            "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ message: userMessage, bin: userBin }),
+    })
+    .then(response => {
+        const reader = response.body.getReader();
+        const decoder = new TextDecoder();
+
+        // Function to read the stream recursively
+        function readStream() {
+            return reader.read().then(({ done, value }) => {
+                if (done) {
+                    // Stream is complete, invoke the callback
+                    if (callback) callback();
+                    return;
+                }
+
+                // Decode the chunk and process events
+                const chunk = decoder.decode(value);
+                const events = chunk.split('\n\n');
+
+                events.forEach(event => {
+                    if (event.startsWith('event:message')) {
+                        // Extract data from the event
+                        const dataLine = event.split('\n')[1];
+                        if (dataLine && dataLine.startsWith('data:')) {
+                            try {
+                                // Parse the JSON data and update the UI
+                                const data = 
JSON.parse(dataLine.replace('data:', ''));
+                                if (data.content) {
+                                    accumulatedChatResponse += data.content;
+                                    chatMessageElement.innerHTML = 
marked.parse(styleMatch(accumulatedChatResponse)); // Render Markdown
+                                    chatbox.scrollTo(0, chatbox.scrollHeight);
+                                }
+                                if (data.record) {
+                                    accumulatedRecordResponse += data.record;
+                                    recordMessageElement.innerHTML = 
marked.parse(styleMatch(accumulatedRecordResponse)); // Render Markdown
+                                    recordbox.scrollTo(0, 
recordbox.scrollHeight);
+                                }
+
+                                hljs.highlightAll();
+                            } catch (error) {
+                                console.error('Failed to parse event data:', 
error);
+                            }
+                        }
+                    }
+                });
+
+                // Continue reading the stream
+                return readStream();
+            });
+        }
+
+        // Start reading the stream
+        return readStream();
+    })
+    .catch(error => {
+        console.error('Error:', error);
+        chatMessageElement.classList.add("error");
+        chatMessageElement.textContent = "Oops! Something went wrong. Please 
try again.";
+        recordMessageElement.classList.add("error");
+        recordMessageElement.textContent = "Oops! Something went wrong. Please 
try again.";
+
+        // Invoke the callback in case of error
+        if (callback) callback();
+    });
+};
+
+// 
渲染航班信息的函数,需要在generateResponse函数中当机票预定成功调用renderFlightInfo渲染,这里假如返回data.flightinfo
+const renderFlightInfo = (flightInfo) => {
+    const flightInfoContainer = document.getElementById('flight-info');
+    // 清空提示信息
+    flightInfoContainer.innerHTML = `<h3>航班信息</h3><p>正在加载航班信息...</p>`;
+    // 返回为空
+    if (flightInfos.length === 0) {
+        flightInfoContainer.innerHTML += "<p>没有航班信息可显示</p>";
+        return;
+    }
+    flightInfos.forEach(flightInfo => {
+        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 />
+        `;
+        flightInfoContainer.innerHTML += flightInfoHTML;
+    });
+};
+
+chatInput.addEventListener("input", () => {
+    if (chatInput.value.trim() !== "") {
+        sendChatBtn.style.display = "block";
+    } else {
+        sendChatBtn.style.display = "none";
+    }
+    // Adjust the height of the input textarea based on its content
+    chatInput.style.height = `${inputInitHeight}px`;
+    chatInput.style.height = `${chatInput.scrollHeight}px`;
+});
+chatInput.addEventListener("keydown", (e) => {
+    // If Enter key is pressed without Shift key and the window
+    // width is greater than 800px, handle the chat
+    if (e.key === "Enter" && !e.shiftKey && window.innerWidth > 800) {
+        e.preventDefault();
+        handleChat();
+    }
+});
+sendChatBtn.addEventListener("click", handleChat);
+
+addBtn = document.getElementById("add-btn");
+fileInput = document.getElementById("input");
+
+// file process
+function filesToBlob(file) {
+    let reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = e => {
+        fileBlobArr.push(e.target.result);
+        let fileDiv = document.createElement('div');
+        // delete btn
+        let removeDiv = document.createElement('div');
+        removeDiv.id = 'file' + '-' + fileBlobArr.length;
+        removeDiv.innerHTML = '×';
+        // file name
+        let fileName = document.createElement('p');
+        fileName.innerHTML = file.name;
+        fileName.title = file.name;
+
+        let img = document.createElement('img');
+        img.src = e.target.result;
+
+
+        fileDiv.appendChild(img);
+        fileDiv.appendChild(removeDiv);
+        fileDiv.appendChild(fileName);
+
+        document.getElementById("drop").appendChild(fileDiv);
+    };
+
+    reader.onerror = () => {
+        switch (reader.error.code) {
+            case '1':
+                alert('File not found');
+                break;
+            case '2':
+                alert('Security error');
+                break;
+            case '3':
+                alert('Loading interrupted');
+                break;
+            case '4':
+                alert('File is not readable');
+                break;
+            case '5':
+                alert('Encode error');
+                break;
+            default:
+                alert('File read fail');
+        }
+    };
+}
+
+function handleFileSelect(event) {
+    const files = event.target.files;
+    if (files.length > 0) {
+        const file = files[0];
+
+        if (!file.type.startsWith('image/')) {
+            alert("Only support image files");
+            return;
+        }
+
+        fileArr.push(file);
+        filesToBlob(file);
+
+        document.querySelector('.drop-box').style.setProperty('--div-count', 
"1");
+        document.getElementById("drop").style.display = "flex";
+        addBtn.style.display = "none";
+    }
+}
+
+// content
+function styleMatch(content) {
+    // <think>
+    // code / equation
+    const regex = /(```[\s\S]*?```)|(\$\$[\s\S]*?\$\$)/g;
+    return addStringsAroundMatch(content.replace(/\n{2,}/g, '\n').trim(), 
regex, '\n', '\n');
+}
+
+function addStringsAroundMatch(text, regex, prefix, suffix) {
+    return text.replace(regex, (match) => {
+        return `${prefix}${match}${suffix}`;
+    });
+}
+
+fileInput.addEventListener('change', handleFileSelect);
+
+addBtn.addEventListener('click', () => {
+    fileInput.click();
+    document.getElementById("drop").style.display = "flex";
+});
+
+function clear() {
+    document.getElementById("drop").innerHTML = '';
+    document.getElementById("drop").style.display = "none";
+    addBtn.style.display = "flex";
+    fileInput.value = "";
+    fileBlobArr = [];
+    fileArr = [];
+}
+
+document.getElementById("drop").addEventListener('click', clear);
\ No newline at end of file
diff --git a/llm/book-flight/go-client/frontend/static/style.css 
b/llm/book-flight/go-client/frontend/static/style.css
new file mode 100644
index 00000000..a564d108
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/static/style.css
@@ -0,0 +1,328 @@
+/* This file originally cloned from 
https://github.com/yotam-halperin/chatbot-static-UI*/
+
+
+/* Import Google font - Poppins */
+@import 
url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: "Poppins", sans-serif;
+}
+body {
+  background: #E3F2FD;
+}
+
+/* 机票预订信息 */
+.booking-confirmation {
+  position: fixed;
+  top: 50%;
+  left: 10%;
+  transform: translate(-50%, -50%) scale(1);
+  width: 20%;
+  height: 90%;
+  z-index: 1000;
+  padding: 0 20px 20px 20px;
+  transition: box-shadow 0.3s;
+  overflow-y: auto; /* 允许垂直滚动 */
+}
+#flight-info {
+  margin-bottom: 15px;
+  padding: 10px;
+  background: linear-gradient(to bottom, #87ceeb, #fff); /* 渐变的背景色 */
+  border: 1px solid #ddd;
+  border-radius: 15px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  transition:
+    transform 0.3s ease,
+    box-shadow 0.3s ease,
+    background 0.3s ease;
+}
+#flight-info:hover {
+  transform: translateY(5px); /* 悬停时上移 10px */
+  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
+}
+#flight-info p {
+  margin-top: 16px;
+  margin-bottom: 16px;
+}
+
+.chatbot {
+  position: fixed;
+  left: 40%;
+  top: 50%;
+  width: 40%;
+  height: 90%;
+  background: #fff;
+  border-radius: 15px;
+  overflow: hidden;
+  opacity: 1;
+  transform-origin: bottom right;
+  box-shadow: 0 0 128px 0 rgba(0,0,0,0.1),
+    0 32px 64px -48px rgba(0,0,0,0.5);
+  transition: all 0.1s ease;
+  pointer-events: auto;
+  transform: translate(-50%, -50%) scale(1);
+  display: flex; /* Added flexbox layout */
+  flex-direction: column; /* Added column direction */
+}
+.record {
+  position: fixed;
+  left: 80.5%;
+  top: 50%;
+  width: 40%;
+  height: 90%;
+  background: #fff;
+  border-radius: 15px;
+  overflow: hidden;
+  opacity: 1;
+  transform-origin: bottom right;
+  box-shadow: 0 0 128px 0 rgba(0,0,0,0.1),
+    0 32px 64px -48px rgba(0,0,0,0.5);
+  transition: all 0.1s ease;
+  pointer-events: auto;
+  transform: translate(-50%, -50%) scale(1);
+  display: flex; /* Added flexbox layout */
+  flex-direction: column; /* Added column direction */
+  
+}
+body.show-chatbot .record {
+  opacity: 1;
+  pointer-events: auto;
+  transform: scale(1);
+}
+.record header {
+  padding: 16px 0;
+  position: relative;
+  background: linear-gradient(to bottom, #4169e1, #87ceeb);
+  border-radius: 10px;
+  text-align: center;
+}
+body.show-chatbot .chatbot {
+  opacity: 1;
+  pointer-events: auto;
+  transform: scale(1);
+}
+.chatbot header {
+  padding: 16px 0;
+  position: relative;
+  background: linear-gradient(to bottom, #4169e1, #87ceeb);
+  border-radius: 10px;
+  text-align: center;
+}
+.chatbot header span {
+  overflow-y: auto;
+  position: absolute;
+  right: 15px;
+  top: 50%;
+  display: none;
+  cursor: pointer;
+  transform: translateY(-50%);
+}
+.record header span {
+  overflow-y: auto;
+  position: absolute;
+  right: 15px;
+  top: 50%;
+  display: none;
+  cursor: pointer;
+  transform: translateY(-50%);
+}
+header h2 {
+  font-size: 1.4rem;
+}
+.chatbox {
+  flex-grow: 1; /* Added flex-grow to chatbox */
+  overflow-y: auto; /* Changed to auto to enable vertical scroll */
+  padding: 30px 20px 100px;
+}
+.chatbot :where(.chatbox, textarea)::-webkit-scrollbar {
+  width: 6px;
+}
+.chatbot :where(.chatbox, textarea)::-webkit-scrollbar-track {
+  background: #fff;
+  border-radius: 25px;
+}
+.chatbot :where(.chatbox, textarea)::-webkit-scrollbar-thumb {
+  background: #ccc;
+  border-radius: 25px;
+}
+.chatbox .chat {
+  display: flex;
+  list-style: none;
+  letter-spacing: 0.6px;
+  line-height: 2;
+  margin-bottom: 10px;
+}
+.chatbox .chat img {
+  max-width: 250px;
+  max-height: 100px;
+  height: auto;
+  border-radius: 10px;
+  margin-top: 5px;
+}
+
+.chatbox .outgoing {
+  margin: 10px 0;
+  justify-content: flex-start;
+  display: flex;
+  list-style: none;
+  align-items: flex-end;
+  flex-direction: column;
+}
+.chatbox .incoming span {
+  width: 32px;
+  height: 32px;
+  color: #fff;
+  cursor: default;
+  text-align: center;
+  line-height: 32px;
+  align-self: flex-start;
+  background: #724ae8;
+  border-radius: 4px;
+  margin: 0 10px 7px 0;
+}
+.chatbox .chat p {
+  white-space: pre-wrap;
+  padding: 12px 6px;
+  border-radius: 0 10px 0 10px;
+  max-width: 100%;
+  color: #000;
+  font-size: 0.95rem;
+  background: #fafafa;
+}
+.chatbox .incoming p {
+  border-radius: 5px 5px 5px 5px;
+}
+.chatbox .outgoing p {
+  background: #f0f4ff;
+  border-radius: 5px 5px 5px 5px;
+}
+.chatbox .chat p.error {
+  color: #721c24;
+  background: #f8d7da;
+}
+.chatbox .incoming p {
+  color: #000;
+  /* background: #f2f2f2; */
+}
+.chatbot .chat-input {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  position: relative; /* Changed to relative */
+  bottom: 0;
+  width: 100%;
+  background: #fff;
+  padding: 3px 20px;
+  border-top: 1px solid #ddd;
+}
+.chat-input textarea {
+  height: 55px;
+  width: 100%;
+  border: none;
+  outline: none;
+  resize: none;
+  max-height: 180px;
+  padding: 15px 15px 15px 0;
+  font-size: 0.95rem;
+}
+.chat-input #send-btn
+.chat-input #add-btn {
+  align-self: flex-end;
+  color: #724ae8;
+  cursor: pointer;
+  height: 55px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1.35rem;
+}
+.chat-input textarea:valid ~ span {
+  visibility: visible;
+}
+
+.drop-box {
+  display: none;
+  height: 60px;
+  width: 20%;
+  border: 1px dashed #a89b9b;
+  overflow: hidden;
+  overflow-y: auto;
+}
+
+.drop-box > div {
+  width: 40px;
+  height: 90%;
+  margin: 1px 3px;
+  display: block;
+  position: relative;
+}
+
+.drop-box > div > img {
+  width: 90%;
+  height: 70%;
+}
+
+.drop-box > div > div {
+  position: absolute;
+  right: 5px;
+  top: 5px;
+  width: 15px;
+  height: 15px;
+  /* background: #347aa5; */
+  color: #000;
+  /* color: white; */
+  border-radius: 50%;
+  text-align: center;
+  line-height: 15px;
+  /* font-weight: bold; */
+  font-size: 15px;
+  cursor: pointer;
+}
+
+.drop-box > div > p {
+  bottom: 5px;
+  text-align: center;
+  line-height: 10px;
+  font-size: 10px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.drop-text {
+  position: absolute;
+  width: 450px;
+  height: 50px;
+  top: 200px;
+  text-align: center;
+  line-height: 50px;
+  opacity: 0.3;
+}
+
+.drop-text > span {
+  color: #347aa5;
+  cursor: pointer;
+}
+
+
+@media (max-width: 600px), (max-height: 600px) {
+  .chatbot {
+    right: 0;
+    bottom: 0;
+    height: 100%;
+    border-radius: 0;
+    width: 100%;
+  }
+  .chatbot .chatbox {
+    height: 90%;
+    padding: 25px 15px 100px;
+  }
+  .chatbot .chat-input {
+    padding: 5px 15px;
+  }
+  .chatbot header span {
+    display: block;
+  }
+}
\ No newline at end of file
diff --git a/llm/book-flight/go-client/frontend/templates/index.html 
b/llm/book-flight/go-client/frontend/templates/index.html
new file mode 100644
index 00000000..21b72dcb
--- /dev/null
+++ b/llm/book-flight/go-client/frontend/templates/index.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+    <meta charset="utf-8">
+    <title>Chatbot in JavaScript | CodingNepal</title>
+    <link rel="stylesheet" href="../static/style.css">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="stylesheet" 
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0";
 />
+    <link rel="stylesheet" 
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,1,0";
 />
+    <script src="../static/script.js" defer></script>
+    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js";></script>
+    <link rel="stylesheet" 
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css";>
+    <script 
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js";></script>
+    <style>
+        .markdown-body {
+            line-height: 1.2;
+            font-family: sans-serif;
+            padding: 20px;
+        }
+    </style>
+</head>
+<body>
+
+<!-- 机票预订信息 -->
+<div class="booking-confirmation">
+    <div id="flight-info">
+        <h3>航班信息</h3>
+        <p>正在加载航班信息...</p>
+    </div>
+</div>
+<div class="chatbot">
+    <header>
+        <h2>Chatbot</h2>
+    </header>
+    <ul class="chatbox">
+        <li class="chat incoming">
+            <span class="material-symbols-outlined">smart_toy</span>
+            <p class="chat incoming content" id="content">Hi there 👋</p>
+        </li>
+    </ul>
+    <div class="chat-input">
+        <textarea placeholder="Enter a message..." spellcheck="false" 
required></textarea>
+        <span id="send-btn" class="material-symbols-rounded" style="display: 
none;">send</span>
+        <div id="drop" class="drop-box"></div>
+        <span id="add-btn" class="material-symbols-rounded">add</span>
+    </div>
+    <input id="input" accept="image/png, image/jpeg, image/gif" type="file" 
style="display: none">
+</div>
+
+<div class="record">
+    <header>
+        <h2>Record</h2>
+    </header>
+    <ul class="chatbox">
+    </ul>
+</div>
+
+</div>
+
+</body>
+
+<script>
+    const CONFIG = {
+        TIME_OUT_SECOND: {{ .TimeoutSecond }} * 1000
+    };
+</script>
+
+</html>
\ No newline at end of file
diff --git a/llm/book-flight/go-server/actions/action.go 
b/llm/book-flight/go-server/actions/action.go
new file mode 100644
index 00000000..0823b8f2
--- /dev/null
+++ b/llm/book-flight/go-server/actions/action.go
@@ -0,0 +1,40 @@
+/*
+ * 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 actions
+
+import (
+       "encoding/json"
+       "regexp"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/mcp"
+)
+
+type Action mcp.RequestRPC
+
+func NewAction(text string) Action {
+       re := regexp.MustCompile("```json[^`]*```")
+       matches := re.FindAllString(text, -1)
+       action := Action{}
+       if len(matches) > 0 {
+               match := matches[0][7 : len(matches[0])-3]
+               json.Unmarshal([]byte(match), &action)
+       }
+       return action
+}
diff --git a/llm/book-flight/go-server/agents/agent.go 
b/llm/book-flight/go-server/agents/agent.go
new file mode 100644
index 00000000..df60c015
--- /dev/null
+++ b/llm/book-flight/go-server/agents/agent.go
@@ -0,0 +1,37 @@
+/*
+ * 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 agents
+
+import (
+       "github.com/gin-gonic/gin"
+       "github.com/tmc/langchaingo/schema"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools"
+)
+
+// Agent is the interface all agents must implement.
+type Agent interface {
+       // Plan Given an input and previous steps decide what to do next. 
Returns
+       // either actions or a finish.
+       Plan(ctx *gin.Context, intermediateSteps []schema.AgentStep, inputs 
map[string]string) ([]schema.AgentAction, *schema.AgentFinish, error) 
//nolint:lll
+       GetInputKeys() []string
+       GetOutputKeys() []string
+       GetTools() []tools.Tool
+}
diff --git a/llm/book-flight/go-server/agents/cot_agent.go 
b/llm/book-flight/go-server/agents/cot_agent.go
new file mode 100644
index 00000000..8844df7b
--- /dev/null
+++ b/llm/book-flight/go-server/agents/cot_agent.go
@@ -0,0 +1,223 @@
+/*
+ * 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 agents
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "strings"
+       "time"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/actions"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/conf"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/model"
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-server/model/ollama"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/prompts"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools"
+)
+
+type CotAgentRunner struct {
+       llm             model.LLM
+       tools           tools.Tools
+       maxThoughtSteps int32
+       cotPrompts      conf.CfgPrompts
+       memoryAgent     []map[string]any
+       memoryMsg       []map[string]any
+}
+
+func NewCotAgentRunner(
+       llm model.LLM,
+       tools tools.Tools,
+       maxSteps int32,
+       cotPrompts conf.CfgPrompts,
+) CotAgentRunner {
+       return CotAgentRunner{
+               llm:             llm,
+               tools:           tools,
+               maxThoughtSteps: maxSteps,
+               cotPrompts:      cotPrompts,
+               memoryAgent:     []map[string]any{},
+               memoryMsg:       []map[string]any{},
+       }
+}
+
+func (cot *CotAgentRunner) Run(
+       ctx context.Context,
+       input string,
+       callopt model.Option,
+       callrst model.CallFunc,
+) (string, error) {
+       timeNow := time.Now().Format("2006-01-02 15:04:05")
+       opts := model.NewOptions(callopt)
+
+       // Init Memory
+       cot.memoryAgent = []map[string]any{}
+       cot.memoryMsg = cot.updateMessage(cot.memoryMsg, input, "")
+
+       var task string
+       if len(cot.memoryMsg) > 0 {
+               task = cot.summaryIntent(timeNow, callopt)
+       } else {
+               task = input
+       }
+
+       // Runner
+       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)
+               taskState = InitTaskState(action.Method)
+
+               observation := cot.execAction(action, opts)
+               cot.memoryAgent = cot.updateMemory(cot.memoryAgent, response, 
observation)
+
+               if InterruptTask(taskState) {
+                       break
+               }
+
+               idxThoughtStep++
+       }
+
+       var err error
+       reply := "Sorry, failed to complete your task."
+       if idxThoughtStep < cot.maxThoughtSteps {
+               reply, err = cot.finalStep(task, input, timeNow, taskState, 
callopt, callrst)
+
+               cot.memoryMsg = cot.updateMessage(cot.memoryMsg, task, reply)
+               if taskState == TaskCompleted || taskState == TaskUnrelated {
+                       cot.memoryMsg = []map[string]any{}
+               }
+       }
+
+       return reply, err
+}
+
+func (cot *CotAgentRunner) GetInputCtx(input string) string {
+       var respBuilder strings.Builder // Use strings.Builder
+       for _, msg := range cot.memoryAgent {
+               if val, ok := msg["user"]; ok {
+                       respBuilder.WriteString(fmt.Sprintf("\n%v", val))
+               }
+       }
+       respBuilder.WriteString(fmt.Sprintf("\n%v", input))
+
+       return strings.TrimSpace(respBuilder.String())
+}
+
+func (cot *CotAgentRunner) summaryIntent(timeNow string, callopt model.Option) 
string {
+       prompt := prompts.CreatePrompt(
+               cot.cotPrompts.IntentPrompt,
+               map[string]any{
+                       "memory": cot.memoryMsg,
+                       "time":   timeNow,
+               },
+       )
+       response, _ := cot.llm.Call(context.Background(), prompt, callopt, 
ollama.WithTemperature(0.0))
+       return model.RemoveThink(response)
+}
+
+func (cot *CotAgentRunner) thinkStep(
+       task string,
+       now string,
+       callopt model.Option,
+       opts model.Options,
+) (actions.Action, string) {
+       prompt := prompts.CreatePrompt(
+               cot.cotPrompts.ReactPrompt,
+               map[string]any{
+                       "task":                task,
+                       "memory":              cot.memoryAgent,
+                       "time":                now,
+                       "tools":               cot.tools.ToolsDescription(),
+                       "format_instructions": 
conf.GetConfigPrompts().FormatInstructions,
+               },
+       )
+       response, _ := cot.llm.Invoke(context.Background(), prompt, callopt, 
ollama.WithTemperature(0.0))
+       opts.CallOpt("\n")
+       response = model.RemoveThink(response)
+       return actions.NewAction(response), response
+}
+
+func (cot *CotAgentRunner) finalStep(
+       task string,
+       input string,
+       date string,
+       taskState TaskState,
+       callopt model.Option,
+       callrst model.CallFunc,
+) (string, error) {
+       config := map[string]any{"task": task}
+       promptTemplate := cot.cotPrompts.FinalPrompt
+       switch taskState {
+       case TaskUnrelated:
+               promptTemplate = cot.cotPrompts.UnrelatedPrompt
+               config["task"] = input
+       case TaskInputRequired:
+               promptTemplate = cot.cotPrompts.InputPrompt
+               config["memory"] = cot.memoryAgent
+       default:
+               config["memory"] = cot.memoryAgent
+               config["time"] = date
+       }
+
+       prompt := prompts.CreatePrompt(promptTemplate, config)
+       reply, err := cot.llm.Call(context.Background(), prompt, callopt, 
ollama.WithTemperature(0.0))
+       reply = model.RemoveThink(reply)
+
+       callrst(reply)
+       return reply, err
+}
+
+func (cot *CotAgentRunner) execAction(action actions.Action, opts 
model.Options) string {
+       var err error
+       var observation string = fmt.Sprintf("Can't find tool: %v.", 
action.Method)
+       tool := cot.tools.QueryTool(action.Method)
+       if tool != nil {
+               strArgs, _ := json.Marshal(action.Params)
+               observation, err = tool.Call(context.Background(), 
string(strArgs))
+               opts.CallOpt("\n")
+               if err != nil {
+                       observation = "Validation Error in args: " + 
string(strArgs)
+               }
+       }
+       return observation
+}
+
+func (cot *CotAgentRunner) updateMemory(memory []map[string]any, response 
string, observation string) []map[string]any {
+       return append(memory,
+               map[string]any{"input": response, "output": "\nResult:\n" + 
observation},
+       )
+}
+
+func (cot *CotAgentRunner) updateMessage(memory []map[string]any, msgUser 
string, msgAgent string) []map[string]any {
+       if msgUser != "" {
+               memory = append(memory, map[string]any{"Human": msgUser})
+       }
+
+       if msgAgent != "" {
+               memory = append(memory, map[string]any{"Agent": msgAgent})
+       }
+
+       return memory
+}
diff --git a/llm/book-flight/go-server/agents/task.go 
b/llm/book-flight/go-server/agents/task.go
new file mode 100644
index 00000000..4dd2b7f1
--- /dev/null
+++ b/llm/book-flight/go-server/agents/task.go
@@ -0,0 +1,205 @@
+/*
+ * 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 agents
+
+import (
+       "context"
+       "strings"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools"
+)
+
+type TaskState int
+
+const (
+       TaskUndefined TaskState = 0
+       TaskSubmitted TaskState = 1 << iota
+       TaskWorking
+       TaskInputRequired
+       TaskCompleted
+       TaskFailed
+       TaskCanceled
+       TaskUnrelated
+)
+
+// InitTaskState
+func InitTaskState(value string) TaskState {
+       var rst TaskState
+       switch strings.ToUpper(value) {
+       case "TASKUNDEFINED":
+               rst = TaskUndefined
+       case "TASKSUBMITTED":
+               rst = TaskSubmitted
+       case "TASKWORKING":
+               rst = TaskWorking
+       case "TASKINPUTREQUIRED":
+               rst = TaskInputRequired
+       case "TASKCOMPLETED":
+               rst = TaskCompleted
+       case "TASKFAILED":
+               rst = TaskFailed
+       case "TASKCANCELED":
+               rst = TaskCanceled
+       case "TASKUNRELATED":
+               rst = TaskUnrelated
+       default:
+               rst = TaskUndefined
+       }
+       return rst
+}
+
+func CreateToolkitByVariadic(description string, taskFlag TaskState, ts 
...tools.Tool) tools.Tools {
+       return CreateToolkit(description, taskFlag, ts)
+}
+
+// CreateToolkit
+func CreateToolkit(description string, taskFlag TaskState, ts []tools.Tool) 
tools.Tools {
+       var toolsTask []tools.Tool = ts
+       if taskFlag&TaskSubmitted != 0 {
+               if t, err := 
tools.CreateTool[TaskSubmittedTool]("TaskSubmitted", "任务已提交", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskWorking != 0 {
+               if t, err := tools.CreateTool[TaskWorkingTool]("TaskWorking", 
"任务正在处理中", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskInputRequired != 0 {
+               if t, err := 
tools.CreateTool[TaskInputRequiredTool]("TaskInputRequired", "任务需要更多信息输入", ""); 
err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskCompleted != 0 {
+               if t, err := 
tools.CreateTool[TaskCompletedTool]("TaskCompleted", "任务已完成", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskFailed != 0 {
+               if t, err := tools.CreateTool[TaskFailedTool]("TaskFailed", 
"任务执行失败", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskCanceled != 0 {
+               if t, err := tools.CreateTool[TaskCanceledTool]("TaskCanceled", 
"任务已取消", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+       if taskFlag&TaskUnrelated != 0 {
+               if t, err := 
tools.CreateTool[TaskUnrelatedTool]("TaskUnrelated", "不相关任务", ""); err == nil {
+                       toolsTask = append(toolsTask, t)
+               }
+       }
+
+       return tools.NewToolkit(toolsTask, description)
+}
+
+func InterruptTask(taskFlag TaskState) bool {
+       return 
taskFlag&(TaskInputRequired|TaskCompleted|TaskFailed|TaskCanceled|TaskUnrelated)
 != 0
+}
+
+/*
+TaskUndefinedTool
+*/
+type TaskUndefinedTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskUndefinedTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskUndefined", nil
+}
+
+/*
+TaskSubmittedTool
+*/
+type TaskSubmittedTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskSubmittedTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskSubmitted", nil
+}
+
+/*
+TaskWorkingTool
+*/
+type TaskWorkingTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskWorkingTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskWorking", nil
+}
+
+/*
+TaskInputRequiredTool
+*/
+type TaskInputRequiredTool struct {
+       tools.BaseTool
+       MissingInfo string `json:"missing_info" validate:"required"`
+}
+
+func (mi TaskInputRequiredTool) Call(ctx context.Context, input string) 
(string, error) {
+       return input, nil
+}
+
+/*
+TaskCompletedTool
+*/
+type TaskCompletedTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskCompletedTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskCompleted", nil
+}
+
+/*
+TaskFailedTool
+*/
+type TaskFailedTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskFailedTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskFailed", nil
+}
+
+/*
+TaskCanceledTool
+*/
+type TaskCanceledTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskCanceledTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskCanceled", nil
+}
+
+/*
+TaskUnrelatedTool
+*/
+type TaskUnrelatedTool struct {
+       tools.BaseTool
+}
+
+func (ptt TaskUnrelatedTool) Call(ctx context.Context, input string) (string, 
error) {
+       return "TaskUnrelated", nil
+}
diff --git a/llm/book-flight/go-server/cmd/server.go 
b/llm/book-flight/go-server/cmd/server.go
new file mode 100644
index 00000000..5744e496
--- /dev/null
+++ b/llm/book-flight/go-server/cmd/server.go
@@ -0,0 +1,142 @@
+/*
+ * 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 main
+
+import (
+       "context"
+       "fmt"
+       "log"
+       "runtime/debug"
+       "strings"
+)
+
+import (
+       _ "dubbo.apache.org/dubbo-go/v3/imports"
+       "dubbo.apache.org/dubbo-go/v3/protocol"
+       "dubbo.apache.org/dubbo-go/v3/server"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/agents"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/conf"
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-server/model/ollama"
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools"
+       
"github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools/bookingflight"
+       chat "github.com/apache/dubbo-go-samples/llm/book-flight/proto"
+)
+
+var cfgEnv = conf.GetEnvironment()
+
+func getTools() tools.Tools {
+       var t tools.Tool
+       var err error
+       var tool_list []tools.Tool
+
+       t, err = tools.CreateTool[bookingflight.SearchFlightTicketTool]("查询机票", 
"查询指定日期可用的飞机票。", "")
+       if err == nil {
+               tool_list = append(tool_list, t)
+       }
+       t, err = tools.CreateTool[bookingflight.PurchaseFlightTicketTool](
+               "购买机票", "购买飞机票。会返回购买结果(result), 和座位号(seat_number)", "")
+       if err == nil {
+               tool_list = append(tool_list, t)
+       }
+
+       return agents.CreateToolkit(
+               "订机票工具包,查询/预订机票功能。",
+               
agents.TaskCompleted|agents.TaskInputRequired|agents.TaskFailed|agents.TaskUnrelated,
+               tool_list,
+       )
+}
+
+type ChatServer struct {
+       llm *ollama.LLMOllama
+       cot agents.CotAgentRunner
+}
+
+func NewChatServer() (*ChatServer, error) {
+       llm := ollama.NewLLMOllama(cfgEnv.Model, cfgEnv.Url)
+       cot := agents.NewCotAgentRunner(llm, getTools(), 10, 
conf.GetConfigPrompts())
+       return &ChatServer{llm: llm, cot: cot}, nil
+}
+
+func (s *ChatServer) Chat(ctx context.Context, req *chat.ChatRequest, stream 
chat.ChatService_ChatServer) (err error) {
+       defer func() {
+               if r := recover(); r != nil {
+                       log.Printf("panic in Chat: %v\n%s", r, debug.Stack())
+                       err = fmt.Errorf("internal server error")
+               }
+       }()
+
+       if s.llm == nil {
+               return fmt.Errorf("LLM is not initialized")
+       }
+
+       if len(req.Messages) == 0 {
+               log.Println("Request contains no messages")
+               return fmt.Errorf("empty messages in request")
+       }
+
+       respFunc := func(resp string) error {
+               return stream.Send(&chat.ChatResponse{
+                       Record: resp,
+               })
+       }
+
+       rstFunc := func(resp string) error {
+               return stream.Send(&chat.ChatResponse{
+                       Content: "\n" + strings.TrimSpace(resp),
+               })
+       }
+
+       input := req.Messages[len(req.Messages)-1].Content
+       _, err = s.cot.Run(ctx, input, ollama.WithStreamingFunc(respFunc), 
rstFunc)
+       if err != nil {
+               log.Printf("Run failed: %v", err)
+       }
+
+       return nil
+}
+
+func main() {
+       srv, err := server.NewServer(
+               server.WithServerProtocol(
+                       protocol.WithPort(cfgEnv.PortClient),
+               ),
+       )
+       if err != nil {
+               fmt.Printf("Error creating server: %v\n", err)
+               return
+       }
+
+       chatServer, err := NewChatServer()
+       if err != nil {
+               fmt.Printf("Error creating chat server: %v\n", err)
+               return
+       }
+
+       if err := chat.RegisterChatServiceHandler(srv, chatServer); err != nil {
+               fmt.Printf("Error registering handler: %v\n", err)
+               return
+       }
+
+       if err := srv.Serve(); err != nil {
+               fmt.Printf("Error starting server: %v\n", err)
+               return
+       }
+}
diff --git a/llm/book-flight/go-server/conf/bookflight_prompt.yml 
b/llm/book-flight/go-server/conf/bookflight_prompt.yml
new file mode 100644
index 00000000..89a84cff
--- /dev/null
+++ b/llm/book-flight/go-server/conf/bookflight_prompt.yml
@@ -0,0 +1,86 @@
+reactPrompt: "
+当前的任务执行记录:
+{memory}
+
+你是强大的AI飞机票助手,可以使用工具与指令查询并购买飞机票。
+
+你可以使用以下工具或指令,它们又称为动作或actions:
+{tools}
+
+当前日期:{time},你的任务是:
+{task}
+
+特别说明:
+1. 若问题与查询/购买机票无关时,直接调用 TaskUnrelated 指令;
+2. 查询/购买机票的必要信息:出发地、目的地、出发时间;除此之外自行推断。
+
+按照以下格式输出:
+
+任务:你收到的需要执行的任务
+思考: 观察你的任务和执行记录,并思考你下一步应该采取的行动
+根据以下格式说明,输出你选择执行的动作/工具:
+{format_instructions}
+。
+"
+
+intentPrompt: "
+以下是你与用户之间的交互过程:
+{memory}
+
+用简单的话语对用户意图进行摘要总结;
+无需添加称呼或保留错误信息;
+不要进行深度思考;
+无需补充缺失部分,只输出总结结果。
+"
+
+finalPrompt: "
+以下是你的思考过程和使用工具与外部资源交互的结果。
+{memory}
+
+当前日期:{time},你的任务是:{task}。
+
+你已经完成任务。
+现在请根据上述结果简要总结出你的最终答案。
+直接给出答案。不用再解释或分析你的思考过程。
+"
+
+inputPrompt: "
+你的任务是:
+{task}
+
+需要按照以下内容提示用户缺失信息:
+{memory}
+
+输出要求:
+
+1. 现在请根据上述结果简要总结出要求用户需要补充的信息;
+2. 航班号需要给出备选项,其他无需给出备选项;
+3. 直接给出答案,不用再解释或分析你的思考过程,需要对信息进行总结;
+
+尽可能用中文描述缺失信息,输出格式:
+
+需要补充以下信息:
+<缺失信息1>,
+<缺失信息2> 备选项:可选值1,可选值2,...,
+<缺失信息n>, ...
+"
+
+unrelatedPrompt: "直接、简要地回答下列问题,不需要重复返回:\n{task}"
+
+summary_prompt: "
+历史摘要:{summary}
+
+最新对话内容:{new_lines}
+
+请分析上述对话,特别关注 Human 的关键信息,简要用一句话总结用户的问题或购买机票的真实任务需求,只需要输出总结内容。"
+
+formatInstructions: '
+```json\n\n
+{
+  "method": "Name of the tool/action",
+  "params": {
+    "parameter_name": "Parameter values required by the tool"
+  }
+}
+```
+'
\ No newline at end of file
diff --git a/llm/book-flight/go-server/conf/config.example.yml 
b/llm/book-flight/go-server/conf/config.example.yml
new file mode 100644
index 00000000..db6e98d9
--- /dev/null
+++ b/llm/book-flight/go-server/conf/config.example.yml
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+LLM: {
+  url: "http://localhost:11434";,
+  model: "deepseek-r1:1.5b"
+}
\ No newline at end of file
diff --git a/llm/book-flight/go-server/conf/config.go 
b/llm/book-flight/go-server/conf/config.go
new file mode 100644
index 00000000..1cc58bc6
--- /dev/null
+++ b/llm/book-flight/go-server/conf/config.go
@@ -0,0 +1,107 @@
+/*
+ * 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 conf
+
+import (
+       "fmt"
+       "log"
+       "os"
+       "strconv"
+       "sync"
+
+       "github.com/joho/godotenv"
+       "gopkg.in/yaml.v3"
+)
+
+var (
+       configPrompts CfgPrompts
+       configEnv     Environment
+       oncePrompts   sync.Once
+       onceEnv       sync.Once
+)
+
+// Config structure matches the environment file structure
+type Environment struct {
+       Model      string `env:"LLM_MODEL"`
+       Url        string `env:"LLM_URL"`
+       ApiKey     string `env:"LLM_API_KEY"`
+       HostClient string `env:"CLIENT_HOST"`
+       PortClient int    `env:"CLIENT_PORT"`
+       UrlClient  string `env:"_"`
+       PortWeb    int    `env:"WEB_PORT"`
+       TimeOut    int    `env:"TIMEOUT_SECONDS"`
+}
+
+// loadConfigPrompts reads and parses environment file
+func loadEnvironment() {
+       err := godotenv.Load()
+       if err != nil {
+               log.Fatalf("Error loading .env file: %v", err)
+       }
+
+       // Reading environment variables
+       configEnv.Model = os.Getenv("LLM_MODEL")
+       configEnv.Url = os.Getenv("LLM_URL")
+       configEnv.ApiKey = os.Getenv("LLM_API_KEY")
+       configEnv.HostClient = os.Getenv("CLIENT_HOST")
+       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)
+}
+
+func GetEnvironment() Environment {
+       onceEnv.Do(loadEnvironment)
+       return configEnv
+}
+
+func AtoiWithDefault(key string, defaultValue int) int {
+       if value := os.Getenv(key); value != "" {
+               if val, err := strconv.Atoi(value); err == nil {
+                       return val
+               }
+       }
+       return defaultValue
+}
+
+// Config structure matches the YAML file structure
+type CfgPrompts struct {
+       ReactPrompt        string `yaml:"reactPrompt"`
+       FinalPrompt        string `yaml:"finalPrompt"`
+       IntentPrompt       string `yaml:"intentPrompt"`
+       InputPrompt        string `yaml:"inputPrompt"`
+       UnrelatedPrompt    string `yaml:"unrelatedPrompt"`
+       FormatInstructions string `yaml:"formatInstructions"`
+}
+
+// loadConfigPrompts reads and parses YAML file
+func loadConfigPrompts() {
+       data, err := os.ReadFile("go-server/conf/bookflight_prompt.yml")
+       if err != nil {
+               return
+       }
+
+       if err := yaml.Unmarshal(data, &configPrompts); err != nil {
+               return
+       }
+}
+
+func GetConfigPrompts() CfgPrompts {
+       oncePrompts.Do(loadConfigPrompts)
+       return configPrompts
+}
diff --git a/llm/book-flight/go-server/mcp/jsonrpc.go 
b/llm/book-flight/go-server/mcp/jsonrpc.go
new file mode 100644
index 00000000..d5682577
--- /dev/null
+++ b/llm/book-flight/go-server/mcp/jsonrpc.go
@@ -0,0 +1,107 @@
+/*
+ * 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 mcp
+
+const (
+       jsonrpc = "2.0" // jsonrpc version
+)
+
+type RequestRPC struct {
+       // JsonRPC specifies the JSON-RPC version. It MUST be exactly "2.0".
+       JsonRPC string `json:"jsonrpc"` // Explicitly named "jsonrpc" in JSON
+       // Method is a string containing the name of the method to be invoked.
+       // Method names that begin with a lowercase letter are reserved for
+       // system-defined methods and MUST NOT be used for custom methods.
+       Method string `json:"method"`
+       // Params is a structured value that holds the parameter values to be 
used
+       // during the invocation of the method. This member MAY be omitted.
+       Params map[string]any `json:"params,omitempty"` // Omitempty to skip if 
nil
+       // ID is an identifier established by the client that MUST contain a 
String,
+       // Number, or Null value if included. If not included it is assumed to 
be a
+       // notification. The value SHOULD normally not be Null [1] and Numbers 
SHOULD
+       // NOT contain fractional parts [2].
+       // [1] Using Null as a value for the id member in a Request object is
+       // discouraged, as there are no benefits and it introduces ambiguities
+       // when differentiating between Requests, Notifications, and Response
+       // objects.
+       // [2] Fractional parts SHOULD NOT be used as there is no clear
+       // interoperable way to represent them across all systems.
+       ID string `json:"id,omitempty"` // Omitempty to skip if empty
+}
+
+// NewRequestRPC creates a new RequestRPC with the JsonRPC field set to "2.0".
+func NewRequestRPC(method string, params map[string]any, id string) 
*RequestRPC {
+       return &RequestRPC{
+               JsonRPC: jsonrpc,
+               Method:  method,
+               Params:  params,
+               ID:      id,
+       }
+}
+
+type ErrorRPC struct {
+       // A Number that indicates the error type that occurred.
+       // This MUST be an integer.
+       Code int64 `json:"code"`
+       // A String providing a short description of the error.
+       // The message SHOULD be limited to a concise single sentence.
+       Message string `json:"message"`
+       // A Primitive or Structured value that contains additional information 
about the error.
+       // This may be omitted.
+       // The value of this member is defined by the Server(e.g. detailed 
error information, nested errors etc.).
+       Data map[string]any `json:"data,omitempty"` // Omitempty to skip if nil
+}
+
+// NewErrorRPC creates a new ErrorRPC instance.
+func NewErrorRPC(code int64, message string, data map[string]any) *ErrorRPC {
+       return &ErrorRPC{
+               Code:    code,
+               Message: message,
+               Data:    data,
+       }
+}
+
+type ResponseRPC struct {
+       // JsonRPC specifies the JSON-RPC version. It MUST be exactly "2.0".
+       JsonRPC string `json:"jsonrpc"` // Explicitly named "jsonrpc" in JSON
+       // Result is the value that holds the result of the method invocation.
+       // This member MUST be present when there was no error invoking the
+       // method. The value of this member is determined by the method invoked
+       // on the Server.
+       Result map[string]any `json:"result,omitempty"` // Omitempty to skip if 
nil
+       // Error is an object containing information about the error that 
occurred
+       // during the invocation of the method. This member MUST be present when
+       // there was an error invoking the method.
+       Error *ErrorRPC `json:"error,omitempty"` // Omitempty to skip if nil
+       // ID is the identifier established by the Client that MUST contain a 
String,
+       // Number, or Null value if included in the request. It MUST be the 
same as
+       // the value of the id member in the Request object. If there was an 
error
+       // in detecting the id in the Request object (e.g. Parse error or 
Invalid
+       // Request), it MUST be Null.
+       ID string `json:"id,omitempty"` // Omitempty to skip if empty
+}
+
+// NewResponseRPC creates a new ResponseRPC with the JsonRPC field set to 
"2.0".
+func NewResponseRPC(result map[string]any, error *ErrorRPC, id string) 
*ResponseRPC {
+       return &ResponseRPC{
+               JsonRPC: jsonrpc,
+               Result:  result,
+               Error:   error,
+               ID:      id,
+       }
+}
diff --git a/llm/book-flight/go-server/model/llm.go 
b/llm/book-flight/go-server/model/llm.go
new file mode 100644
index 00000000..b7f04a73
--- /dev/null
+++ b/llm/book-flight/go-server/model/llm.go
@@ -0,0 +1,25 @@
+/*
+ * 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 model
+
+import "context"
+
+type LLM interface {
+       Call(ctx context.Context, input string, opts ...Option) (string, error)
+       Stream(ctx context.Context, input string, opts ...Option) (string, 
error)
+       Invoke(ctx context.Context, input string, opts ...Option) (string, 
error)
+}
diff --git a/llm/book-flight/go-server/model/llm_utility.go 
b/llm/book-flight/go-server/model/llm_utility.go
new file mode 100644
index 00000000..180ac789
--- /dev/null
+++ b/llm/book-flight/go-server/model/llm_utility.go
@@ -0,0 +1,43 @@
+/*
+ * 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 model
+
+import "regexp"
+
+func RemoveThink(text string) string {
+       re := regexp.MustCompile(`<think>[\s\S]*</think>`)
+       matches := re.FindAllString(text, -1)
+       if len(matches) > 0 {
+               text = text[len(matches[0]):]
+       }
+       return text
+}
+
+func MergeMaps[K comparable, V any](m1, m2 map[K]V) map[K]V {
+       merged := make(map[K]V)
+
+       for k, v := range m1 {
+               merged[k] = v
+       }
+
+       for k, v := range m2 {
+               merged[k] = v
+       }
+
+       return merged
+}
diff --git a/llm/book-flight/go-server/model/ollama/ollama.go 
b/llm/book-flight/go-server/model/ollama/ollama.go
new file mode 100644
index 00000000..72cb94b3
--- /dev/null
+++ b/llm/book-flight/go-server/model/ollama/ollama.go
@@ -0,0 +1,187 @@
+/*
+ * 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 ollama
+
+import (
+       "context"
+       "log"
+       "net/http"
+       "net/url"
+       "strings"
+)
+
+import (
+       "github.com/ollama/ollama/api"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/model"
+)
+
+type (
+       ImageData = api.ImageData
+       LLMUrl    struct {
+               scheam string // [scheam:]//host
+               host   string // host:port
+       }
+)
+
+func NewURL(url string) LLMUrl {
+       scheam_host := strings.Split(url, "://")
+       return LLMUrl{scheam: scheam_host[0], host: scheam_host[1]}
+}
+
+/*
+Options:
+
+       num_keep:
+       seed:
+       num_predict:
+       top_k:
+       top_p:
+       min_p:
+       typical_p:
+       repeat_last_n:
+       temperature:
+       repeat_penalty:
+       presence_penalty:
+       frequency_penalty:
+       mirostat:
+       mirostat_tau:
+       mirostat_eta:
+       penalize_newline:
+       stop:
+       nama:
+       num_ctx:
+       num_batch:
+       num_gpu:
+       main_gpu:
+       low_vram:
+       vocab_only:
+       vuse_mmap:
+       use_mlock:
+       num_thread:
+*/
+type LLMOllama struct {
+       llmUrl    LLMUrl
+       Model     string
+       Url       string
+       Prompt    string
+       stream    *bool
+       suffix    string
+       images    []ImageData
+       format    string
+       system    string
+       template  string
+       raw       bool
+       keepAlive string
+       options   []any
+}
+
+func NewLLMOllama(model string, url string) *LLMOllama {
+       return &LLMOllama{
+               llmUrl: NewURL(url),
+               Model:  model,
+               Url:    url,
+       }
+}
+
+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)
+
+       optss := model.NewOptions(opts...)
+
+       // By default, GenerateRequest is streaming.
+       req := &api.GenerateRequest{
+               Model:   llm.Model,
+               Prompt:  input,
+               Stream:  llm.stream,
+               Suffix:  llm.suffix,
+               Options: optss.Opts,
+       }
+
+       var respBuilder strings.Builder // Use strings.Builder
+       respFunc := func(resp api.GenerateResponse) error {
+               respBuilder.WriteString(resp.Response)
+               return optss.CallOpt(resp.Response)
+       }
+
+       err := client.Generate(ctx, req, respFunc)
+       if err != nil {
+               log.Fatal(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) {
+       client := api.NewClient(&url.URL{Scheme: llm.llmUrl.scheam, Host: 
llm.llmUrl.host}, http.DefaultClient)
+
+       // Messages
+       messages := []api.Message{
+               api.Message{
+                       Role:    "system",
+                       Content: llm.system,
+               },
+               api.Message{
+                       Role:    "user",
+                       Content: "Provide very brief, concise responses",
+               },
+               api.Message{
+                       Role:    "assistant",
+                       Content: "Provide very brief, concise responses",
+               },
+               api.Message{
+                       Role:    "user",
+                       Content: input,
+                       Images:  llm.images,
+               },
+       }
+
+       // Options
+       optss := model.NewOptions(opts...)
+
+       // ChatRequest
+       req := &api.ChatRequest{
+               Model:    llm.Model,
+               Stream:   llm.stream,
+               Messages: messages,
+               Options:  optss.Opts,
+       }
+
+       var respBuilder strings.Builder // Use strings.Builder
+       respFunc := func(resp api.ChatResponse) error {
+               respBuilder.WriteString(resp.Message.Content)
+               if optss.CallOpt == nil {
+                       return nil
+               }
+               return optss.CallOpt(resp.Message.Content)
+       }
+
+       err := client.Chat(ctx, req, respFunc)
+       if err != nil {
+               log.Fatal(err)
+               return "", err
+       }
+
+       return respBuilder.String(), nil
+}
diff --git a/llm/book-flight/go-server/model/ollama/options.go 
b/llm/book-flight/go-server/model/ollama/options.go
new file mode 100644
index 00000000..03eee75c
--- /dev/null
+++ b/llm/book-flight/go-server/model/ollama/options.go
@@ -0,0 +1,134 @@
+/*
+ * 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 ollama
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/model"
+)
+
+func WithStreamingFunc(fn model.CallFunc) model.Option {
+       return &fn
+}
+
+func WithNumberKeep(numKeep int64) model.Option {
+       return map[string]any{"num_keep": numKeep}
+}
+
+func WithSeed(seed int64) model.Option {
+       return map[string]any{"seed": seed}
+}
+
+func WithNumProdict(num_predict int64) model.Option {
+       return map[string]any{"num_predict": num_predict}
+}
+
+func WithTopK(topK int64) model.Option {
+       return map[string]any{"top_k": topK}
+}
+
+func WithTopP(top_p float64) model.Option {
+       return map[string]any{"top_p": top_p}
+}
+
+func WithMinP(min_p float64) model.Option {
+       return map[string]any{"min_p": min_p}
+}
+
+func WithTypicalP(typical_p float64) model.Option {
+       return map[string]any{"typical_p": typical_p}
+}
+
+func WithRepeatLastN(repeat_last_n int64) model.Option {
+       return map[string]any{"repeat_last_n": repeat_last_n}
+}
+
+func WithTemperature(temperature float64) model.Option {
+       return map[string]any{"temperature": temperature}
+}
+
+func WithRepeatPenalty(repeat_penalty float64) model.Option {
+       return map[string]any{"repeat_penalty": repeat_penalty}
+}
+
+func WithPresencePenalty(presence_penalty float64) model.Option {
+       return map[string]any{"presence_penalty": presence_penalty}
+}
+
+func WithFrequencyPenalty(frequency_penalty float64) model.Option {
+       return map[string]any{"frequency_penalty": frequency_penalty}
+}
+
+func WithMirostat(mirostat int64) model.Option {
+       return map[string]any{"mirostat": mirostat}
+}
+
+func WithMirostatTau(mirostat_tau float64) model.Option {
+       return map[string]any{"mirostat_tau": mirostat_tau}
+}
+
+func WithMirostatEta(mirostat_eta float64) model.Option {
+       return map[string]any{"mirostat_eta": mirostat_eta}
+}
+
+func WithPenalizeNewline(penalize_newline bool) model.Option {
+       return map[string]any{"penalize_newline": penalize_newline}
+}
+
+func WithStop(stop []string) model.Option {
+       return map[string]any{"stop": stop}
+}
+
+func WithNama(nama bool) model.Option {
+       return map[string]any{"nama": nama}
+}
+
+// Default: 1024
+func WithNumberContext(numCtx int64) model.Option {
+       return map[string]any{"num_ctx": numCtx}
+}
+
+func WithNumberBatch(num_batch int64) model.Option {
+       return map[string]any{"num_batch": num_batch}
+}
+
+func WithNumberGPU(num_gpu int64) model.Option {
+       return map[string]any{"num_gpu": num_gpu}
+}
+
+func WithMainGPU(main_gpu int64) model.Option {
+       return map[string]any{"main_gpu": main_gpu}
+}
+
+func WithLowVram(low_vram bool) model.Option {
+       return map[string]any{"low_vram": low_vram}
+}
+
+func WithVocabOnly(vocab_only bool) model.Option {
+       return map[string]any{"vocab_only": vocab_only}
+}
+
+func WithVuseMmap(vuse_mmap bool) model.Option {
+       return map[string]any{"vuse_mmap": vuse_mmap}
+}
+
+func WithUseMLock(use_mlock bool) model.Option {
+       return map[string]any{"use_mlock": use_mlock}
+}
+
+func WithNumberThread(num_thread int64) model.Option {
+       return map[string]any{"num_thread": num_thread}
+}
diff --git a/llm/book-flight/go-server/model/option.go 
b/llm/book-flight/go-server/model/option.go
new file mode 100644
index 00000000..2b7e4992
--- /dev/null
+++ b/llm/book-flight/go-server/model/option.go
@@ -0,0 +1,47 @@
+/*
+ * 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 model
+
+type (
+       CallFunc func(input string) error
+       Option   = any
+       Options  struct {
+               CallOpt CallFunc
+               Opts    map[string]any
+       }
+)
+
+func NewOptions(opts ...Option) Options {
+       var respFunc CallFunc
+       optMap := make(map[string]any)
+       if len(opts) > 0 {
+               for _, opt := range opts {
+                       if fn, ok := opt.(*CallFunc); ok {
+                               respFunc = *fn
+                       } else if optm, ok := opt.(map[string]any); ok {
+                               for k, v := range optm {
+                                       optMap[k] = v
+                               }
+                       }
+               }
+       }
+
+       return Options{
+               CallOpt: respFunc,
+               Opts:    optMap,
+       }
+}
diff --git a/llm/book-flight/go-server/prompts/prompt.go 
b/llm/book-flight/go-server/prompts/prompt.go
new file mode 100644
index 00000000..419117a0
--- /dev/null
+++ b/llm/book-flight/go-server/prompts/prompt.go
@@ -0,0 +1,40 @@
+/*
+ * 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 prompts
+
+import (
+       "encoding/json"
+       "fmt"
+       "strings"
+)
+
+func CreatePrompt(prompt string, config map[string]any) string {
+       // config
+       for k, v := range config {
+               switch v.(type) {
+               case map[string]any:
+               case []map[string]any:
+                       vstr, _ := json.Marshal(v)
+                       prompt = strings.ReplaceAll(prompt, "{"+k+"}", 
string(vstr))
+               default:
+                       prompt = strings.ReplaceAll(prompt, "{"+k+"}", 
fmt.Sprintln(v))
+               }
+       }
+
+       return prompt
+}
diff --git a/llm/book-flight/go-server/tools/bookingflight/booking_tools.go 
b/llm/book-flight/go-server/tools/bookingflight/booking_tools.go
new file mode 100644
index 00000000..6970d077
--- /dev/null
+++ b/llm/book-flight/go-server/tools/bookingflight/booking_tools.go
@@ -0,0 +1,155 @@
+/*
+ * 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 bookingflight
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+)
+
+import (
+       "github.com/apache/dubbo-go-samples/llm/book-flight/go-server/tools"
+)
+
+var (
+       date string
+)
+
+/*
+SearchFlightTicketTool
+*/
+type SearchFlightTicketTool struct {
+       tools.BaseTool
+       Origin             string `json:"origin" validate:"required"`
+       Destination        string `json:"destination" validate:"required"`
+       Date               string `json:"date" validate:"required"`
+       DepartureTimeStart string `json:"departure_time_start"`
+       DepartureTimeEnd   string `json:"departure_time_end"`
+}
+
+// origin string, destination string, date string, departureTimeStart string, 
departureTimeEnd string
+func (stt *SearchFlightTicketTool) Call(ctx context.Context, input string) 
(string, error) {
+       err := json.Unmarshal([]byte(input), stt)
+       if err != nil {
+               return fmt.Sprintf("Error: %v", err), err
+       }
+
+       return stt.searchFlightTicket()
+}
+
+func (stt *SearchFlightTicketTool) searchFlightTicket() (string, error) {
+       // Only the departure point is verified here, and other information is 
not verified
+       if stt.Origin != "北京" {
+               return "No relevant content was found", nil
+       }
+
+       date = stt.Date
+       rst := flightInformation()
+       rst_json, err := json.Marshal(rst)
+       return string(rst_json), err
+}
+
+/*
+PurchaseFlightTicketTool
+*/
+type PurchaseFlightTicketTool struct {
+       tools.BaseTool
+       FlightNumber string `json:"flight_number" validate:"required"`
+}
+
+func (ptt *PurchaseFlightTicketTool) Call(ctx context.Context, input string) 
(string, error) {
+       err := json.Unmarshal([]byte(input), &ptt)
+       if err != nil {
+               return fmt.Sprintf("Error: %v", err), err
+       }
+
+       return ptt.purchaseFlightTicket()
+}
+
+func (ptt *PurchaseFlightTicketTool) purchaseFlightTicket() (string, error) {
+       flightInfo := flightInformation()
+       for _, info := range flightInfo {
+               if ptt.FlightNumber == info["flight_number"] {
+                       info["message"] = "Successful purchase."
+                       rst_json, err := json.Marshal(info)
+                       return string(rst_json), err
+               }
+       }
+
+       return fmt.Sprintf("The flight was not found: %v", ptt.FlightNumber), 
nil
+}
+
+func flightInformation() []map[string]string {
+       return []map[string]string{
+               {
+                       "flight_number":  "MU5100",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 07:00", date),
+                       "arrival_time":   fmt.Sprintf("%v 09:15", date),
+                       "price":          "900.00",
+                       "seat_type":      "头等舱",
+               },
+               {
+                       "flight_number":  "MU6865",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 07:20", date),
+                       "arrival_time":   fmt.Sprintf("%v 09:25", date),
+                       "price":          "1160.00",
+                       "seat_type":      "头等舱",
+               },
+               {
+                       "flight_number":  "HM7601",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 07:30", date),
+                       "arrival_time":   fmt.Sprintf("%v 09:55", date),
+                       "price":          "1080.00",
+                       "seat_type":      "普通舱",
+               },
+               {
+                       "flight_number":  "CA1515",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 15:45", date),
+                       "arrival_time":   fmt.Sprintf("%v 17:55", date),
+                       "price":          "1080.00",
+                       "seat_type":      "普通舱",
+               },
+               {
+                       "flight_number":  "GS9012",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 19:00", date),
+                       "arrival_time":   fmt.Sprintf("%v 23:00", date),
+                       "price":          "1250.00",
+                       "seat_type":      "头等舱",
+               },
+               {
+                       "flight_number":  "GS9013",
+                       "origin":         "北京",
+                       "destination":    "上海",
+                       "departure_time": fmt.Sprintf("%v 18:30", date),
+                       "arrival_time":   fmt.Sprintf("%v 22:00", date),
+                       "price":          "1200.00",
+                       "seat_type":      "头等舱",
+               },
+       }
+}
diff --git a/llm/book-flight/go-server/tools/tool.go 
b/llm/book-flight/go-server/tools/tool.go
new file mode 100644
index 00000000..028fab62
--- /dev/null
+++ b/llm/book-flight/go-server/tools/tool.go
@@ -0,0 +1,38 @@
+/*
+ * 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 tools
+
+import "context"
+
+// Tool is a tool for the llm agent to interact with different applications.
+type Tool interface {
+       Name() string
+       Description() string
+       Call(ctx context.Context, input string) (string, error)
+}
+
+// Tools is the manager of the toolkit, mainly providing descriptions of
+// the tools and detailed descriptions of the toolkit.
+type Tools interface {
+       // toolkit description
+       Description() string
+       // all tools description
+       ToolsDescription() string
+       // Query tool, or return nil if it does not exist.
+       QueryTool(method string) Tool
+}
diff --git a/llm/book-flight/go-server/tools/tool_base.go 
b/llm/book-flight/go-server/tools/tool_base.go
new file mode 100644
index 00000000..3442f4be
--- /dev/null
+++ b/llm/book-flight/go-server/tools/tool_base.go
@@ -0,0 +1,160 @@
+/*
+ * 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 tools
+
+import (
+       "fmt"
+       "reflect"
+       "strings"
+)
+
+type BaseTool struct {
+       id             string
+       name           string
+       description    string
+       requestParams  string
+       responseParams string
+       introduction   string
+}
+
+func NewBaseTool(name, description, requestParams, id string) BaseTool {
+       return BaseTool{
+               name:          name,
+               description:   description,
+               requestParams: requestParams,
+               id:            id,
+       }
+}
+
+func (b BaseTool) ID() string   { return b.id }
+func (b BaseTool) Name() string { return b.name }
+func (b BaseTool) Description() string {
+       return b.name + b.RequestParams() + " - " + b.description + "\n"
+}
+func (b *BaseTool) RequestParams() string {
+       if b.requestParams == "" {
+               b.requestParams = scanStructKeys(b)
+       }
+       return b.requestParams
+}
+func (b *BaseTool) ResponseParams() string { return b.responseParams }
+func (b BaseTool) Introduction() string    { return b.introduction }
+
+func scanStructKeys(obj interface{}) string {
+       if obj == nil {
+               return "()"
+       }
+
+       t := reflect.TypeOf(obj)
+       if t.Kind() == reflect.Ptr {
+               // If it is a pointer, get the value it points to
+               t = t.Elem()
+       }
+
+       if t.Kind() != reflect.Struct {
+               fmt.Println("scanStructKeys: not a struct type")
+               return "()"
+       }
+
+       keys := []string{}
+       for i := 0; i < t.NumField(); i++ {
+               field := t.Field(i)
+               jsonTag := field.Tag.Get("json")
+               jsonKey := strings.Split(jsonTag, ",")[0]
+
+               if field.Name == "BaseTool" {
+                       continue
+               }
+
+               // If the JSON tag is empty, use the structure field name
+               if jsonKey == "" {
+                       jsonKey = field.Name
+               }
+
+               keys = append(keys, jsonKey)
+       }
+
+       rst := "(" + strings.Join(keys, ", ") + ")"
+       return rst
+}
+
+// Toolkit is the manager of the toolkit, mainly providing descriptions of
+// the tools and detailed descriptions of the toolkit.
+type Toolkit struct {
+       tools       []Tool
+       toolMap     map[string]*Tool
+       description string
+}
+
+func NewToolkit(tools []Tool, description string) Toolkit {
+       toolkit := Toolkit{
+               tools: tools,
+               toolMap: func() map[string]*Tool {
+                       toolMap := make(map[string]*Tool)
+                       for _, tool := range tools {
+                               // Use the current tool pointer directly
+                               toolMap[tool.Name()] = &tool
+                       }
+                       return toolMap
+               }(),
+               description: description,
+       }
+
+       return toolkit
+}
+
+// Return Toolkit Description
+func (t Toolkit) Description() string {
+       return t.description
+}
+
+// Returns description of all tools
+func (t Toolkit) ToolsDescription() string {
+       var descBuilder strings.Builder // Use strings.Builder
+       for _, tool := range t.tools {
+               descBuilder.WriteString(tool.Description())
+       }
+       return descBuilder.String()
+}
+
+// Query Tool in the Toolkit
+func (t Toolkit) QueryTool(method string) Tool {
+       value, ok := t.toolMap[method]
+       if !ok {
+               return nil
+       }
+       return *value
+}
+
+// CreateTool
+func CreateTool[T any](name, description, id string) (*T, error) {
+       tool := new(T)
+       base := NewBaseTool(name, description, scanStructKeys(tool), id)
+
+       v := reflect.ValueOf(tool).Elem()
+       field := v.FieldByName("BaseTool")
+       if !field.IsValid() {
+               return nil, fmt.Errorf("CreateTool: %T does not have an 
embedded BaseTool field", tool)
+       }
+       if !field.CanSet() {
+               return nil, fmt.Errorf("CreateTool: cannot set BaseTool field 
on %T", tool)
+       }
+
+       field.Set(reflect.ValueOf(base))
+       return tool, nil
+}
diff --git a/llm/book-flight/proto/chat.pb.go b/llm/book-flight/proto/chat.pb.go
new file mode 100644
index 00000000..1fc6a1c7
--- /dev/null
+++ b/llm/book-flight/proto/chat.pb.go
@@ -0,0 +1,332 @@
+//
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+//     protoc-gen-go v1.34.1
+//     protoc        v5.29.3
+// source: chat.proto
+
+package chat
+
+import (
+       protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+       protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+       reflect "reflect"
+       sync "sync"
+)
+
+const (
+       // Verify that this generated code is sufficiently up-to-date.
+       _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+       // Verify that runtime/protoimpl is sufficiently up-to-date.
+       _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ChatRequest struct {
+       state         protoimpl.MessageState
+       sizeCache     protoimpl.SizeCache
+       unknownFields protoimpl.UnknownFields
+
+       Messages []*ChatMessage `protobuf:"bytes,1,rep,name=messages,proto3" 
json:"messages,omitempty"`
+}
+
+func (x *ChatRequest) Reset() {
+       *x = ChatRequest{}
+       if protoimpl.UnsafeEnabled {
+               mi := &file_chat_proto_msgTypes[0]
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               ms.StoreMessageInfo(mi)
+       }
+}
+
+func (x *ChatRequest) String() string {
+       return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatRequest) ProtoMessage() {}
+
+func (x *ChatRequest) ProtoReflect() protoreflect.Message {
+       mi := &file_chat_proto_msgTypes[0]
+       if protoimpl.UnsafeEnabled && x != nil {
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               if ms.LoadMessageInfo() == nil {
+                       ms.StoreMessageInfo(mi)
+               }
+               return ms
+       }
+       return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatRequest.ProtoReflect.Descriptor instead.
+func (*ChatRequest) Descriptor() ([]byte, []int) {
+       return file_chat_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ChatRequest) GetMessages() []*ChatMessage {
+       if x != nil {
+               return x.Messages
+       }
+       return nil
+}
+
+type ChatMessage struct {
+       state         protoimpl.MessageState
+       sizeCache     protoimpl.SizeCache
+       unknownFields protoimpl.UnknownFields
+
+       Role    string `protobuf:"bytes,1,opt,name=role,proto3" 
json:"role,omitempty"` // "human" or "ai"
+       Content string `protobuf:"bytes,2,opt,name=content,proto3" 
json:"content,omitempty"`
+       Bin     []byte `protobuf:"bytes,3,opt,name=bin,proto3" 
json:"bin,omitempty"` // binary file
+       Record  string `protobuf:"bytes,4,opt,name=record,proto3" 
json:"record,omitempty"`
+}
+
+func (x *ChatMessage) Reset() {
+       *x = ChatMessage{}
+       if protoimpl.UnsafeEnabled {
+               mi := &file_chat_proto_msgTypes[1]
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               ms.StoreMessageInfo(mi)
+       }
+}
+
+func (x *ChatMessage) String() string {
+       return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatMessage) ProtoMessage() {}
+
+func (x *ChatMessage) ProtoReflect() protoreflect.Message {
+       mi := &file_chat_proto_msgTypes[1]
+       if protoimpl.UnsafeEnabled && x != nil {
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               if ms.LoadMessageInfo() == nil {
+                       ms.StoreMessageInfo(mi)
+               }
+               return ms
+       }
+       return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
+func (*ChatMessage) Descriptor() ([]byte, []int) {
+       return file_chat_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ChatMessage) GetRole() string {
+       if x != nil {
+               return x.Role
+       }
+       return ""
+}
+
+func (x *ChatMessage) GetContent() string {
+       if x != nil {
+               return x.Content
+       }
+       return ""
+}
+
+func (x *ChatMessage) GetBin() []byte {
+       if x != nil {
+               return x.Bin
+       }
+       return nil
+}
+
+func (x *ChatMessage) GetRecord() string {
+       if x != nil {
+               return x.Record
+       }
+       return ""
+}
+
+type ChatResponse struct {
+       state         protoimpl.MessageState
+       sizeCache     protoimpl.SizeCache
+       unknownFields protoimpl.UnknownFields
+
+       Content string `protobuf:"bytes,1,opt,name=content,proto3" 
json:"content,omitempty"`
+       Record  string `protobuf:"bytes,2,opt,name=record,proto3" 
json:"record,omitempty"`
+}
+
+func (x *ChatResponse) Reset() {
+       *x = ChatResponse{}
+       if protoimpl.UnsafeEnabled {
+               mi := &file_chat_proto_msgTypes[2]
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               ms.StoreMessageInfo(mi)
+       }
+}
+
+func (x *ChatResponse) String() string {
+       return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatResponse) ProtoMessage() {}
+
+func (x *ChatResponse) ProtoReflect() protoreflect.Message {
+       mi := &file_chat_proto_msgTypes[2]
+       if protoimpl.UnsafeEnabled && x != nil {
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               if ms.LoadMessageInfo() == nil {
+                       ms.StoreMessageInfo(mi)
+               }
+               return ms
+       }
+       return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatResponse.ProtoReflect.Descriptor instead.
+func (*ChatResponse) Descriptor() ([]byte, []int) {
+       return file_chat_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ChatResponse) GetContent() string {
+       if x != nil {
+               return x.Content
+       }
+       return ""
+}
+
+func (x *ChatResponse) GetRecord() string {
+       if x != nil {
+               return x.Record
+       }
+       return ""
+}
+
+var File_chat_proto protoreflect.FileDescriptor
+
+var file_chat_proto_rawDesc = []byte{
+       0x0a, 0x0a, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 
0x12, 0x04, 0x63, 0x68,
+       0x61, 0x74, 0x22, 0x3c, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 
0x71, 0x75, 0x65, 0x73,
+       0x74, 0x12, 0x2d, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 
0x73, 0x18, 0x01, 0x20,
+       0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x43, 
0x68, 0x61, 0x74, 0x4d,
+       0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 
0x61, 0x67, 0x65, 0x73,
+       0x22, 0x65, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 
0x61, 0x67, 0x65, 0x12,
+       0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 
0x09, 0x52, 0x04, 0x72,
+       0x6f, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 
0x6e, 0x74, 0x18, 0x02,
+       0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 
0x74, 0x12, 0x10, 0x0a,
+       0x03, 0x62, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 
0x62, 0x69, 0x6e, 0x12,
+       0x16, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 
0x01, 0x28, 0x09, 0x52,
+       0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x22, 0x40, 0x0a, 0x0c, 0x43, 
0x68, 0x61, 0x74, 0x52,
+       0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 
0x6f, 0x6e, 0x74, 0x65,
+       0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 
0x6e, 0x74, 0x65, 0x6e,
+       0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 
0x02, 0x20, 0x01, 0x28,
+       0x09, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x32, 0x40, 0x0a, 
0x0b, 0x43, 0x68, 0x61,
+       0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x04, 
0x43, 0x68, 0x61, 0x74,
+       0x12, 0x11, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x74, 
0x52, 0x65, 0x71, 0x75,
+       0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x43, 
0x68, 0x61, 0x74, 0x52,
+       0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 
0x3f, 0x5a, 0x3d, 0x67,
+       0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 
0x61, 0x63, 0x68, 0x65,
+       0x2f, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2d, 0x67, 0x6f, 0x2d, 0x73, 0x61, 
0x6d, 0x70, 0x6c, 0x65,
+       0x73, 0x2f, 0x6c, 0x6c, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6b, 0x2d, 0x66, 
0x6c, 0x69, 0x67, 0x68,
+       0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x63, 0x68, 0x61, 0x74, 
0x62, 0x06, 0x70, 0x72,
+       0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+       file_chat_proto_rawDescOnce sync.Once
+       file_chat_proto_rawDescData = file_chat_proto_rawDesc
+)
+
+func file_chat_proto_rawDescGZIP() []byte {
+       file_chat_proto_rawDescOnce.Do(func() {
+               file_chat_proto_rawDescData = 
protoimpl.X.CompressGZIP(file_chat_proto_rawDescData)
+       })
+       return file_chat_proto_rawDescData
+}
+
+var file_chat_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_chat_proto_goTypes = []interface{}{
+       (*ChatRequest)(nil),  // 0: chat.ChatRequest
+       (*ChatMessage)(nil),  // 1: chat.ChatMessage
+       (*ChatResponse)(nil), // 2: chat.ChatResponse
+}
+var file_chat_proto_depIdxs = []int32{
+       1, // 0: chat.ChatRequest.messages:type_name -> chat.ChatMessage
+       0, // 1: chat.ChatService.Chat:input_type -> chat.ChatRequest
+       2, // 2: chat.ChatService.Chat:output_type -> chat.ChatResponse
+       2, // [2:3] is the sub-list for method output_type
+       1, // [1:2] is the sub-list for method input_type
+       1, // [1:1] is the sub-list for extension type_name
+       1, // [1:1] is the sub-list for extension extendee
+       0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_chat_proto_init() }
+func file_chat_proto_init() {
+       if File_chat_proto != nil {
+               return
+       }
+       if !protoimpl.UnsafeEnabled {
+               file_chat_proto_msgTypes[0].Exporter = func(v interface{}, i 
int) interface{} {
+                       switch v := v.(*ChatRequest); i {
+                       case 0:
+                               return &v.state
+                       case 1:
+                               return &v.sizeCache
+                       case 2:
+                               return &v.unknownFields
+                       default:
+                               return nil
+                       }
+               }
+               file_chat_proto_msgTypes[1].Exporter = func(v interface{}, i 
int) interface{} {
+                       switch v := v.(*ChatMessage); i {
+                       case 0:
+                               return &v.state
+                       case 1:
+                               return &v.sizeCache
+                       case 2:
+                               return &v.unknownFields
+                       default:
+                               return nil
+                       }
+               }
+               file_chat_proto_msgTypes[2].Exporter = func(v interface{}, i 
int) interface{} {
+                       switch v := v.(*ChatResponse); i {
+                       case 0:
+                               return &v.state
+                       case 1:
+                               return &v.sizeCache
+                       case 2:
+                               return &v.unknownFields
+                       default:
+                               return nil
+                       }
+               }
+       }
+       type x struct{}
+       out := protoimpl.TypeBuilder{
+               File: protoimpl.DescBuilder{
+                       GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+                       RawDescriptor: file_chat_proto_rawDesc,
+                       NumEnums:      0,
+                       NumMessages:   3,
+                       NumExtensions: 0,
+                       NumServices:   1,
+               },
+               GoTypes:           file_chat_proto_goTypes,
+               DependencyIndexes: file_chat_proto_depIdxs,
+               MessageInfos:      file_chat_proto_msgTypes,
+       }.Build()
+       File_chat_proto = out.File
+       file_chat_proto_rawDesc = nil
+       file_chat_proto_goTypes = nil
+       file_chat_proto_depIdxs = nil
+}
diff --git a/llm/book-flight/proto/chat.proto b/llm/book-flight/proto/chat.proto
new file mode 100644
index 00000000..0cdd91d1
--- /dev/null
+++ b/llm/book-flight/proto/chat.proto
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package chat;
+
+option go_package = 
"github.com/apache/dubbo-go-samples/llm/book-flight/proto;chat";
+
+message ChatRequest {
+  repeated ChatMessage messages = 1;
+}
+
+message ChatMessage {
+  string role = 1;  // "human" or "ai"
+  string content = 2;
+  bytes bin = 3;  // binary file
+  string record = 4;
+}
+
+message ChatResponse {
+  string content = 1;
+  string record = 2;
+}
+
+service ChatService {
+  rpc Chat(ChatRequest) returns (stream ChatResponse) {}
+}
\ No newline at end of file
diff --git a/llm/book-flight/proto/chat.triple.go 
b/llm/book-flight/proto/chat.triple.go
new file mode 100644
index 00000000..bdb7eb2f
--- /dev/null
+++ b/llm/book-flight/proto/chat.triple.go
@@ -0,0 +1,177 @@
+// Code generated by protoc-gen-triple. DO NOT EDIT.
+//
+// Source: chat.proto
+package chat
+
+import (
+       "context"
+       "net/http"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3"
+       "dubbo.apache.org/dubbo-go/v3/client"
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+       "dubbo.apache.org/dubbo-go/v3/server"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the 
Triple package
+// are compatible. If you get a compiler error that this constant is not 
defined, this code was
+// generated with a version of Triple newer than the one compiled into your 
binary. You can fix the
+// problem by either regenerating this code with an older version of Triple or 
updating the Triple
+// version compiled into your binary.
+const _ = triple_protocol.IsAtLeastVersion0_1_0
+
+const (
+       // ChatServiceName is the fully-qualified name of the ChatService 
service.
+       ChatServiceName = "chat.ChatService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this 
package. They're
+// exposed at runtime as procedure and as the final two segments of the HTTP 
route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these 
constants to
+// reflection-formatted method names, remove the leading slash and convert the 
remaining slash to a
+// period.
+const (
+       // ChatServiceChatProcedure is the fully-qualified name of the 
ChatService's Chat RPC.
+       ChatServiceChatProcedure = "/chat.ChatService/Chat"
+)
+
+var (
+       _ ChatService = (*ChatServiceImpl)(nil)
+
+       _ ChatService_ChatClient = (*ChatServiceChatClient)(nil)
+
+       _ ChatService_ChatServer = (*ChatServiceChatServer)(nil)
+)
+
+// ChatService is a client for the chat.ChatService service.
+type ChatService interface {
+       Chat(ctx context.Context, req *ChatRequest, opts ...client.CallOption) 
(ChatService_ChatClient, error)
+}
+
+// NewChatService constructs a client for the chat.ChatService service.
+func NewChatService(cli *client.Client, opts ...client.ReferenceOption) 
(ChatService, error) {
+       conn, err := cli.DialWithInfo("chat.ChatService", 
&ChatService_ClientInfo, opts...)
+       if err != nil {
+               return nil, err
+       }
+       return &ChatServiceImpl{
+               conn: conn,
+       }, nil
+}
+
+func SetConsumerChatService(srv common.RPCService) {
+       dubbo.SetConsumerServiceWithInfo(srv, &ChatService_ClientInfo)
+}
+
+// ChatServiceImpl implements ChatService.
+type ChatServiceImpl struct {
+       conn *client.Connection
+}
+
+func (c *ChatServiceImpl) Chat(ctx context.Context, req *ChatRequest, opts 
...client.CallOption) (ChatService_ChatClient, error) {
+       stream, err := c.conn.CallServerStream(ctx, req, "Chat", opts...)
+       if err != nil {
+               return nil, err
+       }
+       rawStream := stream.(*triple_protocol.ServerStreamForClient)
+       return &ChatServiceChatClient{rawStream}, nil
+}
+
+type ChatService_ChatClient interface {
+       Recv() bool
+       ResponseHeader() http.Header
+       ResponseTrailer() http.Header
+       Msg() *ChatResponse
+       Err() error
+       Conn() (triple_protocol.StreamingClientConn, error)
+       Close() error
+}
+
+type ChatServiceChatClient struct {
+       *triple_protocol.ServerStreamForClient
+}
+
+func (cli *ChatServiceChatClient) Recv() bool {
+       msg := new(ChatResponse)
+       return cli.ServerStreamForClient.Receive(msg)
+}
+
+func (cli *ChatServiceChatClient) Msg() *ChatResponse {
+       msg := cli.ServerStreamForClient.Msg()
+       if msg == nil {
+               return new(ChatResponse)
+       }
+       return msg.(*ChatResponse)
+}
+
+func (cli *ChatServiceChatClient) Conn() (triple_protocol.StreamingClientConn, 
error) {
+       return cli.ServerStreamForClient.Conn()
+}
+
+var ChatService_ClientInfo = client.ClientInfo{
+       InterfaceName: "chat.ChatService",
+       MethodNames:   []string{"Chat"},
+       ConnectionInjectFunc: func(dubboCliRaw interface{}, conn 
*client.Connection) {
+               dubboCli := dubboCliRaw.(*ChatServiceImpl)
+               dubboCli.conn = conn
+       },
+}
+
+// ChatServiceHandler is an implementation of the chat.ChatService service.
+type ChatServiceHandler interface {
+       Chat(context.Context, *ChatRequest, ChatService_ChatServer) error
+}
+
+func RegisterChatServiceHandler(srv *server.Server, hdlr ChatServiceHandler, 
opts ...server.ServiceOption) error {
+       return srv.Register(hdlr, &ChatService_ServiceInfo, opts...)
+}
+
+func SetProviderChatService(srv common.RPCService) {
+       dubbo.SetProviderServiceWithInfo(srv, &ChatService_ServiceInfo)
+}
+
+type ChatService_ChatServer interface {
+       Send(*ChatResponse) error
+       ResponseHeader() http.Header
+       ResponseTrailer() http.Header
+       Conn() triple_protocol.StreamingHandlerConn
+}
+
+type ChatServiceChatServer struct {
+       *triple_protocol.ServerStream
+}
+
+func (g *ChatServiceChatServer) Send(msg *ChatResponse) error {
+       return g.ServerStream.Send(msg)
+}
+
+var ChatService_ServiceInfo = server.ServiceInfo{
+       InterfaceName: "chat.ChatService",
+       ServiceType:   (*ChatServiceHandler)(nil),
+       Methods: []server.MethodInfo{
+               {
+                       Name: "Chat",
+                       Type: constant.CallServerStream,
+                       ReqInitFunc: func() interface{} {
+                               return new(ChatRequest)
+                       },
+                       StreamInitFunc: func(baseStream interface{}) 
interface{} {
+                               return 
&ChatServiceChatServer{baseStream.(*triple_protocol.ServerStream)}
+                       },
+                       MethodFunc: func(ctx context.Context, args 
[]interface{}, handler interface{}) (interface{}, error) {
+                               req := args[0].(*ChatRequest)
+                               stream := args[1].(ChatService_ChatServer)
+                               if err := 
handler.(ChatServiceHandler).Chat(ctx, req, stream); err != nil {
+                                       return nil, err
+                               }
+                               return nil, nil
+                       },
+               },
+       },
+}

Reply via email to