打造 AI 驱动的 Git 提交规范助手:基于 React + Express + Ollama+langchain 的全栈实践
在现代软件开发中,高质量的 Git 提交信息不仅是团队协作的基础,更是项目可维护性、可追溯性和工程文化的重要体现。然而,许多开发者(尤其是初学者)常常忽略提交信息的规范性,导致提交日志混乱、难以理解,甚至影响 Code Review 和故障排查效率。
为了解决这一痛点,本文将带你从零构建一个 AI 驱动的 Git 提交规范助手 —— git-differ。该项目结合了前端(React + Tailwind CSS + Axios)、后端(Node.js + Express)以及本地大模型(Ollama + DeepSeek-R1:8B),通过分析 git diff 内容,自动生成符合 Conventional Commits 规范的提交信息。
我们将深入剖析项目的代码结构、技术选型与关键知识点,并围绕 跨域处理、LangChain 集成、自定义 React Hook 封装、Express 路由设计 等核心内容展开详细讲解。
一、项目整体架构与技术栈
git-differ 是一个典型的前后端分离全栈应用:
-
前端:运行于浏览器(如
http://localhost:5173),使用 React 构建 UI,Tailwind CSS 实现响应式样式,Axios 发起 HTTP 请求。 -
后端:运行于 Node.js 环境(
http://localhost:3000),基于 Express 框架提供 RESTful API。 -
AI 引擎:本地部署 Ollama 服务(
http://localhost:11434),加载开源大模型deepseek-r1:8b,通过 LangChain 进行提示工程与输出解析。
整个数据流如下:
用户输入 git diff → 前端发送 POST /chat → 后端接收并调用 Ollama → AI 生成 commit message → 返回前端展示
这种架构不仅解耦清晰,还便于后续扩展(如支持多模型、历史记录、配置管理等)。
二、前端实现:React + 自定义 Hook + Axios 模块化
1. 主组件 App.jsx:UI 与逻辑分离
import { useEffect } from "react"
import { chat } from './api/axios'
import { useGitDiff } from "./hooks/useGitDiff"
export default function App(){
const { loading, content } = useGitDiff('hello')
return (
<div className="flex">
{loading ? 'loading...' : content}
</div>
)
}
主组件极其简洁,仅负责渲染状态。真正的业务逻辑被封装在 useGitDiff 自定义 Hook 中,体现了 “组件只负责 UI” 的最佳实践。
“use开头 封装响应式业务 副作用等 从组件里面剥离 组件只负责UI”
这种模式极大提升了代码的可读性与复用性。未来若需在多个页面使用 AI 生成 commit message 功能,只需调用 useGitDiff 即可。
2. API 层:Axios 模块化封装
// src/api/axios.js
import axios from 'axios'
// 创建axios实例 统一进行配置
const service = axios.create({
baseURL: 'http://localhost:3000',
headers: {
'Content-Type': 'application/json'
},
timeout: 60000
})
export const chat = (message) => {
return service.post('/chat', {
message
})
}
这里通过 axios.create() 创建了一个 专用的 HTTP 客户端实例,统一配置:
-
baseURL:避免在每个请求中重复写后端地址; -
headers:确保请求体为 JSON 格式; -
timeout:设置超时时间,防止请求卡死。
“模块化 在api目录下管理所有的请求”
封装了api请求,在其他组件(如:useGitDiff.js)中只需要模块化导入chat 即可以发起请求,这种组织方式是大型 React 项目的标准做法,便于维护和测试。
3. 自定义 Hook:useGitDiff —— 封装副作用与状态
// src/hooks/useGitDiff.js
import { useState, useEffect } from "react"
import { chat } from "../api/axios"
export const useGitDiff = (diff) => {
const [content, setContent] = useState('')
const [loading, setLoading] = useState(false)
useEffect(() => {
(async () => {
if(!diff) return
setLoading(true)
const { data } = await chat(diff)
setContent(data.reply)
setLoading(false)
})()
// 立即执行异步函数 避免顶层async
}, [diff])
// 将外部需要的loading状态和content内容 return
return {
loading, // 加载状态 用户体验
content // llm得出的commit message
}
}
该 Hook 接收 diff 字符串作为依赖,当其变化时自动发起请求。关键点包括:
- 使用 立即执行异步函数(IIFE) 在
useEffect中处理异步逻辑; - 通过
setLoading提供加载状态反馈; - 依赖数组
[diff]确保只在diff变化时触发请求; - 返回结构化对象,便于解构使用。
“通过立即执行函数 执行异步函数”
这种写法规避了 useEffect 的回调函数不能直接使用 async/await 的限制。
三、后端实现:从零构建一个健壮的 Express AI 服务
后端是整个 git-differ 项目的中枢神经。它不仅要接收前端请求、调用本地大模型,还需保证安全性、稳定性、可维护性与协议规范性。以下我们将结合完整代码,逐层剖析其技术内涵。
1. 基础服务初始化:Express 应用骨架
import express from 'express'
import cors from 'cors'
const app = express()
app.use(express.json())
app.use(cors())
app.listen(3000, () => {
console.log('server is running on port 3000')
})
这段看似简单的代码,实际上完成了现代 Web API 服务的三大基础配置:
✅ express():创建应用实例
- Express 是 Node.js 生态中最流行的 Web 框架,其核心思想是 “中间件管道” 。
-
app是一个可配置、可扩展的 HTTP 服务器容器,后续所有路由、中间件都挂载于此。
✅ app.use(express.json()):请求体解析中间件
- 默认情况下,Express 不会自动解析请求体(
req.body为undefined)。 -
express.json()是一个内置中间件,用于解析Content-Type: application/json的请求体,并将其转换为 JavaScript 对象。 - 若省略此中间件,
req.body将无法获取前端发送的{ message: "..." }数据,导致后续逻辑失败。
💡 最佳实践:应在所有路由定义之前注册全局中间件,确保所有请求都能被正确解析。
✅ app.use(cors()):跨域资源共享(CORS)支持
-
前端开发服务器(如 Vite,默认端口 5173)与后端(3000)构成跨域请求(协议、域名或端口不同)。
-
浏览器出于安全考虑,会拦截跨域请求的响应,除非服务器明确允许。
-
cors()中间件自动处理 OPTIONS 预检请求,并在响应头中添加:Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE Access-Control-Allow-Headers: Content-Type -
在生产环境中,应限制
origin为可信域名(如origin: ['https://your-app.com']),避免开放全站跨域。
2. 路由设计:RESTful API 与 HTTP 语义
app.get('/hello', (req, res) => {
res.send('hello world')
})
app.post('/chat', async (req, res) => {
// ... 业务逻辑
})
📌 路由方法与资源操作语义
-
GET /hello:用于健康检查或简单测试,无副作用,符合幂等性。 -
POST /chat:用于提交git diff内容并获取 AI 生成结果。使用 POST 是因为: 请求包含复杂数据体(diff 文本可能很长);
📌 请求与响应对象(req / res)
-
req.body:由express.json()解析后的请求体数据; -
res.status(code).json(data):设置 HTTP 状态码并返回 JSON 响应; -
res.send():返回纯文本(不推荐用于 API,应统一使用 JSON)。
3. 输入验证:防御性编程的第一道防线
const { message } = req.body
if (!message || typeof message !== 'string') {
return res.status(400).json({
error: 'message 必填,必须是字符串'
})
}
-
永远不要信任客户端输入,后端稳定性 是第一位。即使前端做了校验,后端也必须二次验证。
-
此处检查:
-
message是否存在(防止null、undefined); - 类型是否为字符串(防止传入对象、数组等非法类型)。
-
-
返回 400 Bad Request 状态码,明确告知客户端请求格式错误。
-
错误信息清晰具体,便于前端调试。
4. AI 集成:LangChain 链式调用与错误隔离
import { ChatOllama } from '@langchain/ollama'
import { ChatPromptTemplate } from '@langchain/core/prompts'
import { StringOutputParser } from '@langchain/core/output_parsers'
const model = new ChatOllama({
baseUrl: 'http://localhost:11434',
model: 'deepseek-r1:8b',
temperature: 0.1 //
})
🔗 LangChain 核心抽象:链(Chain)
LangChain 的核心思想是将 LLM 调用过程模块化、可组合。本项目使用了典型的三段式链:
-
Prompt Template:结构化输入
const prompt = ChatPromptTemplate.fromMessages([ ['system', `你是一个专业的 Git 提交信息生成器。请根据以下 git diff 内容,生成一条简洁、符合 Conventional Commits 规范的 commit message。不要解释,只输出 commit message`], ['human', '{input}'] ])- 将原始
message注入到预设对话模板中; - 支持多轮对话上下文(未来可扩展);
- 确保提示词格式符合模型预期。
- 将原始
-
Model:大模型调用
-
ChatOllama是 LangChain 对 Ollama API 的封装; -
baseUrl指向本地 Ollama 服务; -
temperature: 0.1降低随机性,使输出更确定(适合生成规范文本)。
-
-
Output Parser:标准化输出
new StringOutputParser()- 强制将模型返回的复杂对象(如
{ content: "...", role: "assistant" })转换为纯字符串; - 避免前端处理不必要的元数据;
- 保证 API 响应结构稳定:
{ reply: "string" }。
- 强制将模型返回的复杂对象(如
⚠️ 错误处理:隔离 AI 不稳定性
try {
const result = await chain.invoke({ input: message })
res.json({ reply: result })
} catch (err) {
res.status(500).json({ error: '调用大模型失败' })
}
- 大模型调用可能因网络、内存、模型加载失败等原因抛出异常;
- 使用
try/catch捕获所有同步/异步错误; - 返回 500 Internal Server Error,避免服务崩溃;
5. 工程化考量:可维护性与可观测性
-
日志记录:
console.log('正在调用大模型')提供基本执行追踪; - 超时控制:虽未显式设置,但 Ollama 客户端和 Axios 均有默认超时;
- 依赖解耦:AI 逻辑封装在路由内,未来可提取为独立 service 层;
总结:一个合格的后端 API 应具备什么?
| 维度 | 本项目实现 |
|---|---|
| 协议合规 | 正确使用 HTTP 方法、状态码、Content-Type |
| 输入安全 | 严格校验请求体格式与类型 |
| 错误处理 | 区分客户端错误(4xx)与服务端错误(5xx) |
| 跨域支持 | 通过 CORS 中间件解决开发跨域问题 |
| AI 集成 | 使用 LangChain 实现可维护的提示工程 |
| 可扩展性 | 模块化结构,便于未来增加新功能 |
这才是一个面向生产、面向读者的后端实现应有的深度与广度。
四、AI 部分:Ollama 与本地大模型部署
1. 为什么选择 Ollama?
- 开源、轻量、支持多种模型(Llama, DeepSeek 等);
- 提供类 OpenAI 的 API 接口(
/api/chat),便于集成; - 可在消费级 GPU 或 CPU 上运行(需足够内存)。
2. 模型选择:deepseek-r1:8b
-
r1表示 Reasoning 版本,推理能力更强; -
8b为 80 亿参数,平衡性能与资源消耗; -
需通过命令下载:
ollama pull deepseek-r1:8b ollama run deepseek-r1:8b # 测试
“ollama帮助我们像openai一样的api 接口 http://localhost:11434”
3. 性能提示
- 首次加载模型较慢(需加载到内存);
-
temperature: 0.1降低随机性,使输出更确定、规范; - 若响应慢,可考虑量化版本(如
q4_K_M)。
五、总结
通过 git-differ 项目,我们完整实践了一个 AI 增强型开发者工具 的全栈开发流程:
- 前端:React 自定义 Hook + Axios 模块化;
- 后端:Express 路由 + CORS + 错误处理;
- AI:Ollama + LangChain 提示工程 + 输出解析。
这不仅解决了“如何写好 Git 提交信息”的实际问题,更展示了 本地大模型在开发者工具链中的落地场景。随着开源模型能力不断提升,类似工具将极大提升个人与团队的开发效率与代码质量。
最终目标:让新手也能像高手一样,写出清晰、规范、有价值的 Git 提交记录。
现在,启动你的 AI Git 助手,让每一次提交都成为工程艺术的一部分!