阅读视图

发现新文章,点击刷新页面。

从零开始:使用 Ollama 在本地部署开源大模型并集成到 React 应用

一、为什么我们需要 Ollama?——本地部署大模型的必要性

在人工智能浪潮席卷全球的今天,大模型已经成为开发者和企业的标配工具。然而,当我们谈论使用大模型时,往往面临两个核心问题:数据安全成本控制

传统上,我们依赖 OpenAI、Claude、Gemini 等闭源模型服务。这些服务虽然强大,但存在几个关键问题:

  1. 数据安全:所有输入数据都通过互联网传输到第三方服务器,存在敏感信息泄露风险
  2. 成本高昂:API 调用费用随着使用量增加而线性增长
  3. 依赖外部服务:一旦服务中断,应用将无法使用

而 Ollama 的出现,为我们提供了一个全新的解决方案:将开源大模型部署在本地,让企业能够完全掌控数据和模型。

二、Ollama:本地部署大模型的利器

Ollama 是一个开源工具,专为在本地机器上部署和运行大语言模型而设计。它的核心优势在于:

  • 简单易用:通过命令行即可完成模型部署
  • 兼容性好:提供与 OpenAI API 兼容的接口
  • 资源优化:支持多种模型规模,从 0.5B 到 70B 参数量

Ollama 基础使用

bash

# 下载模型
ollama pull qwen2.5:0.5b

# 运行模型
ollama run qwen2.5:0.5b

默认情况下,Ollama 会在 localhost:11434 端口提供 API 服务,这个端口是兼容 OpenAI 接口的,意味着我们可以使用熟悉的 OpenAI 客户端库来调用它。

为什么选择 11434 端口?

Ollama 选择 11434 端口作为默认服务端口,这并非随意选择。这个端口在 1024-65535 的范围内,属于"用户端口"(非特权端口),避免了需要以 root 权限运行的麻烦。同时,这个端口在 Ollama 的文档和社区中已经约定俗成,使用它能确保与其他开发者和工具的兼容性。

三、在 React 应用中集成 Ollama

接下来,让我们看看如何将 Ollama 集成到 React 应用中。以下是关键代码分析:

1. API 服务封装

javascript

// api/ollamaApi.js
import axios from 'axios';

const ollamaApi = axios.create({
    baseURL: 'http://localhost:11434/v1',
    headers: {
        'Authorization': 'Bearer ollama',
        'Content-Type': 'application/json',
    }
});

export const chatCompletions = async (messages) => {
    try {
        const response = await ollamaApi.post('/chat/completions', {
            model: 'qwen2.5:0.5b',
            messages,
            stream: false,
            temperature: 0.7,
        });
        return response.data.choices[0].message.content;
    } catch (err) {
        console.error('Ollama 请求失败:', err);
        throw err;
    }
};

这段代码创建了一个 Axios 实例,指向 Ollama 的 API 端点。关键点有:

  • baseURL:指向 http://localhost:11434/v1,这是 Ollama 提供的 OpenAI 兼容接口
  • headers:需要设置 Authorization: Bearer ollama,这是 Ollama 的身份验证方式
  • chatCompletions:一个封装好的函数,用于发送聊天请求

2. React Hook 封装

javascript

// hooks/useLLM.js
import { useState, useEffect } from 'react';
import { chatCompletions } from '../api/ollamaApi.js';

export const useLLM = () => {
    const [messages, setMessages] = useState([{
        role: 'user',
        content: '你好',
    }, {
        role: 'assistant',
        content: '你好,我是 Qwen2.5 0.5b 模型',
    }]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    const sendMessage = async (content) => {
        if (!content.trim()) return;
        
        // 添加用户消息
        setMessages(prev => [...prev, { role: 'user', content }]);
        setLoading(true);
        
        try {
            // 调用 Ollama API 获取响应
            const response = await chatCompletions([
                ...messages,
                { role: 'user', content }
            ]);
            
            // 添加助手响应
            setMessages(prev => [...prev, { role: 'assistant', content: response }]);
        } catch (err) {
            setError('请求失败,请重试');
        } finally {
            setLoading(false);
        }
    };

    const resetChat = () => {
        setMessages([{
            role: 'user',
            content: '你好',
        }, {
            role: 'assistant',
            content: '你好,我是 Qwen2.5 0.5b 模型',
        }]);
    };

    return {
        messages,
        loading,
        error,
        sendMessage,
        resetChat,
    };
};

这个 Hook 封装了与 Ollama 的交互逻辑,关键点包括:

  • 状态管理messages 存储聊天记录,loading 表示请求状态,error 存储错误信息
  • 消息发送sendMessage 函数处理用户输入,调用 chatCompletions 获取响应
  • 错误处理:捕获 API 错误并显示给用户

3. React 组件实现

javascript

// App.js
import { useState, useEffect } from 'react';
import { useLLM } from './hooks/useLLM.js';

export default function App() {
    const [inputValue, setInputValue] = useState('');
    const { messages, loading, error, sendMessage, resetChat } = useLLM();
    
    const handleSend = (e) => {
        e.preventDefault();
        if (inputValue.trim()) {
            sendMessage(inputValue);
            setInputValue('');
        }
    };

    return (
        <div className="min-h-screen bg-gray-50 flex flex-col items-center py-6 px-4">
            <div className="w-full max-w-[800px] bg-white rounded-lg shadow-md flex flex-col h-[90vh] max-h-[800px]">
                {/* 聊天界面 */}
                <div className="flex-1 overflow-y-auto p-4 space-y-4">
                    {messages.map((message, index) => (
                        <div key={index} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
                            <div className={`max-w-[80%] rounded-lg p-3 ${message.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
                                {message.content}
                            </div>
                        </div>
                    ))}
                    {loading && (
                        <div className="flex justify-start">
                            <div className="bg-gray-200 rounded-lg p-3">正在思考...</div>
                        </div>
                    )}
                    {error && (
                        <div className="flex justify-start">
                            <div className="bg-red-100 text-red-700 rounded-lg p-3">
                                {error}
                            </div>
                        </div>
                    )}
                </div>
                
                {/* 输入框 */}
                <form className="p-4 border-t" onSubmit={handleSend}>
                    <div className="flex gap-2">
                        <input
                            type="text"
                            value={inputValue}
                            onChange={e => setInputValue(e.target.value)}
                            placeholder="输入消息...按回车发送"
                            disabled={loading}
                            className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                        />
                        <button
                            type="submit"
                            className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition disabled:bg-gray-400"
                            disabled={loading || !inputValue.trim()}
                        >
                            发送
                        </button>
                    </div>
                </form>
            </div>
        </div>
    );
}

这个组件实现了完整的聊天界面,包括:

  • 消息列表展示
  • 输入框和发送按钮
  • 加载状态和错误提示
  • 与 useLLM Hook 的集成

四、本地部署大模型的硬件要求与挑战

在决定使用 Ollama 部署本地模型时,硬件要求是首要考虑因素。以下是一些关键点:

1. 硬件需求

模型规模 GPU 显存需求 推荐配置 适用场景
0.5B-1B 2-4GB 无 GPU 也可运行 个人开发、测试
3B-7B 6-12GB NVIDIA RTX 3060+ 中小型应用
13B+ 24GB+ NVIDIA RTX 4090+ 企业级应用

2. 本地部署 vs 云端部署

本地部署

  • ✅ 数据安全可控
  • ✅ 无 API 调用费用
  • ✅ 可定制化模型
  • ❌ 需要硬件投入
  • ❌ 需要运维知识

云端部署

  • ✅ 无需硬件投入
  • ✅ 易于扩展
  • ❌ 数据安全风险
  • ❌ 长期成本高

3. 企业级应用考虑

对于企业级应用,如 "生成前端代码" 这类需求,本地部署有明显优势:

  • 安全性:代码作为敏感信息,不通过互联网传输
  • 定制化:可以根据企业代码规范微调模型
  • 稳定性:不受第三方 API 服务中断影响

五、安全与隐私:本地部署的核心优势

正如我在文章开头提到的,安全是本地部署大模型的最大优势。让我们深入探讨:

1. 数据安全

在使用 OpenAI 等服务时,所有输入数据都会发送到第三方服务器。对于企业应用,这可能意味着:

  • 代码库、业务逻辑、客户数据被第三方存储
  • 违反数据隐私法规(如 GDPR)
  • 无法满足企业内部安全审计要求

而本地部署确保了数据完全在企业内部流转,无需通过互联网传输。

2. 安全最佳实践

即使在本地部署,也需考虑以下安全措施:

  • 禁用外部访问:在防火墙中限制 Ollama 服务的访问
  • 使用身份验证:在 API 请求中添加认证(如 Authorization: Bearer ollama
  • 定期更新:保持 Ollama 和模型版本最新
  • 模型隔离:为不同应用使用不同模型实例

六、全栈开发视角:从需求到实现

从需求分析到最终实现,这是一个典型的全栈开发流程:

  1. 需求分析:需要一个安全的聊天机器人,不依赖外部 API
  2. 技术选型:选择开源模型 + 本地部署方案
  3. 后端开发:使用 Ollama 部署模型,提供 API 接口
  4. 前端开发:使用 React 构建用户界面,集成 Ollama API
  5. 测试与优化:确保消息流畅,处理错误情况
  6. 部署上线:在生产环境中部署应用

这个流程展示了现代全栈开发中,前端和后端如何协同工作,共同实现一个安全、可靠的应用。

七、结语:拥抱本地部署大模型的未来

随着大模型技术的快速发展,我们正进入一个数据主权隐私保护日益重要的时代。Ollama 作为本地部署大模型的工具,为我们提供了一个强大而灵活的解决方案。

通过将模型部署在本地,我们可以:

  • 保护敏感数据
  • 降低长期成本
  • 实现高度定制化
  • 提升应用稳定性

正如我在代码中展示的,集成 Ollama 到 React 应用并不复杂,关键在于理解其工作原理和正确处理 API 调用。

在未来的开发中,我会继续探索更多本地部署大模型的可能性,包括:

  • 多模态模型(支持图像、音频等)
  • 更高效的模型量化技术
  • 企业级安全增强

无论您是个人开发者还是企业技术负责人,本地部署大模型都是值得投入的方向。它不仅解决了当前的安全问题,还为未来的 AI 应用奠定了坚实基础。

"在数据即资产的时代,掌握自己的数据和模型,就是掌握未来的主动权。" —— 这就是 Ollama 带给我们的核心价值。

智能驱动的 Git 提交:基于 Ollama 大模型的规范化提交信息生成方案

摘要
在现代软件开发中,清晰、规范的 Git 提交信息不仅是版本控制的核心组成部分,更是团队协作、代码审查、项目复盘和自动化流程的重要依据。然而,传统手动编写提交信息的方式存在效率低、格式不统一、语义模糊等问题,尤其对新手开发者而言,掌握如 Angular 规范等标准格式门槛较高。

本文提出一种创新性解决方案——利用本地部署的大语言模型(LLM)自动生成符合团队规范的 Git 提交信息。我们基于开源框架 Ollama + Node.js/Express 后端 + React 前端,构建了一套“输入 git diff → 调用 LLM 分析 → 输出标准化提交信息”的智能系统。整个过程无需依赖第三方云服务,保障数据隐私的同时实现高效推理。

文章将从实际痛点出发,深入剖析技术架构设计、核心模块实现逻辑与工程实践细节,并探讨可扩展方向,旨在为开发者提供一套可落地、易集成、高可用的 AI 驱动型 Git 工作流增强工具。


一、为什么需要“智能 Git 提交”?

1.1 当前 Git 提交流程的三大痛点

痛点 具体表现 影响
① 格式混乱,缺乏统一标准 新手常写 "update file""fix bug" 等笼统描述;不同成员使用不同风格(如 feat/feature、doc/docs) 日志难以追溯,CI/CD 自动化解析失败
② 编写耗时,影响开发节奏 开发者需反复思考如何概括改动,打断编码心流 降低整体开发效率
③ 语义不准,沟通成本上升 描述与实际变更不符,导致 Code Review 困难或误判风险 增加协作摩擦,不利于知识沉淀

这些问题在中小型团队或快速迭代项目中尤为突出。而随着 AIGC 技术的发展,大模型强大的自然语言理解与生成能力,恰好可以成为解决上述问题的理想工具。


1.2 解决思路:让 AI 成为你的“提交助手”

我们的核心理念是:

git diff 内容交给本地大模型分析,由其自动识别变更类型并生成符合团队规范的提交信息

该方案具备以下优势:

  • 🛡️ 数据安全:全程运行于本地环境,敏感代码不会上传至任何云端 API;
  • 响应迅速:Ollama 支持 GPU 加速推理,平均响应时间 <2s;
  • 📏 格式可控:通过精心设计的提示词(Prompt Engineering),强制输出结构化结果;
  • 💡 学习成本低:即使不懂 Angular 规范的新手,也能一键获得专业级提交信息。

这不仅是一次效率提升,更是一种开发范式的升级 —— 让开发者专注于“做什么”,而非“怎么说”。


二、整体技术架构设计

本系统采用典型的前后端分离架构,各模块职责明确,便于维护与扩展。

+------------------+     HTTP POST /chat      +--------------------+
|                  | -----------------------> |                    |
|   React 前端     |                          |   Express 后端     |
| (Vite + Tailwind)| <----------------------- | (Node.js + CORS)   |
|                  |     JSON { reply }       |                    |
+------------------+                          +----------+---------+
                                                         |
                                                         | gRPC/HTTP
                                                         v
                                                +------------------+
                                                |    Ollama Server   |
                                                | (deepseek-r1:8b)   |
                                                +------------------+

技术栈一览

层级 技术选型 说明
前端 React + Vite + TailwindCSS + Custom Hooks 快速构建响应式 UI,组件解耦
后端 Express.js + CORS 中间件 轻量级 RESTful 接口服务
AI 引擎 Ollama + deepseek-r1:8b 本地部署大模型,支持中文理解和推理
LLM 调用层 LangChain.js 封装 Prompt 构建、链式调用、输出解析
通信协议 HTTP/JSON 前后端交互简洁可靠
跨域处理 cors 中间件 解决开发环境下端口隔离问题

端口规划

服务 地址 功能
前端 http://localhost:5173 用户界面展示
后端 http://localhost:3001 提供 /chat 接口
Ollama http://localhost:11434 大模型推理服务

三、后端实现:打造稳定可靠的 AI 接口服务

3.1 初始化项目与依赖安装

npm init -y
npm install express cors @langchain/ollama @langchain/core axios

创建 index.js 文件作为主入口。


3.2 核心代码解析:构建智能提交生成接口

import express from 'express';
import cors from 'cors';
import { ChatOllama } from '@langchain/ollama';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';

// Step 1: 初始化 Ollama 大模型
const model = new ChatOllama({
  baseUrl: 'http://localhost:11434',     // Ollama 服务地址
  model: 'deepseek-r1:8b',               // 使用 DeepSeek-R1 8B 版本(中文能力强)
  temperature: 0.1,                      // 控制输出随机性,越低越稳定
  numPredict: 200                        // 最多生成 200 token,避免过长
});

// Step 2: 创建 Express 应用
const app = express();
app.use(express.json());                 // 解析请求体中的 JSON 数据
app.use(cors());                         // 允许跨域访问(开发环境)

// Step 3: 定义核心接口 POST /chat
app.post('/chat', async (req, res) => {
  try {
    const { message } = req.body;

    // 参数校验:确保传入有效的 diff 内容
    if (!message || typeof message !== 'string' || message.trim().length === 0) {
      return res.status(400).json({
        error: '无效参数:请提供非空字符串形式的 Git Diff 内容'
      });
    }

    // 构建提示词模板(关键!决定输出质量)
    const prompt = ChatPromptTemplate.fromMessages([
      [
        'system',
        `
你是一位专业的 Git 提交信息生成专家,必须严格遵循 Angular 提交规范。
请根据以下规则生成提交信息:

【输出格式】
<type>: <short summary>

【type 类型说明】
- feat: 新增功能
- fix: 修复缺陷
- docs: 文档变更
- style: 代码格式调整(不影响逻辑)
- refactor: 重构(既不新增功能也不修复 bug)
- test: 测试相关改动
- chore: 构建过程或辅助工具变动

【要求】
1. 仅输出一条提交信息,不要有多余解释;
2. 描述简洁明了,控制在 50 字以内;
3. 准确判断变更类型,避免误分类;
4. 使用中文简述改动内容。
        `.trim()
      ],
      ['human', '{input}']
    ]);

    // 构建调用链:Prompt → Model → Parser
    const chain = prompt.pipe(model).pipe(new StringOutputParser());

    // 执行调用
    const result = await chain.invoke({ input: message });

    // 返回成功响应
    res.status(200).json({ reply: result.trim() });

  } catch (error) {
    console.error('[Server Error]', error.message);
    res.status(500).json({
      error: '服务器内部错误:无法调用大模型,请检查 Ollama 是否正常运行'
    });
  }
});

// 启动服务
app.listen(3001, () => {
  console.log('✅ 后端服务已启动:http://localhost:3001');
});

3.3 关键设计思想解读

✅ 1. 提示词工程(Prompt Engineering)是成败关键

我们通过 system 角色明确设定了:

  • 模型角色定位(Git 提交专家)
  • 输出格式约束(Angular 规范)
  • 字数限制与语言要求
  • 行为边界(禁止额外解释)

这种“强引导式”提示词显著提升了输出的一致性和可用性。

✅ 2. 温度值调优:平衡创造性与稳定性

设置 temperature: 0.1 是为了抑制模型“自由发挥”。对于格式固定的任务,低温度能有效防止输出偏离预期。

✅ 3. 防御性编程:健壮的参数校验机制

提前拦截非法输入,减少无效请求对模型资源的浪费,同时提升用户体验。

✅ 4. 本地化部署:兼顾性能与隐私

相比调用 OpenAI 或通义千问 API,Ollama 的本地部署模式具有:

  • 零网络延迟
  • 无 API 调用费用
  • 绝对的数据安全性(代码不出内网)

特别适合企业级开发场景。


四、前端实现:优雅的交互体验与逻辑封装

4.1 项目初始化(Vite + React + TailwindCSS)

npm init vite
npm install tailwindcss @tailwindcss/vite
npm i axios

配置 tailwind.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// <https://vitejs.dev/config/>export default defineConfig(
{plugins: 
[react(),
tailwindcss()]
,})

更新 index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

4.2 API 封装:统一接口调用层

创建 src/api/index.js

import axios from 'axios';

const client = axios.create({
  baseURL: 'http://localhost:3001',
  timeout: 15000,
});

export const generateCommitMessage = (diff) => {
  return client.post('/chat', { message: diff });
};

4.3 自定义 Hook:useGitDiff —— 实现业务逻辑复用

创建 src/hooks/useGitDiff.js

import { useState, useEffect } from 'react';
import { generateCommitMessage } from '../api';

export const useGitDiff = (diff) => {
  const [content, setContent] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  useEffect(() => {
    if (!diff?.trim()) {
      setContent('');
      setError('');
      return;
    }

    const fetch = async () => {
      setLoading(true);
      setError('');
      try {
        const response = await generateCommitMessage(diff);
        setContent(response.data.reply);
      } catch (err) {
        const msg = err.response?.data?.error || '未知错误';
        setError(msg);
        setContent('');
      } finally {
        setLoading(false);
      }
    };

    fetch();
  }, [diff]);

  return { loading, content, error };
};

🔍 Hook 设计哲学:将“状态管理 + 异步请求 + 生命周期控制”封装为可复用单元,使组件只关注 UI 渲染,真正实现关注点分离。


4.4 页面组件:极简 UI,极致体验

src/App.jsx

import { useState } from 'react';
import { useGitDiff } from './hooks/useGitDiff';

export default function App() {
  const [diffInput, setDiffInput] = useState(`diff --git a/src/App.jsx b/src/App.jsx
index 1a2b3c4..5d6e7f8 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,6 @@
 import { useState } from 'react';
+import { useGitDiff } from './hooks/useGitDiff';
 
 export default function App() {
   const [count, setCount] = useState(0);
`);

  const { loading, content, error } = useGitDiff(diffInput);

  const handleCopy = () => {
    navigator.clipboard.writeText(content).then(
      () => alert('✅ 提交信息已复制到剪贴板!'),
      () => alert('❌ 复制失败')
    );
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4">
      <div className="max-w-5xl mx-auto bg-white rounded-xl shadow-lg overflow-hidden">
        <header className="bg-gradient-to-r from-teal-500 to-cyan-600 text-white p-6 text-center">
          <h1 className="text-3xl font-bold">🤖 Git 提交信息智能生成器</h1>
          <p className="mt-2 opacity-90">基于 Ollama 大模型,本地化 AI 辅助 Commit</p>
        </header>

        <main className="p-8 space-y-8">
          {/* 输入区域 */}
          <section>
            <label className="block text-lg font-semibold text-gray-700 mb-3">
              📄 Git Diff 内容
            </label>
            <textarea
              value={diffInput}
              onChange={(e) => setDiffInput(e.target.value)}
              placeholder="在此粘贴 git diff 输出内容..."
              className="w-full h-60 p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-400 focus:border-transparent resize-none font-mono text-sm"
            />
          </section>

          {/* 输出区域 */}
          <section className="border-t pt-6">
            <h2 className="text-xl font-medium text-gray-800 mb-4">✨ 生成的提交信息</h2>

            {loading && (
              <div className="flex items-center space-x-2 text-gray-500">
                <span className="animate-pulse">🧠 正在分析变更...</span>
              </div>
            )}

            {error && (
              <div className="p-4 bg-red-50 text-red-700 rounded border border-red-200">
                ❌ {error}
              </div>
            )}

            {content && !loading && (
              <div className="space-y-3">
                <div className="p-4 bg-gray-50 rounded border font-mono text-gray-800">
                  <code>{content}</code>
                </div>
                <button
                  onClick={handleCopy}
                  className="px-5 py-2 bg-teal-500 hover:bg-teal-600 text-white rounded transition-colors duration-200 flex items-center space-x-1"
                >
                  <span>📋</span>
                  <span>复制提交信息</span>
                </button>
              </div>
            )}
          </section>
        </main>

        <footer className="text-center text-gray-500 text-sm py-4 border-t bg-gray-50">
          Built with ❤️ using Ollama + LangChain + React
        </footer>
      </div>
    </div>
  );
}

4.4 前端亮点总结

特性 说明
🎨 TailwindCSS 实现现代化 UI 快速构建美观、响应式的界面,无需写 CSS
🪝 Custom Hook 封装逻辑 实现业务逻辑复用,提升代码组织性
🕐 加载状态反馈 明确告知用户正在处理,避免“卡死”错觉
⚠️ 错误友好提示 帮助用户快速定位问题
📋 一键复制功能 提升实用性,贴近真实工作流

五、跨域问题与服务启动流程

5.1 跨域问题本质

浏览器出于安全考虑实施“同源策略”(Same-Origin Policy),当前端运行在 5173 端口,而后端在 3001 时,默认无法通信。

5.2 解决方案

已在后端启用 cors() 中间件:

app.use(cors());

⚠️ 生产环境中建议指定白名单:

app.use(cors({ origin: 'https://yourdomain.com' }));

5.3 完整启动流程

# 1. 启动 Ollama 并下载模型(首次需联网)
ollama run deepseek-r1:8b

# 2. 启动后端服务
cd service && nodemon index.js

# 3. 启动前端服务
cd frontend && npm run dev

✅ 成功标志:前端页面打开,输入 diff 可收到生成结果。


六、未来扩展方向与优化建议

6.1 功能增强

方向 说明
🔌 集成 Git 命令行工具 使用 simple-git 自动获取 git diff,实现“一键生成”
🧩 支持多文件批量分析 一次处理多个变更文件,生成综合提交信息
🎯 自定义提交规范 允许团队上传自己的 commit rule 配置(如加入 perf:security:
📚 历史记录持久化 利用 localStorage 存储常用提交模板,支持回溯与复用

6.2 性能优化

优化项 实现方式
🧠 模型轻量化 替换为 qwen:0.5bphi3:mini 等小型模型,加快推理速度
💾 缓存机制 对相同 diff 内容缓存结果,避免重复调用
🌊 流式输出(Streaming) 使用 LangChain 的 .stream() 方法,逐步渲染生成内容,减少等待感
🧰 CLI 工具化 构建命令行版本,直接嵌入 Git 工作流(如 git smart-commit

七、总结:从“人工撰写”到“AI 协作”的跃迁

本文完整实现了一个基于 Ollama + Express + React 的智能 Git 提交信息生成系统,其核心价值体现在以下几个维度:

维度 价值体现
🔐 数据安全优先 本地部署,代码永不离境,满足企业合规需求
🧩 架构清晰可维护 分层设计 + Hook 封装,易于二次开发与集成
🤖 AI 赋能提效 大模型精准理解代码变更,输出专业级提交文案
🛠️ 开箱即用 代码完整、注释详尽,开发者可快速上手

这套系统不仅能帮助新手快速掌握提交规范,更能为资深工程师节省每日重复劳动的时间。更重要的是,它标志着我们正从“纯人工操作”迈向“人机协同开发”的新时代。


📣 结语:让每一次 Commit 都更有意义

“好的提交信息,是写给人看的日志,而不是给机器读的记录。”

借助大模型的力量,我们可以把枯燥的“文字包装”交给 AI,自己则专注于更有创造力的工作。这不仅是工具的进化,更是思维方式的转变。

下一步,你可以尝试将其集成进你的 IDE 插件、Git Hook 或 CI/CD 流水线,真正实现“智能提交,一步到位”。


打造 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.bodyundefined)。
  • 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 是否存在(防止 nullundefined);
    • 类型是否为字符串(防止传入对象、数组等非法类型)。
  • 返回 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 调用过程模块化、可组合。本项目使用了典型的三段式链:

  1. Prompt Template:结构化输入

    const prompt = ChatPromptTemplate.fromMessages([  ['system', `你是一个专业的 Git 提交信息生成器。请根据以下 git diff 内容,生成一条简洁、符合 Conventional Commits 规范的 commit message。不要解释,只输出 commit message`],
      ['human', '{input}']
    ])
    
    • 将原始 message 注入到预设对话模板中;
    • 支持多轮对话上下文(未来可扩展);
    • 确保提示词格式符合模型预期。
  2. Model:大模型调用

    • ChatOllama 是 LangChain 对 Ollama API 的封装;
    • baseUrl 指向本地 Ollama 服务;
    • temperature: 0.1 降低随机性,使输出更确定(适合生成规范文本)。
  3. 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 助手,让每一次提交都成为工程艺术的一部分!

❌