Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🏨 酒旅 PMS AI 助手

用人话查订单、改房态 —— 一个能跑起来、能读懂、能改造的 Function Calling 实战范例

Vue3 + .NET 10,约 20 个源文件,零配置(内存库)即可启动。 专为想搞懂「大模型怎么调用工具操作业务系统」的开发者准备。

.NET Vue TypeScript Microsoft.Extensions.AI License

中文 · 注释/提交/文档全中文 · 任意 OpenAI 兼容大模型(通义/智谱/OpenAI/本地)

界面预览:左侧自然语言对话,右侧房态看板
左侧说人话查订单/改房态 · 右侧房态看板 · 写操作完成后看板自动刷新

这是什么

一个最小但完整的酒店管理系统(PMS)AI 助手。你对它说人话:

「8203 房间清扫完了,设为可售」

它背后通过 Function Calling(工具调用) 把这句话翻译成结构化操作,调用业务层真正改掉数据库里的房态,再用自然语言回复你。

它不是又一个「贴个 ChatGPT 框」的玩具,而是想认真回答一个问题:

怎么让大模型安全地操作真实业务系统,而不是让它满嘴跑火车污染数据?

三个亮点

🔒 AI 碰不到数据库 大模型只能调两个工具,工具一律落到业务层。权限、状态机、审计全在业务层兜底——模型再怎么幻觉也污染不了数据。这是全项目最重要的一条架构红线。
🔬 手写 vs 框架,双路径对照 /api/chat 用框架自动跑工具调用循环(生产路径);/api/chat-manual 用一个手写 for 循环复现框架内部那套逻辑,并返回 trace 让你看清每一轮模型在想什么、调了什么工具、拿到什么结果。这是本项目最适合学习的部分。
🌊 真·流式 SSE 后端 text/event-stream,前端 fetch + ReadableStream 边收边渲染,写操作完成后看板自动刷新。

架构一览

Vue3 前端 ──SSE流式对话──▶ .NET 10 后端
  ├ AI 对话面板                ├ Chat 编排 (Microsoft.Extensions.AI)
  ├ 房态看板                   │    ├ Tool: 查询订单 ┐
  └ 订单列表                   │    └ Tool: 修改房态 ┘ 只能调业务层
                              ├ Domain Service (权限/状态机/审计兜底)
                              └ EF Core 10 → 内存库(演示) / PG·MySQL(生产)

一句话原则工具方法 → 业务服务 → DbContext,绝不让工具直接碰数据库。这条边界是新增任何 AI 能力时都不能破的底线。


🚀 三步跑起来

前置:.NET 10 SDK + Node.js 18+,以及一个 OpenAI 兼容大模型的 API Key。

1. 配置大模型

API Key 走环境变量(不进 git,更安全):

# bash / macOS / Linux
export Llm__ApiKey=sk-xxxx

# Windows PowerShell
$env:Llm__ApiKey="sk-xxxx"

BaseUrl / Modelbackend/PmsAi.Api/appsettings.json 里改。任意 OpenAI 兼容服务都行,只改这两项:

服务 BaseUrl Model 示例
通义千问 https://dashscope.aliyuncs.com/compatible-mode/v1 qwen-plus
智谱 GLM https://open.bigmodel.cn/api/paas/v4 glm-4
OpenAI https://api.openai.com/v1 gpt-4o
本地 Ollama http://localhost:11434/v1 你部署的模型

💡 Kimi/Moonshot 等默认开思考的模型,需在 appsettings.jsonLlm:DisableThinking 设为 true,否则多轮工具调用会报 400。标准 OpenAI 不要开。原理见下文「踩坑记录」。

2. 启动后端(端口 5080)

cd backend/PmsAi.Api
dotnet run

内存库自动灌入演示房间和订单。验证:浏览器打开 http://localhost:5080/api/rooms 应看到房态 JSON。

3. 启动前端(端口 5173)

cd frontend
npm install
npm run dev

打开 http://localhost:5173。左侧 AI 对话,右侧房态看板 / 订单列表。Vite 已把 /api 代理到 5080,无需额外跨域配置。

试试这些指令

查一下张伟的订单
今天有哪些已入住的订单?
8203房间清扫完了,设为可售
把8205设成维修中,原因是水管漏水

📚 你能学到什么

这个项目刻意做小,是为了让你一个下午读懂全部代码。读完你会理解:

  • Function Calling 到底是什么:不是魔法,就是「模型输出一段 JSON 说要调哪个函数 → 你的代码真的去调 → 把结果喂回模型 → 模型继续」的循环。
  • 怎么把「人话」路由到正确的函数:靠工具方法上的 [Description] 特性——它原样发给大模型,写得越清楚路由越准。调 AI 行为优先改描述,而不是加代码。
  • 怎么给 AI 套上安全护栏:让它只能调有限的几个工具,所有校验下沉到业务层。
  • 流式 SSE 前后端怎么配合:为什么用 fetch 而不是 EventSource(后者不支持 POST)。

🔍 重点:把「框架黑盒」拆开看

大多数教程直接用 .UseFunctionInvocation() 一行搞定,你却不知道里面发生了什么。本项目特意准备了对照组

/api/chat(生产) /api/chat-manual(教学)
工具调用循环 框架自动(.UseFunctionInvocation() 手写 for 循环,逐行可读
响应方式 流式 SSE 非流式,返回完整 trace
适合 上线用 看懂原理用

打开 Controllers/ManualChatController.cs,那段带详细注释的 for 循环,就是框架内部 FunctionInvokingChatClient 替你做的事——「按名字找工具 → 反序列化参数 → 真正执行 → 回灌结果 → 再问模型」。看懂它,你就真的懂 Function Calling 了。

建议的阅读顺序

  1. Ai/PmsTools.cs —— 暴露给 AI 的两个工具,先看 [Description] 怎么写
  2. Controllers/ManualChatController.cs —— 手写循环,理解工具调用的完整链路
  3. Services/RoomStatusService.cs —— 业务层兜底:房态状态机 + 审计日志
  4. Controllers/ChatController.cs + Program.cs —— 生产版怎么用框架 + 流式
  5. frontend/src/api/client.ts + stores/chat.ts —— 前端怎么读流、怎么触发刷新

📁 目录结构

pms-ai/
├── backend/PmsAi.Api/
│   ├── Program.cs                      # 启动 + DI + LLM 接入
│   ├── Models/                         # Room / Order / AuditLog(中文枚举贯穿全栈)
│   ├── Data/                           # DbContext + 种子数据
│   ├── Services/                       # 业务层(AI 的兜底:权限/状态机/审计)
│   ├── Ai/
│   │   ├── PmsTools.cs                 # ★ 暴露给 LLM 的两个工具
│   │   ├── ChatSessionStore.cs         # 按 sessionId 的会话记忆
│   │   └── DisableThinkingPolicy.cs    # 思考模型兼容补丁
│   └── Controllers/
│       ├── ChatController.cs           # /api/chat 流式(生产)
│       ├── ManualChatController.cs     # ★ /api/chat-manual 手写循环(教学)
│       └── PmsController.cs            # /api/rooms /api/orders 看板接口
└── frontend/src/
    ├── api/client.ts                   # SSE 流式读取
    ├── stores/chat.ts                  # 对话状态 + 写操作触发刷新
    └── views/                          # ChatAssistant / RoomBoard / OrderList

🛠 技术说明 & 设计取舍

  • AI 编排Microsoft.Extensions.AI + Microsoft.Extensions.AI.OpenAIUseFunctionInvocation() 自动处理工具调用循环。
  • 中文枚举贯穿全栈RoomStatus(可售/已占用/维修中/清扫中/锁房)、OrderStatus(已确认/已入住/已退房/已取消)故意用中文枚举成员——枚举值同时就是 AI 工具的参数取值,省掉来回翻译。也可用特性(Attribute)做值映射来实现同样效果,但本项目重点不在此,故从简。
  • 房态状态机:合法性校验集中在 RoomStatusService.UpdateAsync,目前硬规则是「已占用的房间不能直接置为可售(需先退房)」,每次成功修改写一条 AuditLog。新增流转规则就加在这一处。
  • 会话记忆ChatSessionStore 是按 sessionId 的内存字典,首次创建注入 system prompt。

⚠️ 踩坑记录

  • 思考模型 + 多轮工具调用冲突:Kimi 等默认开思考的模型,要求每条带工具调用的消息携带 reasoning_content,但框架回灌历史时会丢这个字段,导致 400「reasoning_content is missing」。解法是 DisableThinkingPolicy——给请求体注入 {"thinking":{"type":"disabled"}},由 Llm:DisableThinking 开关控制。
  • 写操作触发前端刷新:前端靠 chat store 的 dataVersion 计数器驱动看板刷新,判断「写操作」是硬匹配工具名 UpdateRoomStatus——新增写工具时记得同步 stores/chat.ts
  • 流式只能用 fetchEventSource 不支持 POST,所以前端用 fetch + ReadableStream 手动读 SSE 流。

🏗 从演示到生产的改造点

这个项目是「能跑的骨架」,离上线还差这些(每一条都是很好的练手课题):

当前(演示) 生产建议
EF Core 内存库 Npgsql(PG)或 Pomelo.MySql,加迁移
操作人写死 "AI助手" 接入登录 + JWT,工具里取真实操作人做权限校验
修改房态自动执行 写操作加二次确认:工具返回 pending,前端弹确认按钮再提交
会话存内存 换 Redis,支持多实例
无限流 /api/chat 加限流,控制 token 成本

🤝 参与 & 反馈

  • 觉得有帮助,点个 ⭐ Star 是最大的鼓励,也能让更多刚入行的小伙伴看到。
  • 发现 bug、有改进想法、想加新工具?欢迎提 IssuePR
  • 这个项目定位是教学范例,所以特别欢迎「这里我没看懂」类的提问——你的困惑能帮我把注释和文档写得更好。

📄 License

MIT © yangka

About

酒旅 PMS AI 助手:用自然语言查订单/改房态的 Function Calling 实战范例 · Vue3 + .NET 10

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages