普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月14日首页

langchainjs&langgraphjs入门(二)格式化输出

作者 The_massif
2026年1月14日 08:49

格式化输出

zod

zod是一个ts的类型校验库,langchain官方推荐使用zod来定义ai输出的schema,例如:

import {z} from 'zod'
// 期望ai返回对象,其中包含name,age,skills
const schema = z.object({
    name:z.string().describe('姓名'),
    age: z.number().init().describe('年龄'),
    skills: z.array(z.string()).describe('技能')
})

安装:

npm i zod

速查表:

API 用途 示例
z.string() 字符串 z.string()
z.number() 数字 z.number().int()
z.boolean() 布尔值 z.boolean()
z.object() 对象 z.object({ name: z.string() })
z.array() 数组 z.array(z.string())
z.enum() 枚举 z.enum(["A", "B"])
z.union() 联合类型 z.string().or(z.number())
z.optional() 可选字段 z.string().optional()
z.nullable() 允许 null z.string().nullable()
z.default() 默认值 z.number().default(0)
z.literal() 字面量 z.literal("on")
z.record() 键值映射 z.record(z.string())
z.tuple() 元组 z.tuple([z.string(), z.number()])
.refine() 自定义校验 .refine(s => s.length > 3)
.transform() 转换输出 .transform(s => s.toUpperCase())
.describe() 描述 z.string().describe('姓名')

withStructuredOutput

上一章节的例子可以看到模型的输出只是普通的字符串,并没有格式化.无法直接使用.要想让模型输出格式化的内容可以使用官方推荐的zod,使用他来定义数据结构并验证数据结构是否正确,从而帮助langchain实现输出的格式化和验证.

  1. 首先使用zod定义类型
  2. 然后通过langchain提供的.withStructuredOutput接口使用类型,调用这个方法传入zod定义的类型.模型将添加所需的所有模型参数和输出解析器

示例

import model from './1调用模型.mjs'
import { z } from 'zod'

const schema = z.object({ isCool: z.boolean() }) // 定义输出类型
const structuredLlm = model.withStructuredOutput(schema)

const res = await structuredLlm.invoke('我帅不帅')
console.log("res:",res); // res: { isCool: true }输出了结构化的内容,可以看到模型也知道我很帅

实际上withStructuredOutput在背后会根据schema自动生成严格的提示词,并自动解析验证模型输出,然后将结果返回给开发者

withStructuredOutputlangchain封装后的便捷api,如果想深入理解背后做了什么可以查看这里,后面我们也会详细讲解

StringOutputParser

StringOutoutParser可以从LLM回复的消息中直接提取文本内容,使得我们获取的不再是AIMessage对象而是纯文本

用法

实例化的方式进行创建

import { StringOutputParser } from "@langchain/core/output_parsers";

// 创建实例(无需传递任何参数)
const outputParser = new StringOutputParser();
// 使用
const res = await llm.invoke('你好')
const str = outputParser.invoke(res)

// 简便使用
const chain = llm.pipe(outputParser)
const res = chain.invoke('你好')

对于格式化输出,为便于记忆暂时先了解这么多.知道langchain提供了这么个功能,当上述能力不满足实际开发场景时,再去翻阅官方文档即可.

关注我,该专栏会持续更新!

昨天 — 2026年1月13日首页

AI写代码记录

作者 _瑶瑶_
2026年1月13日 16:36

直接使用AI写代码目前应该算是挺火的,cursor感觉无所不能,Trae也在齐头并进,虽然说差点,但是满足日常开发绰绰有余。

但是在使用中我有一些自己的观点:

(1)使用AI协助开发是最优解,依赖AI开发有待商榷
(2)开发经验浅的人不适合直接使用AI编程
(3)非开发人员(比如美工)尽量不要直接用AI设计界面以及功能

在开发中,我一直秉承着老式开发原则,除了重复性功能(比如:登录防抖节流等)、数据处理(将接口返回的数据重构为其它的数据机构)之外,其实很少用到AI,更多的把AI当做一个知识库,忘记了的查询一下,未接触过的了解一下,仅此而已。

为什么突然想浅记一下,因为最近有两起使用过程不那么愉快的经历。

事件一:

公司让美工设计界面,美工指挥cursor直接生成了一套没有连接后端数据的一套完整的系统。在不了解公司使用的vue开发时,cursor先用nextjs构建了一套服务端渲染的系统,在想和公司之前做的项目融合的时候发现完全不一样。

在了解公司使用的技术栈之后,重新使用vue生成了一套一模一样的完整的系统,可能是不了解技术,描述的不够准确,生成的内容不是那么恰当。

下面举一个例子可能更明白一点。

总共有4个路由界面,4个路由的结构为:

  {
    path: "/homePage",
    name: "homePage",
    component: () => import("@/views/HomePage.vue"),
  },
  {
    path: "/userPage",
    name: "userPage",
    component: () => import("@/views/UserPage.vue"),
  },
  {
    path: "/messagePage",
    name: "messagePage",
    component: () => import("@/views/MessagePage.vue")
  },
  {
    path: "/articlePage",
    name: "articlePage",
    component: () => import("@/views/ArticlePage.vue")
  }

每一个页面vue文件的代码为

<template>
    <Header />
    ……
</template>

每一个页面的布局为 image.png

嗯……看到运行效果的时候其实感觉不出来一点不对,觉得设计的蛮好的,但是准备开发的时候发现,header组件是每一个页面都有的,而且在其中会需要项目的信息,所以就在Header中调用了项目信息。这些信息每一个页面都需要,但是这样写每个页面都获取不到项目的信息。而且切换页面的时候,header中会重新调接口,事情彷佛有些不对……

既然用了vue,vue也做了很多单页面应用,而且这个项目结构来看的话就是个单页面应用呀,但是美工的描述和AI的生成并没有意识到这个问题,个人认为正确的结构应该是下面这样的。

路由结构

 {
    path: "/homePage",
    component: Layout,
    children: [{
      path: "",
      name: 'homePage',
      component: () => import("@/views/secondVersion/HomePage.vue"),
    }]
  },
  {
    path: "/userPage",
    component: Layout,
    children: [{
      path: "",
      name: 'userPage',
      component: () => import("@/views/UserPage.vue"),
    }]
  },
  {
    path: "/messagePage",
    component: Layout,
    children: [{
      path: "",
      name: "messagePage",
      component: () => import("@/views/MessagePage.vue")
    }]
  },
  {
    path: "/articlePage",
    component: Layout,
    children: [{
      path: "",
      name: "articlePage",
      component: () => import("@/views/ArticlePage.vue")
    }]
  },

页面结构应该是这样的

image.png

这样每一次的路由切换只切换router-view的部分

所以不了解项目结构的美工不建议直接使用AI生成页面并直接让前端继续开发功能,因为可能会有很多地方不合理,有时候修改的时间甚至大于直接开发的时间。

事件二

公司同事很喜欢用AI直接写功能,但是可能自己的基础不是很牢固,平时做页面、写功能都没有问题,但是稍微一复杂的东西就使用AI实现。

有一个这个功能,类似于购物车数量功能,可加(一次加1)可减(一次减1)可输入,也可以长按加(一次加5)长按减(一次减5)。

因为数量有范围,范围是1-99,如果没有值的情况下,默认显示99,(不知道为什么会有这个设定)

我模拟的部分(类似)AI给的代码如下:

const max = 99;
const numValue = ref(0)
const defaultValue = ref(99)

const operate = (type) => {
    let tempValue = numValue.value || defaultValue;
    if (type == 1) {
        if (tempValue >= max) {
            // 已经是最大值不能设置
        }
        tempValue += 1;
    }
    ……
}

其它功能都好了,就是值为0的时候按加号会提示“已经是最大值不能设置”。

按说这个问题很好解决,虽然我贴的内容很少,但是同事就一直没有解决,可能一天工作也很累,看的眼睛都花了,所以才没有解决。但是我认为如果不是AI生成的,是自己写的是不是就不会出现这个问题。

毕竟自己写的话,在0是有效值的时候是不会用这个值为0的变量去进行||运算的。

总结

以上就是最近的使用过程,不能否认AI带来的优点是使工作更方便的更高效,但是不合理的使用也会带来很多麻烦。

昨天以前首页

【AI 编程实战】第 7 篇:登录流程设计 - 多场景、多步骤的优雅实现

作者 HashTang
2026年1月12日 21:13

登录是用户进入应用的第一道门,但设计一个体验好、可维护、多场景适用的登录流程并不简单。这篇文章以心动恋聊小程序为例,展示如何和 AI 对话,设计一套完整的登录系统——从微信授权到手机号绑定,从弹窗组件到全局状态管理。

系列专栏【AI 编程实战:TRAE SOLO 全栈开发指南】

本篇主题:登录流程设计 - 多场景、多步骤的优雅实现

实战项目:心动恋聊 - AI 恋爱聊天助手

一、开篇:登录流程的复杂性

1.1 看似简单,实则复杂

很多人觉得登录就是"点击按钮 → 调用接口 → 跳转首页",但实际业务中的登录流程要复杂得多:

用户点击登录
    ↓
微信静默登录(获取 openid)
    ↓
检查是否已注册?
    ├── 是 → 直接登录成功
    └── 否 → 需要手机号授权
              ↓
         用户授权手机号
              ↓
         是否新用户?
              ├── 是 → 完善资料(性别、年龄)
              └── 否 → 登录成功

还有更多场景

  • 任意页面触发登录(不只是登录页)
  • 登录成功后执行回调(如继续之前的操作)
  • 登录弹窗而非登录页(不打断用户浏览)
  • 登录状态全局同步

1.2 我需要什么

我需要一套完整的登录系统

  • 微信一键登录 + 手机号授权
  • 新用户引导完善资料
  • 全局登录弹窗(任意页面可触发)
  • 登录成功回调机制
  • 状态全局同步

二、整体架构设计

2.1 我的需求

我:需要设计一套登录系统,要求:
    1. 微信小程序环境
    2. 支持微信登录 + 手机号授权
    3. 新用户要引导填性别和年龄
    4. 任意页面都能触发登录弹窗
    5. 登录成功后能执行回调

2.2 AI 分析并给出架构

AI:这是一个比较复杂的登录系统,我来设计整体架构。

📐 架构设计:

┌─────────────────────────────────────────────────┐
│                    业务页面                       │
│   (首页、我的、详情页...任意需要登录的地方)         │
└───────────────────────┬─────────────────────────┘
                        │ 调用
                        ▼
┌─────────────────────────────────────────────────┐
│              loginModalStore                     │
│   (全局状态:控制弹窗显示、管理回调队列)            │
└───────────────────────┬─────────────────────────┘
                        │ 驱动
                        ▼
┌─────────────────────────────────────────────────┐
│              LoginModalRoot                      │
│   (全局组件:挂载在 App 层,监听 Store 状态)       │
└───────────────────────┬─────────────────────────┘
                        │ 包含
                        ▼
┌─────────────────────────────────────────────────┐
│              LoginModal                          │
│   (登录弹窗:多步骤流程 login→gender→age)         │
└───────────────────────┬─────────────────────────┘
                        │ 调用
                        ▼
┌─────────────────────────────────────────────────┐
│               userStore                          │
│   (用户状态:登录方法、用户信息、持久化)            │
└─────────────────────────────────────────────────┘

🗂️ 文件结构:

src/
├── store/
│   ├── user.ts           # 用户状态 + 登录方法
│   └── loginModal.ts     # 登录弹窗状态
├── components/
│   └── auth/
│       └── LoginModalRoot.vue  # 全局登录弹窗容器
└── pages/
    └── my/
        └── components/
            └── LoginModal.vue  # 登录弹窗主体

这样设计的好处:
1. 任意页面调用 loginModalStore.open() 即可触发登录
2. 登录逻辑集中在 LoginModal,不分散到各页面
3. 支持回调机制,登录成功后执行特定操作
我:这个架构看起来不错,开始实现吧

三、登录弹窗状态管理

3.1 设计 loginModalStore

AI:先实现登录弹窗的状态管理。

核心功能:
1. 控制弹窗显示/隐藏
2. 管理登录成功回调队列
3. 触发登录流程或资料完善流程
// src/store/loginModal.ts
import { defineStore } from 'pinia';
import type { UserInfo } from 'shared-types';

type LoginSuccessHandler = (userInfo?: UserInfo) => void | Promise<void>;

interface LoginModalState {
  visible: boolean;
  shouldStart: boolean; // 是否触发登录流程
  shouldStartProfileCompletion: boolean; // 是否触发资料完善流程
  queue: LoginSuccessHandler[]; // 回调队列
}

export const useLoginModalStore = defineStore('loginModal', {
  state: (): LoginModalState => ({
    visible: false,
    shouldStart: false,
    shouldStartProfileCompletion: false,
    queue: [],
  }),

  actions: {
    /**
     * 打开登录弹窗
     * @param options.onSuccess - 登录成功后的回调
     * @param options.startProfileCompletion - 是否直接进入资料完善
     */
    open(options?: { onSuccess?: LoginSuccessHandler; startProfileCompletion?: boolean }) {
      // 将回调加入队列
      if (options?.onSuccess) {
        this.queue.push(options.onSuccess);
      }

      this.visible = true;

      if (options?.startProfileCompletion) {
        this.shouldStartProfileCompletion = true;
      } else {
        this.shouldStart = true;
      }
    },

    /**
     * 登录流程已启动(由 LoginModalRoot 调用)
     */
    markStarted() {
      this.shouldStart = false;
    },

    /**
     * 资料完善流程已启动
     */
    markProfileCompletionStarted() {
      this.shouldStartProfileCompletion = false;
    },

    /**
     * 登录成功,执行所有回调
     */
    async resolveSuccess(userInfo?: UserInfo) {
      const callbacks = [...this.queue];
      this.queue = [];

      // 依次执行回调
      for (const callback of callbacks) {
        await callback(userInfo);
      }

      this.visible = false;
      this.shouldStart = false;
      this.shouldStartProfileCompletion = false;
    },

    /**
     * 关闭弹窗
     */
    close() {
      this.visible = false;
      this.shouldStart = false;
      this.shouldStartProfileCompletion = false;
      this.queue = [];
    },
  },
});
AI:使用方式:

// 任意页面触发登录
import { useLoginModalStore } from '@/store/loginModal'

const loginModalStore = useLoginModalStore()

// 场景1:简单触发登录
loginModalStore.open()

// 场景2:登录成功后执行操作
loginModalStore.open({
  onSuccess: (userInfo) => {
    console.log('登录成功:', userInfo)
    // 继续之前的操作,如发送消息、购买会员等
  }
})

// 场景3:直接进入资料完善(用户已登录但未填资料)
loginModalStore.open({ startProfileCompletion: true })

3.2 我追问回调队列的作用

我:为什么要用队列?直接存一个回调不行吗?
AI:队列是为了处理"连续触发"的场景。

🎯 场景示例:
用户在页面 A 点击"发送消息"(需要登录)
→ 弹出登录框
→ 用户没登录,又点击了"收藏"(也需要登录)
→ 这时有两个回调等待执行

如果只存一个回调,第二个会覆盖第一个。
用队列就能保证两个操作都能在登录后执行。

当然,如果你的业务不需要这种场景,用单个回调也可以。

四、全局登录弹窗容器

4.1 LoginModalRoot 设计

我:loginModalStore 有了,但弹窗组件在哪里渲染?
    每个页面都引入一遍吗?
AI:不用。创建一个全局容器组件,挂载在 App 层。

设计思路:
1. LoginModalRoot 放在 App.vue 或 Layout 中
2. 它监听 loginModalStore 的状态
3. 状态变化时,调用 LoginModal 的方法
<!-- src/components/auth/LoginModalRoot.vue -->
<template>
  <LoginModal ref="loginModalRef" v-model="modalVisible" @login-success="handleLoginSuccess" />
</template>

<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue';
import type { UserInfo } from 'shared-types';
import LoginModal from '@/pages/my/components/LoginModal.vue';
import { useLoginModalStore } from '@/store/loginModal';

const loginModalStore = useLoginModalStore();
const loginModalRef = ref<InstanceType<typeof LoginModal> | null>(null);

// 双向绑定弹窗显示状态
const modalVisible = computed({
  get: () => loginModalStore.visible,
  set: (value: boolean) => {
    loginModalStore.setVisible(value);
  },
});

// 登录成功处理
const handleLoginSuccess = async (userInfo?: UserInfo) => {
  await loginModalStore.resolveSuccess(userInfo);
};

// 监听"开始登录"信号
watch(
  () => loginModalStore.shouldStart,
  async (shouldStart) => {
    if (!shouldStart) return;
    await nextTick();
    loginModalRef.value?.startLogin();
    loginModalStore.markStarted();
  },
);

// 监听"开始资料完善"信号
watch(
  () => loginModalStore.shouldStartProfileCompletion,
  async (shouldStart) => {
    if (!shouldStart) return;
    await nextTick();
    loginModalRef.value?.startProfileCompletion();
    loginModalStore.markProfileCompletionStarted();
  },
);
</script>

4.2 挂载到全局

AI:把 LoginModalRoot 挂载到 Layout 或 App 层:
<!-- src/layouts/default.vue -->
<template>
  <slot />
  <!-- 全局登录弹窗 -->
  <LoginModalRoot />
</template>

<script setup lang="ts">
import LoginModalRoot from '@/components/auth/LoginModalRoot.vue';
</script>
AI:这样所有使用 default 布局的页面都能触发登录弹窗,
不需要每个页面单独引入。

五、多步骤登录弹窗

5.1 登录流程设计

我:现在要实现 LoginModal 本身了。
    流程是:登录 → 性别选择 → 年龄选择 → 完成
AI:我来设计多步骤的状态机:

📋 步骤状态:
- login: 登录界面(微信授权 + 协议勾选)
- gender: 性别选择(新用户)
- age: 年龄选择(新用户)

📋 流程逻辑:
1. 用户点击登录 → 调用微信登录
2. 检查返回结果:
   - 有 token + user_id → 老用户,直接成功
   - 无 token → 需要手机号授权
   - needsRegistration=true → 新用户,进入资料完善
<!-- src/pages/my/components/LoginModal.vue -->
<template>
  <u-popup
    v-model="isVisible"
    mode="bottom"
    border-radius="24"
    :safe-area-inset-bottom="true"
    :closeable="true"
    @close="handleClose"
  >
    <view class="login-modal-content">
      <!-- 步骤1:登录界面 -->
      <view v-if="currentStep === 'login'" class="login-step">
        <view class="pt-4.5 pb-10">
          <text class="block text-center text-lg font-bold">欢迎登录</text>
        </view>

        <!-- 登录按钮 -->
        <view class="px-9 pb-4">
          <XButton
            text="手机号快捷登录"
            :loading="isLoading"
            :open-type="needPhoneAuth ? 'getPhoneNumber' : undefined"
            @getphonenumber="handlePhoneNumber"
            @click="handleLoginClick"
          />
        </view>

        <!-- 协议勾选 -->
        <view class="px-9 pb-20">
          <view class="flex items-center justify-center" @click="toggleAgreement">
            <view
              class="w-5 h-5 rounded-full border flex items-center justify-center"
              :class="isAgreed ? 'bg-primary border-primary' : 'border-gray-400'"
            >
              <u-icon v-if="isAgreed" name="checkmark" size="20" color="#fff" />
            </view>
            <text class="ml-2 text-sm">
              勾选同意
              <text class="text-primary" @click.stop="openAgreement('user')">《用户协议》</text>
              和
              <text class="text-primary" @click.stop="openAgreement('privacy')">《隐私政策》</text>
            </text>
          </view>
        </view>
      </view>

      <!-- 步骤2:性别选择 -->
      <view v-else-if="currentStep === 'gender'" class="gender-step">
        <view class="pt-4 pb-10">
          <text class="block text-center text-lg font-bold">选择你的性别</text>
          <text class="block text-center text-sm text-gray-500 mt-2">更精准匹配回复话术</text>
        </view>

        <view class="flex justify-center gap-8 pb-20">
          <view
            v-for="gender in genderOptions"
            :key="gender.value"
            class="flex flex-col items-center"
            @click="selectGender(gender.value)"
          >
            <image :src="gender.icon" class="w-32 h-32" />
            <text class="mt-2">{{ gender.label }}</text>
            <view
              v-if="selectedGender === gender.value"
              class="w-5 h-5 rounded-full bg-primary mt-2"
            />
          </view>
        </view>
      </view>

      <!-- 步骤3:年龄选择 -->
      <view v-else class="age-step">
        <view class="pt-4 pb-10">
          <text class="block text-center text-lg font-bold">选择你的年龄段</text>
        </view>

        <view class="flex flex-wrap justify-center gap-4 pb-20">
          <view
            v-for="age in ageOptions"
            :key="age"
            class="px-6 py-3 rounded-full"
            :class="selectedAge === age ? 'bg-primary text-white' : 'bg-gray-100'"
            @click="selectAge(age)"
          >
            {{ age }}
          </view>
        </view>
      </view>
    </view>
  </u-popup>
</template>

5.2 登录逻辑实现

// LoginModal.vue <script setup>
import { ref, computed } from 'vue';
import { useUserStore } from '@/store/user';
import { toast } from '@/utils/toast';
import { GenderEnum, AgeGroupEnum } from 'shared-types';
import { requestWechatLoginCode } from '@/utils/wechat';

const userStore = useUserStore();

// 当前步骤
const currentStep = ref<'login' | 'gender' | 'age'>('login');

// 状态
const isAgreed = ref(false);
const isLoading = ref(false);
const needPhoneAuth = ref(false);
const selectedGender = ref('');
const selectedAge = ref('');

// 性别和年龄选项
const genderOptions = [
  { value: 'male', label: '男', icon: '/static/images/male.png' },
  { value: 'female', label: '女', icon: '/static/images/female.png' },
];
const ageOptions = ['00后', '05后', '90后', '80后', '70后'];

/**
 * 处理登录按钮点击
 */
const handleLoginClick = async () => {
  if (!isAgreed.value) {
    toast.warning('请勾选同意用户协议');
    return;
  }

  // 如果需要手机号授权,由 open-type 处理
  if (needPhoneAuth.value) return;

  await performWechatLogin();
};

/**
 * 执行微信登录
 */
const performWechatLogin = async () => {
  isLoading.value = true;
  try {
    // 1. 获取微信 code
    const loginCode = await requestWechatLoginCode();

    // 2. 调用 Store 登录方法
    const result = await userStore.wechatLogin({ code: loginCode });

    // 3. 判断结果
    if (result.token && result.user_id) {
      // 已有账号
      if (result.needsRegistration) {
        // 新用户,需要完善资料
        currentStep.value = 'gender';
      } else {
        // 老用户,直接成功
        completeLogin();
      }
    } else {
      // 需要手机号授权
      needPhoneAuth.value = true;
    }
  } catch (error) {
    console.error('微信登录失败:', error);
    toast.error('登录失败,请重试');
  } finally {
    isLoading.value = false;
  }
};

/**
 * 处理手机号授权
 */
const handlePhoneNumber = async (event: any) => {
  const { code, errMsg } = event.detail || {};

  if (!code) {
    if (errMsg?.includes('user deny')) {
      toast.info('已取消手机号授权');
    }
    return;
  }

  isLoading.value = true;
  try {
    const loginCode = await requestWechatLoginCode();
    const result = await userStore.phoneLogin({
      code,
      login_code: loginCode,
    });

    if (result.needsRegistration) {
      currentStep.value = 'gender';
    } else {
      completeLogin();
    }
  } catch (error) {
    toast.error('验证手机号失败');
  } finally {
    isLoading.value = false;
  }
};

/**
 * 选择性别
 */
const selectGender = (gender: string) => {
  selectedGender.value = gender;
  // 延迟跳转,让用户看到选择效果
  setTimeout(() => {
    currentStep.value = 'age';
  }, 500);
};

/**
 * 选择年龄
 */
const selectAge = async (age: string) => {
  selectedAge.value = age;

  // 提交资料
  setTimeout(async () => {
    await submitProfile();
  }, 300);
};

/**
 * 提交用户资料
 */
const submitProfile = async () => {
  const genderValue = selectedGender.value === 'male' ? GenderEnum.MALE : GenderEnum.FEMALE;

  const ageMapping: Record<string, number> = {
    '00后': AgeGroupEnum.POST_00,
    '05后': AgeGroupEnum.POST_05,
    '90后': AgeGroupEnum.POST_90,
    '80后': AgeGroupEnum.POST_80,
    '70后': AgeGroupEnum.POST_70,
  };

  await userStore.updateUserInfo(
    {
      gender: genderValue,
      age_group: ageMapping[selectedAge.value],
    },
    '登录成功',
  );

  completeLogin();
};

/**
 * 完成登录
 */
const completeLogin = () => {
  emit('login-success', userStore.userInfo);
  handleClose();
};

/**
 * 关闭弹窗
 */
const handleClose = () => {
  isVisible.value = false;
  // 重置状态
  setTimeout(() => {
    currentStep.value = 'login';
    isAgreed.value = false;
    selectedGender.value = '';
    selectedAge.value = '';
    needPhoneAuth.value = false;
  }, 300);
};

// 暴露方法给父组件
defineExpose({
  startLogin: () => {
    resetState();
    isVisible.value = true;
  },
  startProfileCompletion: () => {
    currentStep.value = 'gender';
    isVisible.value = true;
  },
});

六、业务页面集成

6.1 触发登录的场景

我:登录弹窗做好了,业务页面怎么用?
AI:业务页面只需要调用 loginModalStore.open():
// 场景1:需要登录才能操作的按钮
const handleSendMessage = () => {
  if (!userStore.isLoggedIn) {
    loginModalStore.open({
      onSuccess: () => {
        // 登录成功后,继续发送消息
        doSendMessage();
      },
    });
    return;
  }
  doSendMessage();
};

// 场景2:封装成通用的登录检查函数
const requireLogin = (callback: () => void) => {
  if (userStore.isLoggedIn) {
    callback();
    return;
  }

  loginModalStore.open({
    onSuccess: () => callback(),
  });
};

// 使用
const handleBuyVip = () => {
  requireLogin(() => {
    uni.navigateTo({ url: '/pages/vip/index' });
  });
};

const handleCollect = () => {
  requireLogin(() => {
    doCollect();
  });
};

6.2 我追问:能不能更简洁

我:每次都要写 if (!isLoggedIn) 很繁琐,能不能更简洁?
AI:可以封装成 Hook:
// src/hooks/useRequireLogin.ts
import { useUserStore } from '@/store/user';
import { useLoginModalStore } from '@/store/loginModal';

export function useRequireLogin() {
  const userStore = useUserStore();
  const loginModalStore = useLoginModalStore();

  /**
   * 需要登录才能执行的操作
   */
  const requireLogin = (callback: () => void | Promise<void>) => {
    if (userStore.isLoggedIn) {
      callback();
      return;
    }

    loginModalStore.open({
      onSuccess: async () => {
        await callback();
      },
    });
  };

  /**
   * 创建需要登录的处理函数
   */
  const withLogin = <T extends (...args: any[]) => any>(fn: T): T => {
    return ((...args: Parameters<T>) => {
      requireLogin(() => fn(...args));
    }) as T;
  };

  return {
    requireLogin,
    withLogin,
    isLoggedIn: computed(() => userStore.isLoggedIn),
  };
}
// 页面中使用
const { requireLogin, withLogin, isLoggedIn } = useRequireLogin();

// 方式1:包装函数
const handleSendMessage = () => {
  requireLogin(() => {
    doSendMessage();
  });
};

// 方式2:直接创建带登录检查的函数
const handleBuyVip = withLogin(() => {
  uni.navigateTo({ url: '/pages/vip/index' });
});

const handleCollect = withLogin(() => {
  doCollect();
});

七、登录状态判断优化

7.1 预检微信登录状态

我:每次打开登录弹窗都要等用户点击,
    能不能提前知道需不需要手机号授权?
AI:可以在打开弹窗时"预检"微信登录状态:
// LoginModal.vue
const determineLoginStrategy = async () => {
  if (!isMpWeixin) {
    needPhoneAuth.value = false;
    return;
  }

  try {
    // 静默获取 code,尝试登录
    const loginCode = await requestWechatLoginCode();
    const response = await loginByWechat({
      code: loginCode,
      source: clientSource,
      channel: clientChannel,
    });

    // 如果能直接登录,不需要手机号
    if (response.code === 200 && response.data?.token) {
      needPhoneAuth.value = false;
    } else {
      needPhoneAuth.value = true;
    }
  } catch (error) {
    needPhoneAuth.value = true;
  }
};

// 打开弹窗时调用
const startLogin = () => {
  resetState();
  isVisible.value = true;
  // 预检登录状态
  void determineLoginStrategy();
};
AI:这样的好处:
1. 老用户:按钮显示"微信登录",点击直接成功
2. 新用户:按钮显示"手机号快捷登录",需要授权

用户体验更顺畅,不用点两次。

八、核心经验:登录系统设计要点

8.1 架构设计原则

原则 说明
状态集中 loginModalStore 统一管理弹窗状态和回调
组件分离 LoginModalRoot 负责桥接,LoginModal 负责 UI 逻辑
全局可用 挂载在 Layout 层,任意页面可触发
回调机制 支持登录成功后执行特定操作

8.2 流程设计要点

// ✅ 推荐:多步骤用状态机
const currentStep = ref<'login' | 'gender' | 'age'>('login');

// ❌ 不推荐:多个 boolean 控制
const showLogin = ref(true);
const showGender = ref(false);
const showAge = ref(false);
// ✅ 推荐:预检登录状态
const startLogin = () => {
  void determineLoginStrategy(); // 提前判断需要哪种登录
};

// ❌ 不推荐:用户点击才判断
const handleClick = () => {
  // 点击后才知道需要手机号,体验差
};

8.3 错误处理

// 区分不同的错误场景
const showWechatLoginError = (error: any) => {
  if (error?.code === -8) {
    toast.error('未安装微信客户端');
    return;
  }
  toast.error('登录失败,请重试');
};

// 手机号授权取消 vs 失败
const handlePhoneNumber = (event: any) => {
  const { code, errMsg } = event.detail;
  if (!code) {
    if (errMsg?.includes('user deny')) {
      toast.info('已取消授权'); // 用户主动取消,不是错误
    } else {
      toast.error('获取手机号失败'); // 真正的错误
    }
    return;
  }
};

九、总结:登录系统的完整实现

9.1 文件清单

文件 职责
store/loginModal.ts 弹窗状态 + 回调队列
store/user.ts 用户状态 + 登录方法
components/auth/LoginModalRoot.vue 全局弹窗容器
pages/my/components/LoginModal.vue 登录弹窗 UI + 逻辑
hooks/useRequireLogin.ts 登录检查 Hook

9.2 关键收获

  1. 架构先行:先设计整体架构,再实现细节
  2. 状态集中:用 Store 管理弹窗状态和回调
  3. 多步骤流程:用状态机管理,避免多个 boolean
  4. 体验优化:预检登录状态,减少用户等待
  5. 错误区分:用户取消 vs 系统错误,提示不同

9.3 下一篇预告

《【AI 编程实战】第 8 篇:组件封装的艺术 - 从业务代码到可复用组件》

下一篇展示如何设计通用组件:

  • 从业务代码中提取组件
  • Props 和 Events 设计
  • 组件的扩展性和灵活性

登录系统不只是"调用接口",而是用户体验、状态管理、错误处理的综合考验。 通过和 AI 对话,逐步理清每个环节,最终形成完整的解决方案。

如果这篇文章对你有帮助,请点赞、收藏、转发!

AI时代2025年下半年学的这些Web前端特性有没有用?

作者 张鑫旭
2026年1月12日 18:19

40岁老前端学习记录第6波,每半年一次。对前五次学习内容感兴趣的可以去我的掘金专栏“每周学习记录”进行了解。

不过这次前端记录有别于过往,会聊一聊学这些东西有没有价值。

先看学了什么。

第27周 2025.6.30-7.6

本周学习Select原生组件的样式自定义。

其中按钮部分若想完全自定义,使用下面的CSS代码:

select { appearance: base-select; }

若想下拉部分的样式可以自定义,则需要使用::picker()伪元素函数设置:

::picker(select) { appearance: base-select; }

此时,select元素和option元素就可以自如的自定义了。

其中,::picker-icon伪元素是设置下拉按钮后面那个三角的:

::checkmark伪元素伪元素指向的是下拉列表选中选项前面的勾勾:

根据我的实践,已经可以完全覆盖<select>元素的样式自定义了,唯一问题是目前仅Chrome浏览器支持。

更多更具体的细节参见我写的这篇文章:“好诶,select下拉框元素支持样式完全自定义啦!

第28周 2025.7.7-7.13

本周学习3个与HTML相关的东西。

1. 全新的search元素

Chrome 117和Safari 17新支持了一个HTML元素名为 <search>,用于定义搜索区域,通常作为表单的一部分。

兼容性如下图所示:

2. h1元素样式规则要变

如果页面有多个h1元素,且这些h1元素被<section>,<aside>,<nav>, 以及<article>等元素嵌套,那么h1元素会自动降级为h2, h3, h4这种。例如:

渲染效果则是这样的:

不过,浏览器可能会改变这种策略,即所有h1的字号保持一致。

然后这种变化是和CSS :heading伪类和:heading()函数一起出现的。

3. 声明式Shadow DOM

无需JavaScript即可定义Shadow DOM,支持服务器端渲染(SSR),提升组件隔离性。

示意代码:

也就是,DOM内容无需像过去一样,全部都通过JavaScript创建的。

但是,如果有交互行为,通常还是需要借助JS代码的,包括自定义组件的注册等。

由于HTML结构可以直接呈现,因此,后端输出就非常方便,SEO也会比过去更友好。

兼容性:

第29周 2025.7.14-7.20

本周学习的是JS新特性,还挺实用的。

RegExp.escape()是所有前端都要熟知的静态函数,用于安全地将字符串转换为正则表达式字面量,目前所有现代浏览器都已经支持。

详见此文:“JS正则新特性:安全过滤RegExp.escape方法

第30周 2025.7.21-7.27

本周学习CSS的if()函数。

CSS之前也支持类逻辑函数,例如数学函数中的min()max(),本质上也是有逻辑的,例如:max(300px, 100vw),表示:

  • 如果100vw的计算宽度大于300px,则最终尺寸300px

  • 否则是100vw的宽度。

现在,CSS支持 if() 函数了,这个是真正意义上的逻辑函数了。

目前,if() 支持三种不同类型的查询:

  • style():样式查询

  • media():媒体查询

  • supports():支持查询

其中,用得最多的当属 style() 样式查询,具体使用案例参见此文:“CSS倒反天罡居然支持if()函数了

第31周 2025.7.28-8.3

本周学习HTML popover属性新增的值hint

Popover可以说是弹出浮层最佳解决方案,自动显隐特性,外加顶层特性,非常香。

但是有一个问题,那就是一次最多只能有一个弹出层显示(如果不使用 showPopover() 方法手动设置的话),例如下面的Tips提示出现的时候会关闭前面的列表。

类似这种仅仅是用做信息展示的轻提示,其实无需关闭之前的弹出层,之前的popover属性能力就不够。

于是,从Chrome 133开始,popover属性新增了属性值hint,可以让轻提示显示的时候不会影响之前的弹出层。

目前仅Chrome浏览器支持:

第32周 2025.8.4-8.10

本周学习CSS索引和数量匹配函数sibling-index()sibling-count()

sibling-index()可以返回当前子元素是第几个,sibling-count()可以返回当前元素的父元素总共有多少个子元素。

这两个函数特别适合用在很多重复子元素需要动态匹配计算的场景。

例如下面的浮动等宽布局示意:

渲染效果如下(目前仅Chrome才有效果),宽度自动等比例分配:

更多细节可以参考我写的这篇文章:“CSS索引和数量匹配函数sibling-index sibling-count简介

第33周 2025.8.11-8.17

本周学习CSS @container scroll-state容器滚动查询。

此特性可以让我们,仅仅通过CSS,就可以检测是否滚动到容器边缘,Scroll Snap边缘,以及sticky元素是否触及定位的边缘。

 然后我们就可以实现更加精确友好的交互控制,参见下面的GIF示意。

还是挺实用的一个特性,例如很多文档需要阅读到底部才能按钮提交,就可以使用这里的CSS技术,无需JavaScript代码判断了。

详见此文:“CSS @container scroll-state容器滚动查询

第34周 2025.8.18-8.24

本周学习的是CSS社区呼声已久的一个特性,叫做corner-shape属性。

CSS的corner-shape属性是CSS背景与边框模块(Level 4)中引入的新特性,它通过扩展border-radius的功能,允许开发者自定义元素的角形状,而不仅限于传统的圆角效果。以下是其核心作用及特性:

扩展border-radius的几何形状

  • 传统圆角border-radius默认生成四分之一椭圆形的圆角(round)。

  • 新增形状corner-shape支持五种专业级角形状:

    • bevel(斜角):用直线替代圆弧,生成切角效果。

    • scoop(凹槽):创建内凹的弧形角。

    • notch(缺口):添加微小内凹的缺口。

    • squircle(超椭圆):介于圆和矩形之间的平滑形状。

    • 保留默认的round(圆角)。

该特性可以实现各种五花八门的图形效果。

特性是个好特性,可惜太新了点,目前仅Chrome 139以上版本才正式支持。

有兴趣的可以访问此文进行进一步了解:抢先学习大开眼界的CSS corner-shape属性

第35周 2025.8.25-8.31

本周学习的是HTML新特性,啥呢?

就是hidden属性支持了一个名为until-found的新值,默认不可见,但是如果用户搜索匹配,或者锚点定位了,就会自动显示,有助于特定场景下提高用户体验。

until-found隐藏的元素还支持一个名为beforematch的事件,在该元素从隐藏变成显示之前的一瞬间执行。

详见我撰写的这篇文章:“HTML之快速了解hidden=until-found的作用

第36周 2025.9.1-9.7

本周学习的也是个挺厉害的CSS特性,那就是@function规则,可以在CSS中函数编程了。

下面的代码是一个响应式布局的函数定义示意。

/* 在小于640px的屏幕上为侧边栏占用1fr的空间,在较大的屏幕上使用相对具体具体宽度值 */
@function --layout-sidebar(--sidebar-width: max(20ch, 20vw)) {
  result: 1fr;
  
  @media (width > 640px) {
    result: var(--sidebar-width) auto;
  }
}

.layout {
  display: grid;
  /*侧边栏宽度兜底20ch和20vw的较大计算值 */
  grid-template-columns: --layout-sidebar();
}

布局效果参见(需要Chrome 139+):

@function 规则可以定义可重用的计算逻辑,让 CSS 代码更灵活、模块化且易于维护。

更多案例与细节见我写的这篇文章:“@function自定义函数让CSS支持编程啦

第37周 2025.9.8-9.14

本周学习使用CSS linear()函数。

CSS 中的linear()函数用于创建自定义的线性缓动效果,它允许你通过定义多个关键点来精确控制动画在不同阶段的播放进度和速度变化。

无论是更加逼真的物理动画,还是特殊的阶梯式动画都不在话下。

目前所有浏览器都已经支持。

借助转换工具,可以将各类JS缓动动画变成CSS linear()函数,还是很厉害的一个特性。

详见我撰写的这篇文章:“使用CSS linear()函数实现更逼真的物理动画效果

第38周 2025.9.15-9.21

本周学习的新特性也是期盼已久的,几行CSS就能实现页面滚动的时候,菜单栏根据标题内容自动高亮。

使用的是CSS scroll-target-group属性以及:target-current伪类。

实现代码非常简单,HTML代码就下面这些:

<menu>
    <li><a href="#intro">前言</a></li>
    <li><a href="#ch1">第1章</a></li>
    <li><a href="#ch2">第2章</a></li>
</menu>

<article>
    <h1>欢迎来到我的博客</h1>
    <section id="intro">...</section>
    <section id="ch1">...</section>
    <section id="ch2">...</section>
</article>

CSS代码也非常简单,就这么点内容:

menu {
    position: fixed;
    scroll-target-group: auto;
}

a:target-current {
    color: red;
}

结束了,就结束了,对吧,给菜单容器设置scroll-target-group:auto,然后菜单里面的链接元素使用:target-current设置匹配样式就可以了。

此时,链接元素对应的href锚点元素进入区域的时候,链接元素就会高亮啦!

牛逼!

目前Chrome浏览器Only!

详见我撰写的这篇文章:“CSS scroll-target-group加:target-current滚动菜单自动高亮

第39周 2025.9.22-9.28

本周学习的是JS特性,Intl.Segmenter构造函数。

Intl.Segmenter不仅能精准返回Emoji字符的长度,还能实现中文语言的自动分词,分句效果,这个特性比预想的实用的多了。

目前所有现代浏览器都支持。

更多内容参见我的这篇文章:“使用Intl.Segmenter返回更准确的字符长度

第40周 2025.9.29-10.5

国庆假期,学什么学,起来high啦!

第41周 2025.10.6-10.12

本周学习CSS field-sizing属性,该属性可以让文本输入框根据输入内容的多少自动改变宽度和高度大小。

某些场景下,还是挺实用的。

例如<textarea> 元素内容输入的时候,高度自动变高。

以前都是需要使用div元素模拟,或者JS代码计算,现在就一行CSS代码的事情。

Chrome已经支持,Safari即将大范围支持,这个CSS的应用前景还可以。

更多内容,可见我撰写的这篇文章:“CSS field-sizing属性简介

第42周 2025.10.13-10.19

本周学习CSS reading-flowreading-order和两个新属性。

Web开发中,DOM文档的属性和视觉表现顺序不一致是很正常的。

在过去,我们会使用HTML tabindex属性进行交互处理。

但是HTML tabindex属性在页面复杂的时候,容易冲突。

在这种背景下,reading-flowreading-order属性应运而生。

其可以让可访问元素的Tab索引顺序和视觉表现顺序表现一致

目前仅Chrome浏览器支持:

注定沦为小众特性,有兴趣可以前往这里了解更多:“CSS reading-flow和reading-order属性简介

第43周 2025.10.20-10.26

本周学习Canvas新支持的锥形渐变绘制方法createConicGradient方法

CSS虽然也能实现锥形渐变,但是如果遇到需要前端截图的需求,那么CSS方案就会有问题,html2canvas并不支持锥形渐变。

此时,可以试试使用Canvas绘制图形。

浏览器全支持,下图就是使用createConicGradient()方法绘制的饼图。

完整代码和语法说明,参见我的这篇文章:“Canvas也支持锥形渐变了createConicGradient方法

第44周 2025.10.27-11.2

本周学习CSS :heading伪类和:heading()函数。

这两个CSS选择器是用来选择h1-h6元素的。

唉,说实话,这是我见过最让我失望的新特性。

完全看不到任何使用这个特性的理由。

是让人无语的垃圾特性。

后来经过同行提醒,这两个CSS伪类匹配的是语义上是h1~h6的元素,参考第28周学习的h1元素自动降级。

不过还是很烂的特性。

为什么说烂呢?有兴趣的可以访问这里进行了解:“垃圾特性之CSS :heading伪类和:heading()函数

第45周 2025.11.3-11.9

本周学习使用CSS锚点定位实现元素的鼠标跟随效果。

下图是其中一个案例的示意:

悬停跟着走,移开恢复到选中元素上。

具体实现参见这篇文章:“CSS锚点定位实战-鼠标跟随交互效果

第46周 2025.11.10-11.16

本周学习CSS :state()伪类函数。

此选择器函数可以匹配Web Components内部通过attachInternals添加的状态,从而实现穿透组件,实现更加精准的样式设置。

兼容性如下:

我觉得还算个不错的设计。

具体案例和细节,可以参见我写的这篇文章:“介绍下与CSS自定义组件相关的:state()函数

第47周 2025.11.17-11.23

本周学习了CSS ::details-content伪元素,可以用来匹配<details>元素的内容DOM,我们可以使用这个伪元素设置背景,边框等样式。

不过,实际开发,还是使用它实现动画效果更多一些。

目前所有现代浏览器都已经支持了这个特性。

详见这篇文章:巧用CSS ::details-content伪元素实现任意展开动画

第48周 2025.11.24-11.30

本周学习CookieStore对象。

在前端开发的长河中,Cookie始终扮演着重要角色。

从用户身份识别到状态维持,它是浏览器与服务器之间轻量通信的核心载体。

但长期以来,我们操作Cookie的方式始终停留在通过document.cookie拼接字符串的“原始阶段”,不仅代码冗余易出错,还无法应对异步场景下的复杂需求。

比方说,我们要设置一个Cookie,需要先获取,然后再手动进行键值对+属性的字符串拼接,例如:

document.cookie = "name=zhangxinxu; max-age=3600; path=/"

这就很麻烦,属性顺序、符号格式稍有偏差就会导致失效。

正是因为上面这些不足,CookieStore API才应运而生。

它将Cookie操作封装为标准化的异步方法,让Cookie管理变得简洁、可控。

详见我的这篇文章:“醒醒,该使用CookieStore新建和管理cookie了

第49周 2025.12.1-12.7

本周学习单IMG标签的图片内阴影效果实现

列举了三种方法:

  1. 如果图片背景纯色,可以通过添加padding,让box-shadow内阴影显示。

  2. 如果不考虑兼容性,可以使用attr()函数让图片地址作为背景图显示。

  3. 最后一种方法,任意图片,且兼容性极佳,就是使用SVG滤镜。

效果示意:

详见我的这篇文章:“单IMG标签的图片内阴影效果实现

第50周 2025.12.8-12.14

本周学习CSS progress()函数。

语法:

progress(<value>, <start>, <end>)

可以返回0-1之间的进度值,常用于进度条、动态过渡、动画关键帧等场景,是 CSS 原生实现进度关联样式的核心工具。

还挺实用的。

但是兼容性一般。

详见我的这篇文章:”CSS progress()函数简介

第51周 2025.12.15-12.21

本周学习CSS锚点定位锚定容器回退检测语法。

CSS锚点定位有边界位置自动改变位置的能力,但是,浏览器却无法检测,导致很多交互效果并不能完全使用CSS实现,着实可惜。

然后,最近,Chrome浏览器新支持了一个特性,那就是锚定容器位置查询检测语法。

.float-element { position-try-fallbacks: flip-block; container-type: anchored; }@container anchored(fallback: flip-block) {   .float-element { /* 如果垂直定位方向改变,如何如何…… */ }}

可以实现下图所示的交互效果,纯CSS实现的:

详见我的这篇文章:“补全不足,CSS锚点定位支持锚定容器回退检测了

第52周 2025.12.22-12.28

本周学习CSS style()样式查询及其range范围语法。

比方说下面的案例,识别CSS变量范围,显示不同的文字颜色。

<span class="score" style="--score: 95;">
  <data>95</data>
</span>
<span class="score" style="--score: 85;">
  <data>85</data>
</span>
<span class="score" style="--score: 65;">
  <data>65</data>
</span>
<span class="score" style="--score: 35;">
  <data>35</data>
</span>

此时,就可以在style()函数中,使用大于号,小于号进行匹配:

@container style(--score >= 90) {
  data {
    color: gold;
  }
}
@container style(--score >= 80) and style(--score < 90) {
  data {
    color: green;
  }
}
@container style(--score >= 60) and style(--score < 80) {
  data {
    color: orange;
  }
}
@container style(--score < 60) {
  data {
    color: red;
  }
}

通俗易懂,三岁小孩也能知道是什么意思。

效果图参考:

Chrome和Safari浏览器都支持,而且已经支持一段时间了。

另外,此查询和attr()if()等函数配合使用后,该特性的场景适应性更是直线攀升。

详见这篇文章:“今日学习CSS style()样式查询及其range范围语法

-------------

好,以上就是我这个40岁的老前端2025年下半年学习的内容。

接下来回答很多前端同行关心的一个问题,都什么年代了,还学这些细枝末节的东西,有什么用?

下面简单说说我的看法,注意,篇幅原因,真就简单说说,回头我专门写一篇聊聊这个事情。

前端还值得学习吗?

结论,还是要学习的,只是需要从以前的熟悉降为了解,然后把节约的精力去学习其他东西。

我本人是AI重度使用者,编程、写作、甚至在自制AI漫剧。

对目前AI编程能力的边界感受挺深的。

短期

以目前的AI能力,当我们实现一个需求的时候,它所实现的代码往往是基于历史代码训练的最稳健的实现,功能运行没问题,但是很多时候,并不是最佳实现。

我已经遇到很多类似的例子了。

例如一个划词评论功能,实现代码洋洋洒洒,实则啰嗦无比,非要提醒AI使用selectionchange事件,代码这才指数级腰斩。

又比如一个富文本输入框中的AT功能,也是一堆selection和range控制,天书一样,未能充分应用输入框自身拖拽、回撤等能力。

我再举本文中出现的一个例子,实现<textarea>多行文本输入框跟随内容高度自动的问题,我可以100%打包票,AI一定会使用JavaScript代码实现,他不会想到说使用CSS field-sizing实现,因为field-sizing有兼容性问题,如果开发者不主动让他优先使用field-sizing,AI绝对不会选择这个技术选型的。

但是,其实很多项目是不需要考虑兼容性的,只需要最新的Chrome浏览器支持。

下面问题来了,如果开发人员不学习,不知道有这么个东西,请问,这个可以大大简化代码量,降低复杂度的东西如何在项目中应用?

如果只奔着功能实现,公司为何还需要你,找个刚毕业的大学生不更香吗?

所以,就目前而言,学肯定是要学的,但是我们不需要去掌握语法和细节了,只需要知道有这么个东西,在什么场景下使用最合适就可以了。

作为优秀的指挥官,务必高屋建瓴,对吧,都有哪些手下、各自优缺点务必要了然于心,这样指挥作战的时候才能发挥出最大的威能。

长期

不过AI本身也是发展的,有可能:

1. AI自动分析项目运行环境,目标客户群体,或者适应技术人员的风格癖好,选择最适合的技术方案。

2. 不是人指挥AI,而是AI指挥人——老子会很多,你想要使用哪一个。

到时候,说不定这些广度知识也就没有必要去花时间去学习的了。

嗯……想了下,试试现在AI有没有这个水平吧。

使用Trae测试。

我让其新建一个空白HTML页面。

结果其自以为是连功能都实现了,果不其然是JavaScript实现的:

然后我对其进行使用其他技术的提示,结果还是一些传统实现:

我看了下其contenteditable的实现,很糟糕,没有关闭富文本的能力:

document.querySelectorAll('.editable-div').forEach(div => {    const placeholder = div.getAttribute('data-placeholder');        // 设置初始placeholder    if (placeholder && !div.textContent.trim()) {        div.innerHTML = `<span style="color: #aaa;">${placeholder}</span>`;    }        // 点击时清除placeholder    div.addEventListener('click', () => {        if (div.innerHTML.includes(placeholder)) {            div.innerHTML = '';        }    });        // 失去焦点时恢复placeholder    div.addEventListener('blur', () => {        if (!div.textContent.trim() && placeholder) {            div.innerHTML = `<span style="color: #aaa;">${placeholder}</span>`;        }    });});

应该设置contenteditable="plaintext-only"

算了,我直接当面指挥他使用filed-sizing属性吧。

我观察AI的思考过程,发现其确实捞到了filed-sizing属性的语法,就在我以为AI可以完美完成任务的时候,结果生成的页面一看,尼玛,完全就是一本正经的胡说八道。

嗯……短期来看,学习还是有必要的,尤其是一些前沿技术。

更长期

其实人生在世,无需那么多算计、功利,纠结于投入产出比,担心价值流失之类的。

想那么多干嘛,持续学习,总不会错的,只要是能够提高自己的,都要学,前端技术本身如此,如何使用AI也是如此。

毕竟,人在职场,在社会,看起来是人与AI的竞争,本质上你活得如何,还是人与人的竞争。

我比你懂很多的前端知识,我又比你更懂AI,你说,我会担心被淘汰吗?

所以,2026年我还将继续学习,继续保持对前端的好奇心,欢迎关注,转发,一起进步。

告别素材焦虑!用 AI 一键生成鸿蒙项目图片素材

作者 万少
2026年1月12日 16:37

告别素材焦虑!用 AI 一键生成鸿蒙项目图片素材

万少:华为HDE、鸿蒙极客

个人主页:blog.zbztb.cn/

2025年参与孵化了20+鸿蒙应用、技术文章300+、鸿蒙知识库用户500+、鸿蒙免费课程2套。

如果你也喜欢交流AI和鸿蒙技术,欢迎扣我。

最近我在B站上进行不定期的免费鸿蒙技术直播,欢迎关注:space.bilibili.com/414874315?s…

程序员找素材,到底有多难?

做项目开发时,我们经常需要各种图片素材。但获取素材这件事,不同角色的体验天差地别:

企业开发:有专业 UI 设计师,直接找设计师要就完事了。

个人开发者:就只能自己想办法:

  • 手动去素材网站搜索、挑选、下载
  • 把素材引入工程
  • 在代码中使用

这一套流程走下来,没个十几二十分钟根本搞不定。更糟心的是,花半天找的素材还不一定满意。

我就一直在想:图片素材能不能像普通文本一样,直接让 AI 生成,然后插到工程里?

鸿蒙工程里怎么用图片?

在鸿蒙(HarmonyOS)开发中,使用图片主要分两步:

  1. 存放图片:把图片放到 resources 目录或 rawfile 目录
  2. 代码引用:在 .ets 文件中引入并使用

这个流程本身很简单,但问题卡在第一步——图片从哪来?

我的解决方案:AI 生成图片脚本

既然 AI 能写代码,那生成图片当然也不在话下。

我的做法是写一个脚本,通过 AI 图像生成接口来获取素材。成本很低,市面上主流的 AI 绘画服务(百度、阿里、火山等)生成一张图片大约 1~3 分钱

脚本的核心功能:

  • 接收一个参数:图片描述(文本字符串)
  • 调用 AI 图片生成接口
  • 返回图片文件流
  • 自动保存到指定位置

实现思路:

  1. 从各 AI 平台官网复制对应语言的 SDK 代码(比如 Python、Node.js 等)
  2. 把自己的 API Key 写入脚本
  3. 封装一个函数,传入图片描述,返回图片文件

实战:免费好用的图片生成服务

推荐一个我经常用的——智谱 AI 的图片生成服务:

🔗 bigmodel.cn/

免费额度对个人开发者来说完全够用,生成效果也很不错。

智谱 AI 平台

将生成图片的脚本直接放在鸿蒙工程内

image-20260112130734993

将脚本包装成可以使用终端调用的文件

image-20260112130828662

AI编辑器中直接对话生成

这里用上架应用-流蓝卡片 为例:

image-20260112131139741

然后执行程序,得到结果

image-20260112131456935


实际效果:

PixPin_2026-01-12_13-17-11

历史文章

  1. AI 玩转鸿蒙 (1):选择合适的AI开发工具

    mp.weixin.qq.com/s/HXbT60vzJ…

下期预告

用 AI 生成鸿蒙代码难免会有小语法错误。

下篇文章我来分享:如何让 AI 自动修复自己的代码错误,实现「生成即可用」的无缝体验。

AI 写的代码有 48% 在"胡说八道":那些你 npm install 的包,可能根本不存在

2026年1月12日 10:18

摘要:研究显示,AI 生成的代码中有 48% 存在"幻觉"——引用了根本不存在的包、API 或方法。更可怕的是,黑客已经开始利用这个漏洞:他们注册 AI 经常"幻觉"出来的假包名,等你 npm install,恶意代码就进了你的项目。这种攻击叫"Slopsquatting",已经影响了 44 万个包依赖。本文带你深入了解这个 AI 时代的新型安全危机。


01. 那个让我后背发凉的 Bug

上周,我在 Code Review 时发现了一个奇怪的 import:

import { validateEmail } from "email-validator-pro"

我没见过这个包,于是去 npm 上搜了一下。

搜索结果:0 个匹配。

我问写这段代码的同事:"这个包是哪来的?"

他说:"Cursor 自动补全的啊,我看着挺专业的就用了。"

我又问:"你 npm install 过吗?"

他愣了一下:"好像……没有?代码能跑啊。"

我看了一眼 package.json,果然没有这个依赖。代码之所以能跑,是因为另一个包里恰好有个同名的函数被导出了。

这次我们运气好。

但如果这个"不存在的包"真的被人注册了呢? 如果里面藏着恶意代码呢? 如果我们真的 npm install 了呢?

这不是假设。这正在发生。


02. AI 代码幻觉:48% 的代码在"胡说八道"

2.1 什么是 AI 代码幻觉?

AI 代码幻觉(AI Code Hallucination)是指 AI 生成的代码中包含:

  • 不存在的包import xxx from 'fake-package'
  • 不存在的 APIresponse.data.nonExistentMethod()
  • 不存在的方法array.filterMap() (JavaScript 没有这个方法)
  • 错误的参数fs.readFile(path, 'utf-8', callback, extraParam)
  • 虚构的配置项{ enableTurboMode: true } (没有这个选项)

2.2 有多严重?

2025 年的研究数据让人触目惊心:

AI 代码幻觉统计(2025年研究):

样本量:576,000 个代码样本
测试模型:16 个主流 LLM

关键发现:
├─ 48% 的 AI 生成代码包含某种形式的幻觉
├─ 440,000 个包依赖是"幻觉"出来的(不存在)
├─ 58% 的幻觉包名会重复出现(AI 会反复犯同样的错)
├─ 开源模型幻觉率:22%
├─ 商业模型幻觉率:5%(好一些,但仍然存在)
└─ 45% 的 AI 生成应用包含可利用的 OWASP 漏洞

将近一半的 AI 代码在"胡说八道"。

2.3 为什么 AI 会"幻觉"?

// AI 幻觉的产生机制
interface HallucinationCause {
  cause: string
  explanation: string
  example: string
}

const hallucinationCauses: HallucinationCause[] = [
  {
    cause: "训练数据过时",
    explanation:
      "AI 的训练数据可能是 1-2 年前的,很多新包它不知道,很多旧包已经改名或废弃",
    example: "推荐使用已经废弃的 request 库,而不是 axios",
  },
  {
    cause: "模式匹配过度泛化",
    explanation:
      "AI 看到 'email' + 'validator' 就觉得应该有个 'email-validator' 包",
    example: "生成 import { validate } from 'email-validator-pro' // 不存在",
  },
  {
    cause: "混淆不同语言/框架",
    explanation:
      "把 Python 的库名用在 JavaScript 里,或者把 React 的 API 用在 Vue 里",
    example: "在 Node.js 里 import pandas // 这是 Python 的库",
  },
  {
    cause: "自信地编造",
    explanation: "AI 不会说'我不知道',它会自信地给出一个看起来合理的答案",
    example: "生成一个完整的、看起来很专业的、但完全虚构的 API 调用",
  },
  {
    cause: "私有代码库盲区",
    explanation: "AI 没见过你公司的内部代码,但会根据命名规律'猜测'",
    example: "猜测你公司有个 @company/utils 包,但实际上叫 @company/common",
  },
]

03. Slopsquatting:黑客的"钓鱼"新玩法

3.1 什么是 Slopsquatting?

Slopsquatting = Slop(AI 生成的垃圾内容)+ Squatting(抢注)

简单来说:黑客注册 AI 经常"幻觉"出来的假包名,等你上钩。

Slopsquatting 攻击流程:

第一步:研究 AI 幻觉模式
├─ 用各种 LLM 生成大量代码
├─ 收集所有"幻觉"出来的包名
└─ 找出重复率最高的(58% 会重复)

第二步:抢注假包名
├─ 在 npm / PyPI 上注册这些包名
├─ 包内容看起来正常(躲避审查)
└─ 但藏有恶意代码

第三步:等待受害者
├─ 开发者用 AI 生成代码
├─ AI "幻觉"出这个包名
├─ 开发者 npm install
└─ 恶意代码进入项目

第四步:获利
├─ 窃取环境变量(API Key、密码)
├─ 植入后门
├─ 加密勒索
└─ 供应链攻击(感染下游项目)

3.2 真实案例

2025 年,安全研究人员发现了一个大规模的 Slopsquatting 攻击:

案例:huggingface-cli 事件

背景:
├─ Hugging Face 是最流行的 AI 模型平台
├─ 官方 CLI 工具叫 huggingface-hub
└─ 但 AI 经常"幻觉"出 huggingface-cli 这个名字

攻击:
├─ 黑客注册了 huggingface-cli 包
├─ 包内容:正常的 CLI 功能 + 隐藏的数据窃取代码
├─ 窃取内容:HF_TOKEN(Hugging Face API 密钥)
└─ 影响:数千个项目被感染

发现过程:
├─ 安全研究人员在分析 AI 幻觉模式时发现
├─ 该包已被下载数万次
└─ 大部分下载来自 AI 辅助开发的项目

3.3 规模有多大?

Slopsquatting 威胁规模(2025-2026):

已发现的恶意包:
├─ npm:3,000+ 个疑似 Slopsquatting 包
├─ PyPI:1,500+ 个疑似 Slopsquatting 包
└─ 其他包管理器:数量不详

潜在攻击面:
├─ 440,000 个 AI 幻觉包名可被利用
├─ 58% 的幻觉包名会重复出现(高价值目标)
└─ 每天有数百万次 AI 辅助的包安装

受影响的开发者:
├─ 97% 的开发者不会验证 AI 推荐的包是否存在
├─ 大部分人直接复制 AI 生成的 import 语句
└─ 很少有人检查 package.json 里的陌生依赖

04. 更可怕的:AI 生成的"合成漏洞"

除了幻觉包名,AI 还会生成一种全新的安全威胁:合成漏洞(Synthetic Vulnerabilities)

4.1 什么是合成漏洞?

合成漏洞是指:只存在于 AI 生成代码中的安全漏洞,人类程序员通常不会写出这种代码。

// 人类程序员写的代码(有漏洞,但是常见模式)
const userId = req.params.id
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`)
// SQL 注入漏洞,但 SAST 工具能检测到

// AI 生成的代码(合成漏洞,工具检测不到)
const userId = req.params.id
const sanitizedId = userId.replace(/[^0-9]/g, "") // 看起来做了过滤
const user = await db.query(`SELECT * FROM users WHERE id = ${sanitizedId}`)
// 问题:如果 userId 是 "1 OR 1=1",过滤后变成 "111"
// 不是注入了,但逻辑完全错误,可能返回错误的用户数据
// 传统 SAST 工具检测不到这种"逻辑漏洞"

4.2 合成漏洞的特点

// 合成漏洞 vs 传统漏洞
interface VulnerabilityComparison {
  aspect: string;
  traditional: string;
  synthetic: string;
}

const comparison: VulnerabilityComparison[] = [
  {
    aspect: "来源",
    traditional: "人类程序员的常见错误",
    synthetic: "AI 的独特错误模式"
  },
  {
    aspect: "可检测性",
    traditional: "SAST/DAST 工具能检测大部分",
    synthetic: "传统工具检测不到"
  },
  {
    aspect: "模式",
    traditional: "已知的漏洞模式(OWASP Top 10)",
    synthetic: "全新的、未分类的漏洞模式"
  },
  {
    aspect: "修复难度",
    traditional: "有成熟的修复方案",
    synthetic: "需要理解 AI 的"思维方式"才能修复"
  },
  {
    aspect: "复现性",
    traditional: "相同输入产生相同漏洞",
    synthetic: "AI 可能每次生成不同的漏洞代码"
  }
];

4.3 研究数据

合成漏洞研究(2025年,50万+代码样本):

发现:
├─ AI 生成的代码比人类代码有更多高危漏洞
├─ AI 会复制训练数据中的不安全编码模式
├─ AI 会"幻觉"出不存在的抽象层和框架
└─ 这些"幻觉框架"创造了全新的攻击面

具体数据:
├─ 45% 的 AI 生成应用包含 OWASP 漏洞
├─ AI 代码的高危漏洞密度是人类代码的 1.5 倍
├─ 30% 的合成漏洞无法被传统 SAST 工具检测
└─ 修复 AI 代码漏洞的时间比修复人类代码多 40%

05. 如何保护自己?

5.1 代码审查清单

// AI 代码审查清单
const aiCodeReviewChecklist = {

  // 1. 依赖检查
  dependencies: [
    "每个 import 的包是否真实存在?",
    "包名拼写是否正确?(typosquatting 风险)",
    "包是否来自官方源?",
    "包的下载量和维护状态如何?",
    "包的最近更新时间?(太新可能是恶意包)"
  ],

  // 2. API 检查
  apis: [
    "调用的 API 是否真实存在?",
    "参数数量和类型是否正确?",
    "返回值类型是否符合预期?",
    "是否使用了已废弃的 API?"
  ],

  // 3. 安全检查
  security: [
    "是否有 SQL 注入风险?",
    "是否有 XSS 风险?",
    "敏感数据是否正确处理?",
    "权限检查是否完整?",
    "是否有硬编码的密钥或密码?"
  ],

  // 4. 逻辑检查
  logic: [
    "边界情况是否处理?",
    "错误处理是否完善?",
    "代码逻辑是否符合需求?",
    "是否有"看起来对但实际错"的代码?"
  ]
};

5.2 工具推荐

防护 AI 代码幻觉的工具:

依赖检查:
├─ npm audit / yarn audit(基础检查)
├─ Snyk(更全面的漏洞扫描)
├─ Socket.dev(专门检测供应链攻击)
└─ deps.dev(Google 的依赖分析工具)

代码扫描:
├─ SonarQube(传统 SAST)
├─ Semgrep(可自定义规则)
├─ CodeQL(GitHub 的代码分析)
└─ AI 专用扫描器(2026年新出的工具)

实时防护:
├─ IDE 插件:在 import 时检查包是否存在
├─ Git Hooks:提交前自动检查依赖
├─ CI/CD 集成:构建时扫描
└─ 运行时监控:检测异常行为

5.3 最佳实践

AI 辅助开发安全最佳实践:

1. 永远不要盲目信任 AI 生成的代码
   ├─ 每个 import 都要验证
   ├─ 每个 API 调用都要查文档
   └─ 每段逻辑都要理解

2. 使用锁文件
   ├─ package-lock.json / yarn.lock
   ├─ 锁定依赖版本
   └─ 防止依赖被篡改

3. 定期审计依赖
   ├─ 每周运行 npm audit
   ├─ 检查新增的依赖
   └─ 移除不需要的依赖

4. 使用私有镜像
   ├─ 公司内部 npm 镜像
   ├─ 只允许白名单包
   └─ 阻止未知包安装

5. 代码审查流程
   ├─ AI 生成的代码必须人工审查
   ├─ 重点检查依赖和安全相关代码
   └─ 使用自动化工具辅助

06. 给不同角色的建议

6.1 如果你是个人开发者

个人开发者防护指南:

立即做:
├─ 安装 Socket.dev 或类似的 IDE 插件
├─ 每次 npm install 前检查包是否存在
├─ 养成查文档的习惯(不要只信 AI)
└─ 定期运行 npm audit

习惯养成:
├─ AI 生成代码后,先读一遍再用
├─ 看到陌生的包名,先去 npm 搜一下
├─ 不确定的 API,查官方文档确认
└─ 保持怀疑态度

6.2 如果你是团队 Leader

团队安全策略:

流程层面:
├─ 建立 AI 代码审查规范
├─ 要求所有 AI 生成代码必须标注
├─ 重点审查依赖变更的 PR
└─ 定期安全培训

工具层面:
├─ CI/CD 集成依赖扫描
├─ 使用私有 npm 镜像
├─ 配置依赖白名单
└─ 自动化安全检查

文化层面:
├─ 鼓励质疑 AI 生成的代码
├─ 奖励发现安全问题的人
├─ 分享 AI 代码踩坑经验
└─ 建立安全意识

6.3 如果你是安全工程师

安全工程师行动指南:

短期:
├─ 研究 AI 代码幻觉模式
├─ 建立 AI 代码专用扫描规则
├─ 监控公司代码库中的可疑依赖
└─ 培训开发团队

中期:
├─ 开发 AI 代码专用安全工具
├─ 建立 AI 代码安全基线
├─ 与 AI 工具厂商合作改进
└─ 参与行业安全标准制定

长期:
├─ 研究合成漏洞的检测方法
├─ 建立 AI 代码安全知识库
├─ 推动 AI 编程工具的安全改进
└─ 培养 AI 安全专业人才

07. 写在最后

AI 编程工具是把双刃剑。

它可以让你的效率提升 10 倍,也可以让你的项目在不知不觉中被植入恶意代码。

48% 的 AI 代码在"胡说八道"。

这不是危言耸听,这是研究数据。

440,000 个幻觉包名等着被利用。

这不是未来威胁,这是正在发生的攻击。

作为程序员,我们需要:

  1. 保持警惕:AI 生成的代码不是"免检产品"
  2. 验证一切:每个包、每个 API、每段逻辑
  3. 使用工具:让自动化工具帮你把关
  4. 持续学习:了解最新的安全威胁和防护方法

最后,送给所有程序员一句话:

"AI 可以帮你写代码,但只有你能为代码的安全负责。"

"那个你随手 npm install 的包,可能正在窃取你的 API Key。"

在 AI 时代,安全意识比任何时候都重要。

保持警惕,保护好自己。


💬 互动时间:你遇到过 AI 代码幻觉吗?你的团队有什么防护措施?评论区聊聊!

觉得有用的话,点赞 + 在看 + 转发,让更多程序员朋友看到~


本文作者是一个差点被 AI 幻觉坑了的程序员。关注我,一起在 AI 时代保持安全意识。

普通前端仔的 2025 : 年终总结与 AI 对我的影响

2026年1月12日 09:30

前言

2025 年已经过去,时间滚滚向前。恍惚间,仿佛上一次过年还在不久之前,转眼一年却已走到尾声。借着掘金这次 # 🏆2025 AI / Vibe Coding 对我的影响|年终征文 活动的机会,我想和大家分享一下自己的年终总结,并聊一聊 2025 年 AI 在工作与学习中对我的实际帮助。

开始坚持写文章分享

在今年年初,我和老婆完成了订婚,年中正式领取了结婚证💗。我的肩上多了一份对家庭的责任,也开始一起规划未来的生活,坚定了一个目标:一定要多赚一些钱,才能更有底气地生活。

后来我想到,之前曾看到有人提到在技术社区持续写文章,有机会接到外包或私活。于是,我决定在自己最常逛的技术社区——掘金,开始发布一些原创技术文章。

最早是在 2024 年 12 月底,因为工作阶段性需求不大,有了一些空闲时间,我便开始动笔。但现实很快给了我反馈:文章写完后几乎没人看。其实这也很正常,就像刚开始做自媒体需要“起号”一样,一个新账号发布的第一篇文章,基本不会有太多曝光。

后来,好朋友韬哥提醒我,文章审核通过后可以让朋友帮忙点点赞,新文章有机会进入周榜,从而获得更多曝光。这里也要感谢我老婆以及几位朋友,对我写作的支持与鼓励和建议。万分感谢🙏

接下来就是不断地写。直到有一篇 # 前端开发又幸福了,Cursor + Figma MCP 快速还原设计稿 意外火了,不仅阅读量明显上涨,还被掘金公众号转发。事实上,这篇文章反而是我写得最随意、耗时最短的一篇,可能正好踩中了 MCP 的热点。当时 MCP 刚出现不久,那段时间我确实非常开心。

或许是因为好奇心比较强——说得直白一点,其实也是想“偷懒”——我一直很愿意尝试新事物😂,所以第一时间体验了 MCP,确实让人眼前一亮。随后我便迫不及待地想把这些体验分享出来,担心同事在实际使用中踩坑,便写下了这篇文章,想着审核通过后可以直接转发给同事参考实践。后面关于 AI 的相关内容,我也会继续深入,具体聊一聊 AI 在工作方式和工作内容上带来的改变。

我在写文章的过程中,也会适当借助一些 AI 辅助。毕竟我的文笔并不算好,容易偏口语化,自己写完再读一遍时,常常都有些读不下去,因此我通常会让 AI 帮我优化一下文案表达。在这里也确实要感谢“AI 老师”,在写作效率和可读性上给了我很大的帮助。

但与此同时,我也非常排斥“AI 味”过重的文章。掘金上有些上周榜的内容,我几乎一眼就能看出是 AI 生成的。或许现在还能分辨,再过两年就未必了。我记得有一次刷到一篇讲“2025 年 JavaScript 新语法糖”的文章,通篇都是 AI 胡编乱造的内容,作者既没有自行验证,也没有标注 AI 生成,就这样直接发布出来。这种行为在我看来完全是在误导新人,想到这里就会感到非常生气。

我始终认为,每个人都应该对自己分享的知识负责。因此,我写的文章尽量都是真人思考、真人实践得出的内容,只是借助 AI 做一些文字层面的润色,而不是让它替我“创作观点”。

随着 AI 能力不断增强,一些常见、零散的编程问题其实已经不太值得单独分享了,比如 JavaScript 时间处理中的各种坑,AI 的回答往往已经足够准确和全面。相比之下,更有价值的内容,反而是系统化、体系化的实践流程与思考总结,这也是我之后更希望持续输出的方向。

跳槽

另一方面,也是想多赚一些钱。成家之前,我的工资养活自己绰绰有余,但成家之后,现实问题就变得具体起来:未来如果有孩子、还没有买房,这些都需要更强的经济支撑。我也很清楚,在中国大部分程序员的职业生命周期大概率只有十几年,到了年龄偏大时,可能就需要考虑转型。2025 年,是我毕业、正式进入社会工作的第三年,因此我做出了一个决定——准备跳槽。

马云曾说过一句话:

跳槽无外乎两个原因,钱给少了,心里受委屈了。

这两点,我可能都占了。在这家公司干了两年,年初时,领导、CTO,以及当初面试我的帆叔,或许是出于生活和前途的考虑,陆续选择了离开。核心人物走后,公司换了新的领导,但我明显感觉到一种“死海效应”。感觉开了很多没有必要的会议,真的像过家家一样,我也感觉到没有效率无头苍蝇一样东一榔头西一棒的做事情。

所谓“死海效应”,是指组织中优秀员工不断流失,如同死海水分蒸发,导致低质量员工比例上升,从而影响整体效率和企业发展。

其实在我第一次提出离职时,公司也给我调了薪。当时我一度以为,自己可能会在这里长期干下去。但后来发生了一些不太方便细说的矛盾,如今回头看,我依然认为自己并没有做错。最终,出于职业发展与前途的考虑我还是选择了离开。

我悄悄提交了离职申请,只提前和一直合作的产品同学说了一声。说实话,我们组的产品在我看来是非常有能力的人才。直到我离职的最后一天,很多同事看到我的签名留言(相遇是缘,祝大家越来越好),才意识到我要走了。那天有十几位同事和我道别,让我非常感动。直到现在,我也还会和前同事们时不时在微信上聊聊天,聊前端,聊 AI。我跟每个同事都很熟悉,可能是我的性格善于把大家链接起来。

提完离职之后,我便立刻开始找工作。我并没有打算 gap 一段时间,因为之前已经 gap 过一次。那次裸辞后玩了两个月,前期确实很爽,像是在过寒暑假;但等旅游结束回到出租屋后,每天不是躺着就是刷手机、玩电脑,生活逐渐失去了目标感。那时我才真正意识到,人是需要劳动的,需要在社会工作中获得价值感。

正因如此,那次我很快重新投入找工作,也正是在那段时间,柯总收留了当时只有一年工作经验的我🙏。

正如马克思所说:

劳动是人类生存的基石,是人自身发展的决定性要素。在共产主义社会高级阶段,“劳动已经不仅仅是谋生的手段,而是本身成了生活的第一需要”。

在跳槽过程中,我也观察到了招聘市场风向的变化:越来越多的公司更倾向于简历中带有 AI 项目经历的候选人。幸运的是,我在 2023 年第一份工作时就参与过一个 AI 相关的生图项目,这让我的简历在市场上颇受欢迎。不过,当时市场对 AI 的重视还有滞后性,真正对 AI 项目经历感兴趣的公司并不多。到了这次跳槽,情况明显不同——AI 相关经历几乎成为必问项,也显著提升了候选人的吸引力。这让我深刻体会到,AI 对程序员带来的不是威胁,而是新的机会。

在面试过程中,我也会主动考察部门的 AI 使用情况。令我震惊的是,很多小公司的团队虽然知道 AI 的存在,但根本没有实际应用,仍然依赖传统的手工编码。显然,我不会选择加入这样的团队,因为对于我而言,高效利用 AI 不只是工具加成,而是能显著提升团队整体效率和技术成长空间的重要指标。

有了上一次裸辞的经历后,这一次在“多赚钱”的前提下,我几乎没有给自己任何休息时间,离职后便立刻投入到找工作中。或许缘分就是这么巧,我很快找到了一份听起来前途还不错的工作。但由于当时没有把工作时长和薪资细节问清楚,也没有在谈薪阶段据理力争到自己真正满意的程度,入职后还是产生了一些后悔的情绪。不过再找一份工作的成本不低,加上自己也有些懒,索性就先在这家公司干了下来。

这是一家总部在北京的做游戏的大公司,在广州新成立的一个部门,部门在 5 月份成立,而我是 8 月份加入的。由于我之前的技术栈和项目经验主要集中在管理后台领域,入职后便从0到1参与并负责了一个内部 BI 后台项目的建设。新公司的领导能力很强,一人同时承担后端开发、产品规划以及与设计师沟通协调等多重角色。

团队规模不大,我们是一个前端、一个后端,再加上一位测试同学,三个人协作完成了这个中台系统的开发,用于支持游戏发行部门的日常业务。

AI

也该聊到 AI 了,不然有点偏题太久了。😂

2022年的 AI

第一次接触 AI 辅助编程,是在 2022 年通过 GitHub Copilot。当时我在写毕业设计,用到的是一个需要发邮件申请试用的 VS Code 插件。印象很深的是,只要写一个诸如“二分查找”的注释,下面很快就能自动补全出完整代码,确实让人觉得相当聪明。

后来在 2022 年 12 月左右,ChatGPT 横空出世。现在回头看,那真的是一件非常幸运的事情——我刚参加工作没多久,大语言模型就已经出现了。那段时间最大的感受是:GPT 写出来的代码,甚至比当时作为初级程序员的我还要规范、完整。

于是后来每次遇到代码重构或优化相关的问题,我都会先问一问它。在不断的使用过程中,也确实从“AI 老师”那里学到了不少编程思路和实践技巧。

2023,2024年的 AI

那时候 ChatGPT 还没有免费开放,基本都是通过国内的镜像站之类的方式在使用,稳定性和体验都谈不上好,但依然挡不住大家的热情。我还记得 Cursor 刚出来的时候,最大的优势就是不需要科学上网就能直接用 GPT,这一点在当时非常有吸引力。谁能想到,后来这个工具不断迭代升级,从一个“能用”的编辑器插件,逐渐成长为 AI IDE 的第一梯队,甚至在某些场景下彻底改变了写代码的方式。

在那个阶段,我的使用方式其实还比较“传统”:写完一段代码,复制出来,粘贴到 GPT 里提问,让它帮我看看有没有优化空间、潜在问题,或者让它补全缺失逻辑,然后再把结果复制回编辑器中。这个流程现在看起来有些笨重,但在当时已经极大提升了效率。很多原本需要翻文档、查 Stack Overflow 的问题,几分钟内就能得到一个相对完整的答案。

那时的 AI 更多还是“辅助工具”的角色,而不是直接参与到编码流程中。它更像是一位随叫随到、耐心十足的高级同事,帮你查资料、给思路、补细节。虽然偶尔也会胡编乱造,需要自己具备判断能力,但不可否认的是,从 2023 年开始,我已经明显感受到:写代码这件事,正在被 AI 悄然重塑。

2025 年的 AI

一直到 2024 年底,Cursor 突然火了起来。我记得好像是某公司的一个大佬的女儿在几乎没有编程经验的情况下,用 Cursor 写了一个小程序,这篇推特被广泛转发后,Cursor 迅速走红。我看到后也下载了最新版,试用后直接被震撼到了——它的补全功能丝滑得让人难以置信,好像能直接理解我脑子里的想法,这是我第一次体验到如此智能又顺手的 AI 编程提示。

当时,我也尝试了 Cursor 的一个竞品 Winsurf,但整体体验还是 Cursor 更佳。有人会说,这不过是把 AI 模型套个壳而已,但我认为“套壳”也有高低之分。作为普通程序员,我们不必去研究模型的理论,但在应用层的交互体验、细节设计做得出色,同样非常了不起。使用 Cursor 后,我明显感受到工作效率提升,甚至可以达到两倍、五倍甚至十倍。

我当时非常积极地向同事推荐,但发现部分同事带有悲观色彩,担心 AI 会替代程序员,因此不愿尝试。对此,我的观点是:AI 是提效工具,它能帮你节省重复劳动,让你有更多时间去学习新技术、思考产品设计和架构优化。AI 的核心意义在于,让程序员从繁琐的 CRUD 工作中解放出来,把时间用在更高价值的工作上,让创意和想象力真正发挥作用。

与此同时,字节跳动推出了 Trae,我也体验过并写过相关征文,但整体体验还是不如 Cursor 顺手。也许是 Trae 的宣传和营销做得比较好,所以在我跳槽面试时,不少团队表示虽然自己没有使用 AI 编程,但知道字节出了 Trae。

后面过春节的时候,国产开源模型之光 DeepSeek 横空出世,连家里的长辈都知道中国出来个 nb 的 AI。太伟大了 DeepSeek 直接选择了开源,给全世界分享他们的成果,respect🫡!!!

在高强度使用了月左右后,我积累了一些经验和方法,也在文章中分享给了大家。

随着 AI 工具的发展,我也开始尝试其他工具,例如 Winsurf 和 Argument Code。特别是 Argument Code,这是一个 VS Code 插件,能够智能寻找代码中相关的影响范围,非常适合进行复杂逻辑分析。背后的 AI 模型 Claude 在这里表现得很聪明,但订阅价格不低,当时约 100 美元/月。

后来我也尝试了 Claude Code 和 Codex 的 CLI,不得不说,Claude 模型确实很强(题外话:但最近对第三方的封禁以及反华的一些魔幻操作,真希望预告新年发布的DeepSeek v4能挫挫这家公司锐气!),尤其在编码和设计相关的理解上非常到位。开源的 Claude-agent-sdk 也很优秀,很多人在它的基础上可以做自己的 CLI 二次开发。不过,我个人还是不太习惯在终端里使用 AI,习惯了有 GUI 界面的 IDE,操作起来更直观、顺手。

谷歌的 Antigravity我也体验了,都是在侧边栏有个对话框,可以试用 Gemini 与 Claude,我经常用 Gemini 写页面,但是写逻辑他很喜欢 any 跳过,很无语😅,写逻辑还是需要用 Claude。每周会限制一些使用额度,不得不说谷歌还是家大业大,想要超车提速就是这么快。但是这个产品名称起的真的不好拼写哈哈。

目前我在试用 Kiro 的 Claude 服务,用的是白嫖的 30 天、500 积分版本。不过这个 IDE 似乎没有智能提示功能(可能是我使用姿势不对?但我理解应该是默认开启的)。

总的来说,虽然 CLI 强大,但对我而言,GUI 界面的交互体验更符合日常编码习惯。我估计下一步还是回到 cursor 了。

对 AI 的思考与想法

写了这么多,我也有些累了。这是我第一次写这么长的文章,可能是因为想表达的内容实在太多了。码了一上午,最后想和大家聊聊我个人对 AI 的理解与思考。

AI 给我的工作效率带来了成倍提升。面试时我也常提到,以前写代码都是一行行敲,现在几乎可以“一片一片”地生成代码。但这并不意味着可以无脑相信 AI 输出的结果。如果每天只是依赖 AI 完成 Vibe Coding,长期下来可能会非常痛苦——-因为你不了解 AI 的实现细节。选用性能差的模型,即便功能实现了,后续改造或迭代可能会非常困难,只能再次依赖 AI 来处理。久而久之,就可能形成“AI 生成的代码屎山”。

因此,我的做法是:每次命令 AI 完成任务后,都会仔细 review 它的代码,再进行提交。如果项目是一次性的小型任务,或许可以不用过于严格,但对于需要长期维护的系统,认真 review 并与 AI 协作至关重要。

同时,AI 目前还无法替代程序员,其根本原因在于缺乏责任感。AI 的上下文长度有限,它无法像人一样,在公司里长期维护多个项目四五年。上下文越长,它遗忘的内容也越多。新建一个窗口,之前的事情就忘记了(可以设置全局 rule) 此外,一些自媒体常吹嘘用 AI 完全不会编程也能完成系统开发,虽然 AI 越来越强,一次性任务看起来很漂亮,但遇到小细节或后续改动时,如果没有懂一点的人去指挥和优化,代码很容易崩溃。

所以,至少需要一个懂技术的人来指导 AI,确保输出可靠。实际上,AI 也可以成为学习的辅助工具:通过它快速学习新的编程语言语法、软件架构最佳实践,再用这些知识高效指挥 AI 完成任务。总结来看,AI 是效率的倍增器,但仍然需要人的经验与判断力来控制风险、保证质量。

我觉得大家应该积极拥抱 AI,面对它、理解它,并善加利用,让 AI 成为让自己如虎添翼的工具。AI 的发展必然会带来产业变革和技术革新,但从更宏观的角度看,它是推动人类文明进步的重要力量。我们正加速步入一个生产力大爆发的时代,AI 将程序员从以往繁琐的搬砖任务中解放出来,让我们有更多精力去思考架构设计、创新功能,以及探索新的技术边界。

更进一步,AI 的真正价值在于它能够让人类在创造力和效率之间找到平衡。以前很多重复性工作占据了大量时间,现在这些工作可以交给 AI 来处理,而程序员可以把精力放在更高层次的思考上:如何设计更优的系统、如何优化用户体验、如何在复杂业务中做出更合理的决策。AI 不仅是工具,也是学习的助力,它能够快速提供信息、分析方案,让我们在短时间内掌握新技术、新方法,从而实现知识和能力的快速积累。

可以说,AI 对程序员而言,是一种能力的放大器,而不是替代品。未来,能够合理运用 AI 的人,将比单纯依赖传统技能的人更具竞争力。在这个过程中,保持学习、理解和掌控 AI 的能力,比单纯追求 AI 生成的结果更重要。真正掌握了这项能力的人,将能够在技术创新和生产力提升的浪潮中站稳脚跟,甚至引领变革。

结语

过去的一年是成长的一年,我也能明显感受到,相比去年的自己,有了不少进步。

希望在新的一年里,AI 能够展现出更惊艳的能力,带来更多创新和可能。期待未来,也祝大家新年快乐,工作顺利,生活愉快,每个人都能不断成长、越来越好。

Skill 真香!5 分钟帮女友制作一款塔罗牌 APP

作者 乘风gg
2026年1月12日 09:14

最近发现一个 AI 提效神器 ——Skills,用它配合 Cursor 开发,我仅用 5 分钟就帮女友做出了一款塔罗牌 H5 APP!在说如何操作之前,我们先大概了解下 Skills 的原理

一、Skills的核心内涵与技术构成

(一)本质界定

Skills 可以理解为给 AI Agent 定制的「专业技能包」,把特定领域的 SOP、操作逻辑封装成可复用的模块,让 AI 能精准掌握某类专业能力,核心目标是实现领域知识与操作流程的标准化传递,使AI Agent按需获取特定场景专业能力。其本质是包含元数据、指令集、辅助资源的结构化知识单元,通过规范化封装将分散专业经验转化为AI Agent可理解执行的“行业SOP能力包”,让 AI 从‘只会调用工具’变成‘懂专业逻辑的执行者

(二)技术构成要素

完整Skill体系由三大核心模块构成,形成闭环能力传递机制:

  1. 元数据模块:以SKILL.md或meta.json为载体,涵盖技能名称、适用场景等关键信息约 100 个字符(Token),核心功能是实现技能快速识别与匹配,为AI Agent任务初始化阶段的加载决策提供依据。
  2. 指令集模块:以instructions.md为核心载体,包含操作标准流程(SOP)、决策逻辑等专业规范,是领域知识的结构化转化成果,明确AI Agent执行任务的步骤与判断依据。
  3. 辅助资源模块:可选扩展组件,涵盖脚本代码、案例库等资源,为AI Agent提供直接技术支撑,实现知识与工具融合,提升执行效率与结果一致性。

和传统的函数调用、API 集成相比,Skills 的核心优势是:不只是 “告诉 AI 能做什么”,更是 “教会 AI 怎么做”,让 AI 理解专业逻辑而非机械执行

二、Skills与传统Prompt Engineering的技术差异

从技术范式看,Skills与传统Prompt Engineering存在本质区别,核心差异体现在知识传递的效率、灵活性与可扩展性上:

  1. 知识封装:传统为“一次性灌输”,冗余且复用性差;Skills为“模块化封装”,一次创建可跨场景复用,降低冗余成本。
  2. 上下文效率:传统一次性加载所有规则,占用大量令牌且易信息过载;Skills按需加载,提升效率并支持多技能集成。
  3. 任务处理:传统面对复杂任务易逻辑断裂,无法整合外部资源;Skills支持多技能组合调用,实现复杂任务全流程转化。
  4. 知识迭代:传统更新需逐一修改提示词,维护成本高;Skills为独立模块设计,更新成本低且关联任务可同步受益。

上述差异决定Skills更适配复杂专业场景,可破解传统Prompt Engineering规模化、标准化应用的瓶颈。

三、渐进式披露:Skills的核心技术创新

(一)技术原理与实现机制

Skills能在不增加上下文负担的前提下支撑多复杂技能掌握,核心在于“按需加载”的渐进式披露(Progressive Disclosure)设计,将技能加载分为三阶段,实现知识传递与上下文消耗的动态平衡:

  1. 发现阶段(启动初始化):仅加载所有Skills元数据(约100个令牌/个),构建“技能清单”明确能力边界,最小化初始化上下文负担。
  2. 激活阶段(任务匹配时):匹配任务后加载对应技能指令集,获取操作规范,实现精准加载并避免无关知识干扰。
  3. 执行阶段(过程按需加载):动态加载辅助资源,进一步优化上下文利用效率。

(二)技术优势与价值

渐进式披露机制使Skills具备三大核心优势:

  1. 降低令牌消耗:分阶段加载避免资源浪费,支持单次对话集成数十个技能,降低运行成本。
  2. 提升执行准确性:聚焦相关知识组件,减少干扰,强化核心逻辑执行精度。
  3. 增强扩展性:模块化设计支持灵活集成新知识,无需重构系统,适配领域知识快速迭代。

四、Cursor Skills

介绍完 Skills 是什么之后,我将使用的是 Cursor 作为我的开发工具。先说明一下,最开始只有 Claude Code 支持 Skills、Codex 紧随其后,口味自己选。

好消息是,Cursor 的 Skills 机制采用了与 Claude Code 几乎完全一致的 SKILL.md 格式。这意味着,你完全不需要从头编写,可以直接将 Claude Code 的生态资源迁移到 Cursor。

(一)Cursor 设置

因为 Cursor 刚支持不久,并且是 Beta 才能使用,所以要进行下面操作

Agent Skills 仅在 Nightly 更新渠道中可用。
要切换更新渠道,打开 Cursor 设置( Cmd+Shift+J ),选择 Beta,然后将更新渠道设置为 Nightly。更新完成后,你可能需要重新启动 Cursor。 如下图所示

要启用或禁用 Agent Skills:

  1. 打开 Cursor Settings → Rules
  2. 找到 Import Settings 部分
  3. 切换 Agent Skills 开关将其开启或关闭 如下图所示

(二)复制 Claude Skills

然后我们直接去 Anthropic 官方维护的开源仓库 anthropics/skills,里面提供了大量经过验证的 Skill 范例,涵盖了创意设计、开发技术、文档处理等多个领域。

你可以访问 github.com/anthropics/… 查看完整列表。以下是这次用到的 Skills

Frontend Design:这是一个专门用于提升前端设计质量的技能。它包含了一套完整的 UI 设计原则(排版、色彩、布局)

然后我们直接把 Skills 里面的 .claude/skills/frontend-design 到当前项目文件下,如图:

模型和模式如下图

提示词如下,不一定非得用我的。

使用 Skill front-design。我要做一个 H5 ,功能是一个塔罗牌。

你是一名经验丰富的产品设计专家和资深前端专家,擅长UI构图与前端页面还原。现在请你帮我完成这个塔罗牌应用的 UI/UX 原型图设计。请输出一个包含所有设计页面的完整HTML文件,用于展示完整UI界面。

注意:生成代码的时候请一步一步执行,避免单步任务过大,时间执行过长

然后 Cursor 会自动学习 Skills,并输出代码

然后就漫长的等待之后,Cursor 会自动做一个需求技术文档,然后会一步一步的实现出来,这时候可以去喝杯茶,再去上个厕所!

最终输出了 5 个页面

  1. 首页 (Home)
  2. 每日抽牌页 (Daily Draw)
  3. 牌阵占卜页 (Spread Reading)
  4. 塔罗百科页 (Encyclopedia)
  5. 占卜历史页 (History)

最终效果如下,整体效果看起来,完全是一个成熟的前端工程师的水准,甚至还带有过渡动画和背景效。因为掘金无法上传视频,欢迎私信我找我要或者关注我:

image.png

扩展阅读

因为 Cursor 目前仅在 Nightly 版本上才可以使用 Skills。如果担心切换此模式会引发意想不到的情况,可以使用另一种方案

OpenSkills 是一个开源的通用技能加载器。

  • 完全兼容:它原生支持 Anthropic 官方 Skill 格式,可以直接使用 Claude 官方市场或社区开发的技能。
  • 桥梁作用:它通过简单的命令行操作,将这些技能转换为 Cursor、Windsurf 等工具可识别的配置(AGENTS.md),从而让 Cursor 具备与 Claude Code 同等的“思考”与“技能调用”能力。

Tailwind 因为 AI 的裁员“闹剧”结束,而 AI 对开源项目的影响才刚刚开始

2026年1月12日 06:53

Tailwind 还是相当明白「会哭的孩子有奶吃」这个道理,“裁员风波”才刚开始,立马就收到谷歌 AI Studio 、Vercel 和 Lovable 的相关赞助:

这个风波其实并不是最近才开始的,早在去年年底,Bun 被 Anthropic 收购加入 Claude Code 阵营的之后,Tailwind CSS 的创始人 Adam Wathan 就发出过灵魂警告:

因为现在很多 AI 公司,比如 OpenAI 、Claude、Gemini 等,都在前端 AI 上都大量使用了 Tailwind,因为很大程度上, Tailwind CSS 训练数据多、表达方式离散可拼装、可控性强、出错成本低 ,例如:

<div class="p-4 rounded-xl shadow-md bg-white">

对 AI 来说,Tailwind 的 class 写法非常像在拼积木,每个 token(p-4 / rounded-xl / shadow-md)都是一个“语义单元”:

  • 既能局部修改(把 p-4p-6
  • 又能组合叠加
  • 还能按响应式/状态扩展(md:p-6 hover:bg-xxx)

在这方面,模型向来更擅长生成离散符号序列(token),而不擅长维护抽象结构,同时 class 贴在元素上所见即所得,符合 AI 追求的尽可能“生成代码一次就能跑”

特别是谷歌的 AI Studio 在这方面倾向特别明显。

那这对 Tailwind 有什么影响?这不是代表着框架在 AI 时代很流行吗?为什么还会出现”裁员“问题?这个影响其实就类似 AI 对 Stackoverflow 的影响,原网站没流量了:

Tailwind 这次的本质矛盾在于,AI 让 Tailwind 使用量更大,但把它原本的赚钱路径(流量 to 转化)给切断了,所以反而出现“越火越穷”的情况。

Tailwind 本体是开源免费的,但是它的典型商业模式是:

Google/搜索 → Tailwind 官网文档/教程 → 认同与依赖 → 购买增值产品(模版、文档、企业合作、教程和顾问咨询等)。

这其实也是很多开源项目的盈利方式,特别国内很多 gitee 的项目更明显,放出简陋开源版本,付费提供文档和完整商业版本,而这些付费产品严重依赖:文档流量 + 心智依赖 ,还有用户在官网停留时间和访问频率,但是现在 AI 在掐断了这个路径

Tailwind 在线文档的流量下降了 40%,收入下降了 80%,实际上写技术文章和公众号的应该也有感受,现在的开发者越来越不喜欢读和找技术文章了,就算读也是让 AI 直接总结

当然,这波闹出来的裁掉 75% 的工程师的事件,多少也有一些标题党的味道,因为工程团队原来有4 个工程师,这次裁掉了 3 个,所以比例高达 75%

实际应该就是剩下创始人+ 1 个工程师+ 1 个兼职的团队规模。

当然,这波赞助风波其实对于 Tailwind 危机来说,也只是解救近期的燃眉之急,因为它不像 Bun ,Bun 对 Anthropic 来说是强战略资产,因为运行时/工具链直接影响 AI 编码产品的性能与交付:

一体化 runtime/toolchain,和 AI coding 产品的工程链路强绑定,收购能立刻减少外部依赖、提升稳定性与性能上限。

所以 Bun 卖的是“工程基础设施能力”(速度/工具链/交付体验),而 Tailwind 虽然十分流行,但是主要商业化通常靠“围绕开源的增值产品/服务漏斗”,成不了核心体系的,简单说:

  • Bun 属于“可控的关键基础设施(runtime/toolchain)”,收购后可以把关键工具链进化成自有资产
  • Tailwind 属于“可替代的开发者体验层(UI styling layer)”,买它不太会给你护城河

在链路上的差距,也导致了它们之间命运的走向不同,当然 Tailwind 深知“发声”的重要性,所以这波闹腾之后,也暂时性解决了生存问题,只是:

赞助只能覆盖一时的资金问题,但解决不了当前开源项目的的商业模式窘境。

AI 切断流量是致命的,StackOverflow 在这方面表现最为明显也最为明显,所以 Tailwind 这波于开发者和开源领域来也是很明显的警钟:

就像我朋友,上午还问有没有什么记账软件推荐,结果下午就直接用 AI 做了一个符合心意的应用,AI 对于个人开发者的影响未来也会越来越明显,如果 AI 可以直接 A2UI 直出功能和结果的时候,是否其他独立产品还有存在的意义?

image-20260111143806306

所以, AI 对于开发者和开源项目的影响才刚刚开始,以前的项目增长和流水靠的是:

  • Google 搜索
  • 文档流量
  • StackOverflow
  • 博客/教程
  • GitHub star 传播

但是现在 AI 时代之后,开源的影响力不再去取决于:

  • 文档写得多好
  • SEO 做得多好

现在的项目是否流畅,越来越由取决于 AI :

  • 训练语料里出现得够不够多
  • 模型偏好它还是偏好别的库
  • 它是否“更适合生成”

而项目能否赚到钱,更要取决于你在 AI 链路里扮演的角色,这也是 Tailwind 这波表现出来的趋势:

虽然你在 AI 时代很多,但是越火,流量却越少。

从零打造AI智能博客:一个项目带你入门全栈与大模型应用开发

作者 SpringLament
2026年1月10日 19:58

写在前面

最近 AI Coding 实在太火了,Cursor、Claude Code 这些工具让写代码变得越来越轻松。你可能也注意到了,这些工具都有一个共同点:在你写代码的时候,它们会实时给你补全建议,按 Tab 就能接受。这种体验太爽了,以至于我想在自己的博客编辑器里也搞一个类似的功能。

与此同时,「全栈开发」和「大模型应用开发」也成了很多人想要学习的方向。

我自己折腾了一个 Next.js 全栈 AI 博客项目,把 Prompt 工程、RAG 知识库、流式输出、AI Copilot 这些东西都实践了一遍。今天想通过这篇文章,把我在这个项目里学到的东西分享出来,希望能帮到想入门这个领域的朋友。

GitHub 地址github.com/flawlessv/S…

🔗 线上地址powder.icu/

本文主要讲述如何实现博客的AI相关功能,想了解基础功能如何实现的同学可以看下Next.js全栈开发从入门到部署实战

先看看这个博客长什么样:

shouye.png

后台仪表盘.png 项目用的技术栈:

  • 前端:Next.js 15 + TypeScript + shadcn/ui + Tailwind CSS
  • 后端:Next.js API Routes + Prisma ORM
  • AI:Kimi API + Ollama + ChromaDB

接下来我会从 Prompt 讲起,然后聊聊 AI Copilot、RAG、流式输出这些功能是怎么实现的。


一切的起点:Prompt

说到大模型应用开发,绑不开的就是 Prompt。

Prompt 是什么? 说白了就是你跟大模型说的话。你怎么问,它就怎么答。问得好,答案就靠谱;问得烂,答案就离谱。

我在做这个项目的时候发现,很多 AI 功能的本质都是一样的:构造一个 Prompt,然后调 LLM API

比如:

  • AI 生成文章标题?Prompt + LLM
  • AI 生成摘要?Prompt + LLM
  • AI 推荐标签?还是 Prompt + LLM

所以想玩好大模型应用,Prompt 工程是必须要会的。

结构化 Prompt

写 Prompt 其实跟写文章差不多,有结构会比乱写好很多。我在项目里用的是一种叫「结构化 Prompt」的写法,大概长这样:

# Role: 你的角色

## Profile

- Author: xxx
- Version: 1.0
- Language: 中文
- Description: 角色描述

## Skills

- 技能1
- 技能2

## Rules

1. 规则1
2. 规则2

## Workflow

1. 第一步做什么
2. 第二步做什么

## OutputFormat

- 输出格式要求

## Initialization

作为 <Role>,严格遵守 <Rules>,按照 <Workflow> 执行任务。

这种写法的好处是逻辑清晰,大模型更容易理解你想要什么。

举个实际的例子,这是我项目里用来生成文章摘要的 Prompt:

export function buildExcerptPrompt(content: string): string {
  return `# Role: 内容摘要撰写专家

## Profile
- Author: Spring Broken AI Blog
- Version: 2.0
- Language: 中文
- Description: 你是一位专业的内容编辑,擅长从长文中提取核心信息,撰写简洁有力的摘要。

## Rules
1. 摘要长度必须严格控制在 100-200 个汉字之间
2. 必须包含文章的核心观点和主要结论
3. 使用简洁、专业的语言,避免冗余表达
4. 只返回摘要文本,不要包含任何其他内容

## Workflow
1. 仔细阅读并理解完整的文章内容
2. 识别文章的核心主题和主要论点
3. 用简洁的语言组织摘要
4. 输出纯文本摘要

## Input
文章内容:
${content.slice(0, 3000)}

## Initialization
作为 <Role>,严格遵守 <Rules>,按照 <Workflow> 撰写摘要。`;
}

你看,其实就是告诉大模型:你是谁、要遵守什么规则、按什么流程做事、输出什么格式。把这些说清楚了,大模型的输出质量会好很多。


AI Copilot:编辑器里的智能补全

这个功能是我觉得最有意思的一个,效果类似 GitHub Copilot 或者 Cursor,在你写文章的时候实时给你补全建议。

AI文章新建和编辑页.png

实现思路

说穿了也不复杂:把文章上下文 + Prompt 丢给 LLM,让它帮你续写

具体流程是这样的:

  1. 用户在编辑器里打字
  2. 我提取光标前 500 个字符作为上下文
  3. 构造一个 Prompt,大意是「根据上下文,续写 5-30 个字」
  4. 调 Kimi API 拿到补全建议
  5. 把建议以灰色斜体显示在光标后面
  6. 用户按 Tab 接受,按 Esc 取消

技术难点

这个功能看起来简单,但实际做起来有几个坑:

1. 非侵入式显示

补全建议不能直接写入文档,只能在视图层显示。

我一开始想的就是用样式来实现——在光标位置叠加一个灰色斜体的文本,看起来像是补全建议,但实际上不是文档的一部分。这个思路是对的,关键是怎么实现。

参考了 VSCode 的做法。VSCode 的 AI 补全(GitHub Copilot)用的是「虚拟文本」机制:补全建议只在视图层显示,不写入文档模型。只有用户按 Tab 确认后,才真正写入。

我用的编辑器是 Tiptap(基于 ProseMirror),刚好有类似的机制叫 Decoration。它可以在视图层叠加显示内容,不影响文档结构,正好符合我的需求。

2. 防抖

用户打字很快的时候,不能每敲一个字就调一次 API,那样太浪费了。我设了 500ms 的防抖,用户停下来半秒钟才触发补全请求。

3. 异步竞态

用户可能在 API 返回之前又继续打字了,这时候光标位置已经变了。如果直接把补全建议显示出来,位置就对不上了。

我的做法是双重位置校验:发请求前记录光标位置,API 返回后再校验一次,位置变了就不显示。

// 第一次校验:防抖回调执行时
const currentState = extension.editor.state;
if (currentSelection.from !== currentFrom) {
  return; // 位置已改变,丢弃请求
}

// 调用 AI API...

// 第二次校验:API 返回后
const latestState = extension.editor.state;
if (latestState.selection.from === currentFrom) {
  // 位置仍然一致,才更新状态
}

4. ProseMirror 插件

编辑器用的是 Tiptap(基于 ProseMirror),补全建议的显示用的是 Decoration,不会影响文档结构,只是视觉上的装饰。

核心代码大概长这样:

// 创建补全建议的视觉装饰
const widget = document.createElement("span");
widget.className = "ai-completion-suggestion";
widget.style.cssText =
  "color: #9ca3af; " + // 灰色
  "font-style: italic; " + // 斜体
  "pointer-events: none; " + // 不拦截鼠标
  "user-select: none;"; // 不可选中

widget.textContent = suggestion;

// 在光标位置显示
const decoration = Decoration.widget(position, widget, {
  side: 1, // 光标后
  ignoreSelection: true,
});

RAG:让 AI 基于你的内容回答问题

RAG 是这个项目里我花时间最多的功能。

先聊聊向量数据库

在讲 RAG 之前,得先说说向量数据库是什么。

我们平时用的数据库,比如 MySQL、MongoDB,存的都是结构化数据或文档。查询的时候用的是精确匹配或者关键词搜索。

但 AI 领域有个问题:怎么找到「语义相似」的内容?比如「如何写好 Prompt」和「Prompt 工程技巧」,这两句话关键词不一样,但意思很接近。传统数据库搞不定这个。

向量数据库就是为了解决这个问题。它的思路是:

  1. 把文本转成一串数字(向量),这个过程叫 Embedding
  2. 语义相似的文本,转出来的向量也相似
  3. 查询的时候,把问题也转成向量,然后找最相似的几个

常见的向量数据库有 Pinecone、Milvus、Chroma 等。我用的是 Chroma,开源免费,轻量好用。

为什么需要 RAG?

大模型虽然很聪明,但它不知道你博客里写了什么。你问它「我之前写的那篇关于 Prompt 的文章讲了什么」,它只能瞎猜。

这是因为大模型的知识有两个问题:

  1. 知识不新:训练数据有截止日期,不知道最新的事
  2. 知识不全:不知道你的私有内容

RAG(Retrieval-Augmented Generation,检索增强生成)就是为了解决这个问题。简单说就是给大模型「开卷考试」:先从你的内容里检索相关信息,再让大模型基于这些信息回答。

cover.gif

我的实现思路

整个流程分两部分:

离线索引(把文章存起来)

  1. 把文章切成小块(语义分块)
  2. 用 Ollama 把每个块转成向量(Embedding)
  3. 把向量存到 ChromaDB

在线检索(用户提问时)

  1. 把用户的问题也转成向量
  2. 在 ChromaDB 里找最相似的几个块
  3. 把这些块作为上下文,构造 Prompt
  4. 调 Kimi API 生成回答

分块的坑

分块这一步踩了不少坑。

一开始我想简单点,按固定字符数切,比如每 500 字一块。结果发现很多问题:句子被截断、段落被分割、检索时匹配到不完整的片段。

后来改成了语义分块,按优先级:

  1. 先按段落分(\n\n
  2. 段落太长就按句子分(。!?
  3. 实在不行才硬切

还有一个坑是 Ollama 的 nomic-embed-text 模型有 800 字符的限制。超过这个长度就报错。

我的处理方式是:如果一个块超过 800 字符,就把它切成多个子块,每个子块单独生成向量,单独存储。这样虽然麻烦点,但不会丢信息。

// 语义分块的核心逻辑
export function chunkPost(content: string, options = {}) {
  const maxChars = options.maxChars || 800; // Ollama 硬限制
  const chunks = [];

  // 按段落分割
  const paragraphs = content.split(/\n\n+/).filter((p) => p.trim());

  for (const para of paragraphs) {
    if (para.length > maxChars) {
      // 段落太长,按句子分割
      splitBySentence(para, chunks);
    } else {
      chunks.push(para);
    }
  }

  return chunks;
}

流式输出:打字机效果

如果你用过 ChatGPT,应该对那个打字机效果有印象。AI 的回答不是一下子全出来,而是一个字一个字蹦出来的。

这个效果不只是好看,更重要的是用户体验。如果等 AI 生成完再返回,用户可能要干等好几秒,体验很差。流式输出让用户立刻看到反馈,感觉响应更快。

实现思路

流式输出的核心是 SSE(Server-Sent Events)

传统的 HTTP 请求是:发请求 → 等待 → 收到完整响应。

SSE 是:发请求 → 保持连接 → 服务器持续推送数据 → 最后关闭连接。

一个请求,多次推送

后端代码大概是这样:

// 创建 SSE 流
const stream = new ReadableStream({
  async start(controller) {
    const sendEvent = (type, data) => {
      const message = `event: ${type}\ndata: ${JSON.stringify(data)}\n\n`;
      controller.enqueue(encoder.encode(message));
    };

    // 调用 Kimi API,流式返回
    await aiClient.chatStream(messages, {}, (chunk) => {
      // 每收到一个文本块,就推送给前端
      sendEvent("chunk", { chunk });
    });

    // 完成后关闭连接
    sendEvent("complete", { done: true });
    controller.close();
  },
});

return new Response(stream, {
  headers: {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  },
});

前端用 fetch + ReadableStream 读取:

const response = await fetch("/api/ai/rag/stream", {
  method: "POST",
  body: JSON.stringify({ question }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  // 解析 SSE 格式,更新 UI
  parseSSE(text, (chunk) => {
    setContent((prev) => prev + chunk); // 追加文本,实现打字机效果
  });
}

其他功能

除了上面说的 AI 功能,项目里还有一些基础功能:

详情.png

aboutme.png

AI 生成标题、摘要、标签

这些都是「Prompt + LLM」的套路。给大模型文章内容,让它生成标题/摘要/标签。

相关文章推荐

用当前文章的标题和摘要生成向量,在 ChromaDB 里找最相似的几篇文章。比传统的「按标签匹配」更智能。

降级机制

RAG 依赖 Ollama 和 ChromaDB,这两个服务挂了怎么办?

我做了降级处理:如果 RAG 不可用,就退化成纯 LLM 模式。虽然回答质量会差一些,但至少功能还能用。


如何获取 LLM API Key

这个项目用的是 Kimi(Moonshot AI)的 API,申请地址:

platform.moonshot.cn/

注册后会有免费额度,个人学习完全够用。

其他可选的 LLM 服务:


快速上手

项目开源在 GitHub,感兴趣的话可以 clone 下来跑一跑:

GitHub 地址github.com/flawlessv/S…

如果觉得有帮助,欢迎给个 ⭐️ Star!

详细的安装步骤在 README 里都有,这里就不展开了。简单说就是:

  1. 安装 Node.js、Ollama、ChromaDB
  2. 配置 Kimi API Key
  3. npm install + npm run dev

总结

做完这个项目,我最大的感受是:大模型应用开发没有想象中那么难

很多功能的本质都是「Prompt + 调 API」,关键是把 Prompt 写好,把流程理清楚。

通过这个项目,你可以学到:

  • Next.js 全栈开发(前端 + 后端 + 数据库)
  • Prompt 工程(结构化 Prompt、角色设定、规则约束)
  • RAG 实现(向量化、语义分块、相似度检索)
  • 流式输出(SSE、ReadableStream)

如果想继续深入,可以看看这些方向:

  • Agent:让 AI 自己规划任务、调用工具
  • MCP:模型上下文协议,统一 AI 与外部系统的交互
  • 微调:用自己的数据训练模型

希望这篇文章对你有帮助。有问题欢迎交流!

附录:参考资料

本项目在开发过程中参考了以下优秀内容:

前端佬们!!AI大势已来,未来的上限取决你的独特气质!恭请批阅!!

作者 大怪v
2026年1月10日 20:23

前言

写这篇文章,纯粹是AI热潮给炸出来的。本来想继续更我的虚拟世界专栏,但看了一眼沸点,好家伙,大家都在聊DeepSeek、ChatGPT,感觉我不说两句显得我很不合群。

还有另外一个原因,就是身边的很多程序员,都在焦虑。

那么,程序员真的会被替代吗?兄弟们,别急别急!

我直接给出我自己的观点:如果还是之前那种记俩API、写两功能的程序员,我相信很快会替代。但是那种会整活、会整合,直接面向问题解决的程序员,不会的!!!

O9.gif

对比以前还要苦哈哈地背API,现在AI把门槛直接铲平了。这哪里是危机?这分明是从“螺丝钉”进化成“架构师”的最佳版本!

我的核心观点:AI对于之前的经验总结、归纳非常牛牪犇!!但是对于复杂问题、现实的敏感度以及所谓创新,他们还直接不能!!

来,上理由!

0变1的门槛,被无限拉低了!

以前你想做一个全栈应用,你得懂前端、后端、数据库、运维... 还没开始写代码,环境配置就先把你劝退了。

现在呢?

只要你的想法够骚,AI就是你的一万个分身。

骄傲.gif

我有一个专栏,手搓虚拟世界

在没有 AI 的时候,你想从 0 到 1 做一个产品,你要懂后端、懂数据库、懂运维,甚至还得懂点 UI 设计。这每一项,都是一座大山。很多很棒的 idea,就死在了“我不会写后端接口”或者“这 UI 丑得我没眼看”上。

现在呢?

AI 就是你的那个全能外包团队。你不会写 SQL?问它。你不会画 icon?让它画。 以前我们为了画一个完美的圆,可能要算半天 Math.PI,现在你只需要告诉 AI:“给我整一个圆,要五彩斑斓的那种。”

0 变 1 的过程,不再是技术的堆砌,而是你“脑洞”的直接具象化。 只要你有想法,技术实现的壁垒正在被 AI 暴力拆除。

这是拼气质的时代

很多人说 AI 出来的代码没有灵魂,是缝合怪。 我说:别急别急!

当所有人都能一键生成标准化代码的时候,什么东西最值钱? 是个性。是那种“独属前端佬气质”的创新。

0.gif

就像当年的 Flash,工具大家都有,但只有少数人能做出《小小火柴人》。AI 时代同理,它能帮你生成 90% 的通用代码,但剩下那 10% 的、决定产品气质的、让人眼前一亮的 “手搓” 部分,才是你真正的价值所在。

未来的牛人,不是谁 API 背得熟,而是谁能用 AI 这个超级引擎,组合出别人没见过的玩法。这不就是我们最擅长的吗?不依赖第三方库(因为 AI 可能会瞎引用),纯靠逻辑和创意,去构建一个新的虚拟世界。

能做什么,取决于你能想到什么

以前想整一个事情,大致如下流程:想法=>需求=>原型=>UI=>交互=>编写代码

完全靠人海战术。 现在?一个拿着 AI 的工程师(或者说“全栈工程师”),战斗力可能抵得上以前的一个公司。

这意味着什么?意味着个人创新的回报率被无限放大了。你不需要在一个项目中,当一颗在大机器里运转的螺丝钉,你有机会成为那个设计机器的人。

假如未来硬件再大升级(就像我之前说的智能眼镜、脑机接口),结合 AI 的生产力,一个人手搓一个“元宇宙”雏形,可能真的不再是梦。

93d794510fb30f248be3f2baca95d143ac4b03e8.gif

AI 不会淘汰有想法的人,它只会淘汰那些只会 copy-paste 的“代码搬运工”。

与其在焦虑中等待被替代,不如现在就头脑热一把,利用这个时代的红利,去“手搓”一点属于你自己的、独一无二的东西。

毕竟,只有当现有的规则已经装不下你的野心时,打破规则才更有乐趣。

前端佬们,别怂,干!

打造 AI 驱动的 Git 提交规范助手:基于 React + Express + Ollama+langchain 的全栈实践

作者 ohyeah
2026年1月10日 16:50

在现代软件开发中,高质量的 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 助手,让每一次提交都成为工程艺术的一部分!

第11章 LangChain

作者 XiaoYu2002
2026年1月10日 16:28

LangChain 是一个用于开发基于大语言模型(LLM)应用程序的开源框架,它通过提供模块化的抽象组件和链式调用工具,将 LLM 与外部数据源(如文档、数据库)和计算工具(如搜索引擎、代码解释器)智能连接,从而构建出具备记忆、推理和行动能力的增强型 AI 应用,典型场景包括智能问答、内容生成和智能体(Agent)系统。

LangChain官方文档:docs.langchain.com/。网址的组成逻辑和Ne…

LangChain支持Python和TypeScript两种编程语言,如图11-1所示。

image-20251219041533100

图11-1 LangChain开源代理框架-语言选项

LangChain开源代理框架有3种选择方案:

(1)LangChain:一些普通对话,音频识别、文字生成,图片生成等等与AIGC相关的,用这选择方案就够了。

(2)LangGraph:想做工作流,类似Dify,Coze,那么就需要使用该方案。

(3)Deep Agents:想做一些大型的AI相关高级应用,就需要使用该方案,Deep Agents是深度集成的意思。

LangChain 作为基础框架,适合构建常规的AIGC应用(如对话、文生图);LangGraph 专注于通过有状态、可循环的图结构来编排复杂、多步骤的智能体工作流,是开发类Dify/Coze平台或自动化业务流程的核心选择;而 Deep Agents 则代表了一种更深度集成、能处理高复杂度任务与自主决策的高级智能体架构,常用于需要多智能体协作或模拟人类工作流的大型企业级AI应用。

我们这里学习的话,使用第一个LangChain就完全够用。LangChain下载使用说明如图11-2所示。我们点击如图11-1所示的第一个选项后,会跳转到如图11-2所示的界面,需要点击左侧边栏的install选项。官方文档有对应的使用说明。

image-20251219042243761

图11-2 LangChain下载使用说明

## 11.1 初始化项目

接下来,我们要开始初始化这次的项目。会沿用第10章 SSE魔改的代码,在该基础上,需要补充以下安装步骤:

(1)@langchain/core:LangChain 的核心基础库,包含链、提示模板、检索器等核心抽象。

(2)@langchain/deepseek: LangChain 为DeepSeek 模型专门提供的集成包,让我们能在 LangChain 框架中直接调用 DeepSeek 的 API。安装规则是@langchain/所需AI大模型,例如@langchain/openai。

(3)langchain:LangChain 的主包,提供了高级、易于使用的接口来组合和使用 LLM。

安装LangChain之后,我们需要一个AI大模型来支持,在这次示例中,选择DeepSeek,因为它的API非常便宜。

// 终端执行命令
npm install @langchain/core @langchain/deepseek langchain

安装之后的package.json文件如下所示。

{
  "type": "module",
  "dependencies": {
    "@langchain/core": "^1.1.6",
    "@langchain/deepseek": "^1.0.3",
    "@types/cors": "^2.8.19",
    "@types/express": "^5.0.6",
    "cors": "^2.8.5",
    "express": "^5.2.1",
    "langchain": "^1.2.1"
  }
}

由于我们在7.1小节已经升级过Node.js版本,因此可以直接用Node.js去运行ts后缀文件,满足Node.js版本大于23的都可以这么做,如果无法运行ts后缀文件,需要检查一下Node.js版本或者采用ts-node。

接下来到index.ts文件中初始化后端服务。

// index.ts
import express from 'express'
import cors from 'cors'

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => { })

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

11.2 接入大模型

接着接入我们的AI大模型DeepSeek,还是在index.ts文件中。

在这里引入了一个key.ts文件,该文件存放着DeepSeek API的Key。

import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
const deepseek = new ChatDeepSeek({
    apiKey: key,
    model: 'deepseek-chat',
    temperature: 1.3,
    maxTokens: 1000, //500-600个汉字
    topP: 1, //设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
    frequencyPenalty: 0,//防复读机诉 AI:"你别老重复同一个词!"-2   2
    presencePenalty: 0,//鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

获取DeepSeek的key,如下4步骤:

(1)打开DeepSeek的API开放平台:platform.deepseek.com/

(2)微信扫码登录,去实名认证。

(3)点击左侧边栏的用量信息选择去充值选项,有在线充值和对公汇款两个选项,选择在线充值,自定义输入1块钱(够用了),然后自己选择支付宝或者微信支付去付款。

(4)付款成功后,点击左侧边栏的API keys选项,创建API key,随便输入一个名称(中英文都可以),然后会弹出key值,此时复制key值再关闭弹窗,因为当你关闭后,就再也拿不到这个key值了。忘记就只能重新再建一个,DeepSeek会提醒你的,如图11-3所示。

友情提示:保护好你的key值,别暴露在公网中,在开源项目上传GitHub中,可以让git忽略key.ts文件。或者如果你的DeepSeek API就只充了1块钱,然后用得剩几毛钱,并且以后都不怎么打算充,那想不想保护key值,就看你心情了。

image.png

图11-3 创建API key注意事项

通过以上步骤获取到DeepSeek的key值后,在项目创建key.ts文件,创建常量key,填入你的key值并导出。

export const key = '你DeepSeek的key值'

回到index.ts文件,接入DeepSeek大模型之后,ChatDeepSeek有一个model字段,这是用于选择我们模型的。已有的模型类型需要从DeepSeek官方文档中获取:模型 & 价格 | DeepSeek API Docs

// DeepSeek字段
apiKey: key,
model: 'deepseek-chat',
temperature: 1.3,
maxTokens: 1000, // 500-600个汉字
topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
frequencyPenalty: 0,// 防复读机诉 AI:"你别老重复同一个词!"-2   2
presencePenalty: 0,// 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2

目前可选的模型有deepseek-chat和deepseek-reasoner。DeepSeek官网价格计算是以百万Token为单位,其中1 个英文字符 ≈ 0.3 个 token;1 个中文字符 ≈ 0.6 个 token。只要大概知道很便宜就足够了。

image-20251219050542592

图11-4 DeepSeek模型选择

temperature字段是温度的含义,在DeepSeek官方文档中有直接给出对应的建议,我们的示例是打算用于对话,因此设置1.3就足够了,Temperature参数设置如图11-5所示。

从应用场景,我们可以理解为temperature字段大概是理性与感性的权衡度,逻辑性越强的场景,温度越低;越感性的场景温度越高。所有AI大模型都是类似的,从他们对应的官方文档去获取对应信息就可以了。

image-20251219050952588

图11-5 DeepSeek模型-Temperature参数设置

其余4个参数maxTokens、topP,frequencyPenalty和presencePenalty如下:

(1)maxTokens 直接决定了 AI 回复的最大长度,限制了单次响应的文本量;

(2)topP(核采样参数)通过控制候选词的概率分布来影响文本的创造性与稳定性——值越低则 AI 的选词越集中和可预测,输出趋于“死板”,值越高则选词范围越宽,输出越“放飞自我”并富有创意。

(3)而 frequencyPenalty 与 presencePenalty 则分别从词频和话题层面抑制重复:frequencyPenalty 正值会惩罚在当前回复中已经频繁出现的词语,促使用词更加多样;presencePenalty 正值则会惩罚在已生成的上下文中出现过的所有主题,鼓励 AI 主动切换到新的话题或角度,从而共同确保生成内容的多样性和连贯性,避免陷入单调或循环重复的表达。

这些值具体设置多少,则需要根据具体场景的经验以及自身的理解,推荐看我写的AI使用手册,开头有讲解到这一部分注意力机制:AI精准提问手册:从模糊需求到精准输出的核心技能(上)

11.3 AI对话

接下来需要从langchain引入createAgent方法,并使用我们设置好的deepseek实例对象。我们调用agent身上的invoke()方法,该方法更适合单次输出(一次性直接返回),即非流式返回。

通过createAgent方法除了可以设置接入的大模型,还可以通过systemPrompt字段去设置Prompt提示词。

通过LangChain代理的stream()方法调用DeepSeek模型处理用户请求:将客户端发送的req.body.message作为用户消息输入,并设置streamMode为 "messages" 来获取结构化的消息流响应;在等待代理完成流式生成后,将整个结果集作为JSON数据一次性返回给客户端。

import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/json')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.invoke({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  })
  res.json(result)
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

接下来回到index.html文件一下,我们需要设置客户端返回给后端的问题,也就是往req.body.message里塞一下咨询AI的问题。

<script>
    fetch('http://localhost:3000/api/chat', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ message: '请问你是什么AI大模型' })
    }).then(async res=>{
        const reader = res.body.getReader()
        const decoder = new TextDecoder()
        while (true) {
            const { done, value } = await reader.read()
            if (done) {
                break
            }
            const text = decoder.decode(value, { stream: true })
            console.log(text)
        }
    })
</script>

我们问了一个:“你是什么AI大模型”的问题,浏览器返回AI对话信息如图11-6所示。

image-20251219054656481

图11-6 DeepSeek模型-Temperature参数设置

到这为止,我们就正式打通了AI对话的环节。并且如果我们打开网络选项卡,可以发现AI对话返回的内容是post请求的。如果我们想改成流式输出也是post请求,在第10.3小节所学习的SSE设置post请求就可以用上了。

11.4 流式输出AI对话

如果我们想修改成流式输出对话的话,需要修改3个地方:

(1)后端设置的Content-Type类型改成事件流类型。

(2)agent不使用invoke()方法,该换专门的agent.stream()流输出方法,并调整对应参数。

(3)agent.stream()流输出方法返回迭代器,针对迭代器去调整输出形式。

import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "messages" })
  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

agent.stream()方法有第二个参数,用于指定流式输出的数据格式和粒度,决定了从流中接收到的是原始令牌、结构化消息还是其他中间结果。agent.stream()方法第二个参数的选项如表11-1所示。我们选择messages就可以了。如果想要真正存粹的打字效果并且节约token,可以使用values选项。

表11-1 agent.stream()方法第二个参数的选项

流模式 返回的数据类型 典型用途 示例输出(逐块)
"messages" 完整的消息对象 需要处理结构化对话(如获取AI回复的完整消息) {"role": "assistant", "content": "你好"}
"values" 底层值(如原始token) 需要实现逐字打印效果或最低级控制 "你" "好"
"stream" 混合事件流 需要同时获取token和消息等多样信息 {"type": "token", "value": "你"}
const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "values" })

其次,由于agent.stream()方法的返回值类型是IterableReadableStream<StreamMessageOutput>,说明返回值就是一个迭代器。因此可以使用for await of语法糖来流式输出内容,不用手动的去调用迭代器的next()方法。

  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()

AI对话-流式输出如图11-7所示。会按顺序返回非常多的JSON格式数据,通过data字段下的kwargs的content可以看到AI返回内容以三两字的形式不断输出。并且在前端的接收流式输出,不会因post请求而出现问题。

image-20251219060557208

图11-7 AI对话-流式输出

流式输出AI对话的完整代码如下:

// index.ts
import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "messages" })
  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
// index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    fetch('http://localhost:3000/api/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ message: '请问你是什么AI大模型' })
    }).then(async res => {
      const reader = res.body.getReader()
      const decoder = new TextDecoder()
      while (true) {
        const { done, value } = await reader.read()
        if (done) {
          break
        }
        const text = decoder.decode(value, { stream: true })
        console.log(text)
      }
    })
  </script>
</body>

</html>

以上就是使用LangChain接入DeepSeek大模型并实现AI对话和基础提示词的案例。

[转载] 认知重建:Speckit用了三个月,我放弃了——走出工具很强但用不好的困境

作者 wyanassert
2026年1月10日 12:27

[转载] 认知重建:Speckit用了三个月,我放弃了——走出工具很强但用不好的困境

原文地址

2025 年 AI 编程工具遍地开花,但一个尴尬的现实是:工具越来越强,预期越来越高,落地却越来越难——speckit 的规范流程在企业需求的”千层套路”、海量代码面前显得理想化,上下文窗口频繁爆满让复杂任务半途而废,每次做类似需求还是要花同样的时间因为知识全在人脑里。本文记录了我从踩坑规范驱动工具,到借鉴 Anthropic 多 Agent 协作架构、融合上下文工程与复合工程理念,最终实现边际成本递减、知识持续复利的完整历程。如果你也在”AI 工具明明很强但就是用不好”的困境中挣扎,或许能找到一些共鸣。附带还有新的工作流下人的工作模式转变思考~

起点:规范驱动开发的美好承诺

1.0 团队的 AI Coding 起点

先交代一下背景:我所在的是一个后端研发团队,日常工作以存量项目迭代为主,涉及多个微服务的协作开发。

2024 年中,团队开始尝试 AI 辅助编程。最初的体验是:

短上下文场景效果不错

  • 写一个独立函数、实现一个工具方法——AI 表现良好
  • 简单的代码补全、格式化、注释生成——确实提效

但规模化复用始终没起来

  • 当时只有三种触发类型的 rules(早期 rules 时代)
  • 虽然提出过”在基础 agent 之上封装 agent”的想法
  • 但几个月过去,仍然没有太多人真正动起来

原因分析

  • 规范没有形成共识——每个人对”怎么用好 AI”理解不同
  • 对 AI 工程化没有标准认识——不知道该往哪个方向努力
  • 提示词复用习惯没建立——好的 prompt 停留在个人经验,没有沉淀

这个困境促使我开始探索外部方案:有没有已经成熟的”AI 编程工程化”方法论?有没有可以直接借鉴的最佳实践?

带着这些问题,我遇到了 speckit 和 openspec。

遇见 speckit:AI 编程的”正确打开方式”?

2024 年开始,AI 编程助手如雨后春笋般涌现。Copilot、Cursor、Claude 让很多人第一次体验到了”AI 写代码”的魔力。但兴奋之后,问题也随之而来:

  • AI 生成的代码质量参差不齐
  • 需求理解经常偏离预期
  • 缺乏持续性,上下文丢失严重
  • 改一处坏十处,维护成本高

正当我被这些问题困扰时,遇到了 speckit——一个规范驱动开发(Spec-Driven Development, SDD)工具包。

speckit 的理念很吸引人:

1
2
3
规范即代码 → 规范直接生成实现,而非仅作为指导文档
权力倒置 → 代码服务于规范,而非规范服务于代码
测试优先 → 强制 TDD,不可协商地要求先写测试

它定义了一套清晰的 5 阶段流程:

1
2
Constitution → Specify → Plan → Tasks → Implement
(宪章) (规范) (计划) (任务) (实施)

每个阶段对应一个命令,依次执行:创建项目宪章和开发原则 → 定义需求和用户故事 → 创建技术实现计划 → 生成可执行的任务列表 → 执行所有任务构建功能。

再加上 9 条不可变的架构原则(库优先、CLI 接口、测试优先、简洁性、反抽象…),7 层 LLM 输出约束机制,防止过早实现、强制标记不确定性、结构化自检…

这不就是 AI 编程的”工程化正确答案”吗?

带着这样的期待,我开始在项目中尝试落地。

openspec:另一种优雅的尝试

除了 speckit,我还研究了 openspec——一个更轻量的规范驱动框架:

1
2
3
Specs as Source of Truth → specs/ 目录始终反映系统当前真实状态
Changes as Proposals → 所有修改先以提案形式存在,经确认后实施
Lock Intent → AI 编码前通过明确规范锁定意图

openspec 的 Delta 机制设计得很巧妙:不同于直接存储完整的”未来状态”,它只存储变更操作本身(ADDED/MODIFIED/REMOVED/RENAMED)。归档时通过语义名称匹配来定位需求,避免了 Git Merge 常见的位置冲突问题。同时采用 Fail-Fast 机制,在写入前做完整冲突检测,保证不会产生半完成状态。

两个工具,两种风格,但都指向同一个目标:让 AI 编程更可控、更规范。

碰壁:理想流程遭遇企业现实

一个真实需求的”千层套路”

让我用一个真实的 12 月活动需求来说明问题:

协作复杂度

  • 跨 BG、跨前后端、跨 FT、跨项目、跨小组、跨服务
  • 跨部门合作接口因合规要求变来变去,迟迟给不到位
  • 雅典娜平台上接近 20 种商品类型,全得人工一个个配
  • 活动流程必须按”玩法引擎”的方法论来拆解
  • 技术方案得按习惯写在 iWiki 里

并行任务流

1
2
3
4
5
6
同时处理:
├── 找产品确认商品细节
├── 找运营确认玩法逻辑
├── 找跨团队研发对齐接口
├── 找跨项目研发对齐交互
└── 内部技术方案评审

方案设计的”考古”需求

  • 某个商品创建、资产查看以前有什么坑?
  • 现在的玩法能力有哪些?能不能直接用?
  • 导航小结页到底是啥?怎么让它弹 Banner?

**写代码前的”九九八十一难”**:

1
2
3
4
5
6
前置任务链:
├── 玩法引擎:依赖数据、激励动作要在引擎仓库里实现
├── 外部依赖:关联的代码改动在其他服务里
├── 配置中心:要去阿波罗(Apollo)配配置
├── 雅典娜:商品场景得先配好(早期没数据还得 Mock)
└── 数据库:涉及表变更,得去测试环境操作

执行中的细节坑

  • 阿波罗配置有个坑,该怎么绕过去?
  • 规则引擎的语法到底怎么写?
  • 商品发放操作是重点,具体发到哪个钱包?

speckit 流程 vs 企业现实

把 speckit 的理想流程放到这个场景里:

1
2
3
4
5
6
7
8
9
speckit 假设的流程:
Constitution → Specify → Plan → Tasks → Implement
↓ ↓ ↓ ↓ ↓
一次性定义 一次性写清 线性规划 任务分解 按序实施

企业现实:
多方博弈 → 动态调整 → 并行推进 → 持续扯皮 → 边做边改
↓ ↓ ↓ ↓ ↓
需求会变 方案会改 依赖会卡 资源会抢 意外会来

核心矛盾:speckit 假设需求是清晰的、可一次性规划的,但企业真实需求是动态的、多方博弈的、持续变化的。

openspec 的 Delta 机制也救不了

openspec 的”提案→审查→归档”流程看起来更灵活,但:

  • **假设需求可以”提案化”**:实际上外部接口因合规变来变去,5 个维度同时推进相互依赖,评审中发现问题需要立即改方案

  • 人工介入成本高:Delta 与主 Spec 冲突时报错终止,复杂冲突需要人工解决,而人的认知窗口有限。具体来说,openspec archive 会在以下情况直接报错退出:

    • MODIFIED 引用的需求在主 Spec 中不存在(可能被别人删了或改名了)

    • ADDED 的需求在主 Spec 中已存在(别的分支先合入了同名需求)

    • RENAMED 的源名称不存在,或目标名称已被占用

    • 同一个需求同时出现在 MODIFIED 和 REMOVED 中(逻辑矛盾)

这些冲突没有自动解决策略,CLI 只会打印类似 MODIFIED failed for header "### Requirement: xxx" - not found 的错误信息,然后终止。你需要:手动打开两个文件对比、理解冲突原因、决定保留哪个版本、手工修改 Delta 文件、重新执行归档。整个过程要求你同时在脑中持有”主 Spec 当前状态”和”Delta 期望变更”两套信息——这对认知负担是很大的挑战

  • 强依赖命名的脆弱性:产品叫”用户激励”,运营叫”活动奖励”,研发叫”商品发放”——同一个需求在不同阶段有不同表述

最致命的问题:无法应对”考古”需求

speckit 和 openspec 都有一个共同盲区:流程从零开始

1
2
3
4
5
6
7
8
9
speckit 流程:
Constitution 定义原则 → Specify 定义需求 → Plan 设计方案 → ...

但真实需求必须"考古":
├── 这个商品创建以前有什么坑?
├── 现有玩法能力有哪些?
├── 导航小结页的 Banner 怎么弹?
├── Apollo 配置有什么特殊处理?
└── 雅典娜 20 种商品类型的配置方式各不同

缺失能力:没有”上下文检索”机制,无法自动关联历史经验、已有能力、已知陷阱。

AI 生成 spec 时能看到的:

  • ✅ 代码仓库
  • ✅ project.md/Constitution
  • ✅ 用户意图

AI 看不到(但需要知道)的:

  • ❌ 业务边界(涉及哪些服务?)
  • ❌ 历史经验(以前怎么做的?有什么坑?)
  • ❌ 配置规范(Apollo 特殊要求?)
  • ❌ 平台知识(雅典娜 20 种商品配置注意事项)
  • ❌ 协作约束(依赖其他团队接口?合规要求?)

结果:依赖人 review 时逐步想起来告诉 AI,45 分钟 + 持续的认知负担。

AI 工程化如何破局?(预告)

面对上述问题,AI 工程化的解决思路是什么?这里先做个预告,详细方案见第五节。

企业现实问题 speckit/openspec 的困境 AI 工程化的解法
需求动态变化 假设一次性规划,变更成本高 需求以”进行中”状态管理,支持随时调整,阶段性沉淀
多线并行博弈 线性流程,Delta 冲突报错终止 Agent 自主决策路由,Skill 独立执行,不强依赖顺序
考古需求 无上下文检索,AI 只能看到代码 context/ 分层管理历史经验,按阶段自动加载
配置/平台知识 需要人 review 时口述 沉淀为 context/tech/,AI 执行时主动提醒
冲突解决成本 人工对比、手工修改、认知负担重 不依赖”合并”,而是”覆盖+沉淀”,冲突时 AI 辅助决策
边际成本恒定 每次 45 分钟,无复利 首次建立 context,后续复用,边际成本递减

核心差异

1
2
3
4
5
6
7
8
9
speckit/openspec 的思路:
规范化流程 → 约束 AI 行为 → 期望产出质量

问题:流程本身不适配企业现实,约束越多越僵化

AI 工程化的思路:
上下文完整性 → AI 决策质量 → 自动沉淀经验 → 下次更好

解法:不是约束 AI,而是给 AI 完整信息 + 让知识复利

一个具体例子——同样是”商品发放”需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
speckit 模式(第 3 次做):
1. Constitution → 写项目原则(已有,跳过)
2. Specify → 写需求规范(45 分钟,人逐步想起遗漏告诉 AI)
3. Plan → 写技术方案(人提醒:Apollo 有坑、钱包要区分)
4. Tasks → 生成任务(人补充:雅典娜配置注意事项)
5. Implement → 执行(遇到问题再排查)
耗时:45 分钟 + 排查时间,知识留在人脑

AI 工程化模式(第 3 次做):
1. /req-dev "商品发放需求"
2. Agent 识别意图 → 自动加载 context/experience/商品发放历史问题.md
3. Agent 提醒:"历史上有钱包选择、Apollo 配置、雅典娜商品类型三个坑点"
4. 人确认:"对,继续"
5. Skill 执行 → 自动校验 → 生成代码 → 沉淀新发现
耗时:10 分钟,知识沉淀到 context/

后续章节将详细展开这套方案的设计原理和落地实践。


反思:从第一性原理重新审视

人的认知局限是刚性约束

实话实说,我的脑容量有限:

  • 记性不好:只能记住关键的大方向,具体细节过脑就忘
  • 专注窗口小:同时关注的信息有限,必须采用”专注单任务+全局索引”策略

我的日常工作模式(经过各种场景检验的最优路径):

  • 任务管理(外挂大脑):Todo List 分优先级(红色紧急/黄色进行中/绿色完成/无色未开始)
  • 备忘录:记录死记硬背的内容(打包命令、数据库 IP 密码、文档散落信息)
  • 桌面即上下文:N 个桌面窗口,每个窗口对应一个垂直领域
  • 复杂任务 SOP 化:脑内计划 + 执行机器模式 + 文档跟踪
  • 简单任务 Fire and Forget:低频低思考成本事项秒回即忘

这套土办法是经过检验的最优路径。如果硬套 speckit/openspec 的范式,反而会丢掉这些 SOP,得不偿失。

执行过程的知识价值被忽视

speckit 和 openspec 都只关注”规范”(Spec)和”结果”(Code),忽视”过程”(Process)。

但真实价值恰恰在过程中:

1
2
3
4
5
执行 → 有问题 → 验证 → 排查 → 继续执行

排查信息往往没被记录

时间一久或换人,下次重新排查

这个循环中的排查信息,才是最宝贵的知识!

边际成本恒定是致命缺陷

1
2
3
4
5
6
7
Speckit 模式:
第 1 次商品发放需求:45 分钟(人逐步想起遗漏)
第 2 次商品发放需求:45 分钟(人 AGAIN 逐步想起遗漏)
第 n 次商品发放需求:45 分钟(还是要想,还是那么久)

边际成本恒定,无复利效应。
知识在哪里?在人脑里,每次都要重新想起来。

这与我期望的”越用越快”完全相反。

转折:遇见复合工程与上下文工程

复合式工程:让每一步都成为下一步的基石

在探索过程中,我接触到了”复合式工程”(Compounding Engineering)的理念。这个概念来自 Claude Code 团队与 Every 团队的实践交流,并在 Every 团队开源的 Compound Engineering Plugin 中得到了系统化实现——这是一个包含 27 个 Agent、19 个 Command、13 个 Skill 的完整 AI 辅助开发工具包。

定义”复合式工程”

“复合式工程”的核心目标非常明确:让每一单元的工程工作使后续工作变得更容易,而非更难。

1
2
传统开发:累积技术债务 → 每个功能增加复杂性 → 代码库越来越难维护
复合工程:每个功能产出文档模式 → 创建可复用组件 → 建立减少决策疲劳的约定 → 知识在团队中复合增长

与传统工程中每增加一个功能都会增加系统复杂度和维护成本不同,”复合式工程”追求的是一种”复利”效应,让系统的能力随着时间推移指数级增长。

核心工作流循环:Plan → Work → Review → Compound

Compound Engineering Plugin 设计了一个闭环的工作流循环:

1
2
3
4
5
Plan ──────→ Work ──────→ Review ──────→ Compound
详细规划 执行工作 质量检查 知识沉淀
↑ │
└───────────────────────────────────────┘
知识复合:下次规划更精准
  • Plan:多代理并行研究仓库模式、最佳实践、框架文档,输出结构化计划
  • Work:系统性执行计划,边做边测,质量内建
  • Review:多代理并行审查(安全、性能、架构等),输出分级 Todo
  • Compound:这是复合工程的核心——将解决的问题结构化记录,形成团队知识资产

完整实现参见:Compound Engineering Plugin

为什么叫”Compound”?

1
2
3
4
5
6
第一次解决 "N+1 query in brief generation" → Research (30 min)
文档化 → docs/solutions/performance-issues/n-plus-one-briefs.md (5 min)
下次类似问题 → Quick lookup (2 min)
知识复合 → Team gets smarter

Each unit of engineering work should make subsequent units of work easier—not harder.

实现机制:知识复合的典型场景

实现复合工程的关键,在于建立系统化的知识沉淀机制。以下是几个典型场景:

场景 1:Agent 重复犯同类错误

1
2
3
触发:发现 Agent 在某类问题上反复出错
沉淀:将教训写入 AGENTS.md / CLAUDE.md / 系统提示词
效果:该类错误不再发生,无需人工提醒

场景 2:某类问题需要频繁人工检查

1
2
3
触发:Code Review 时反复指出同类问题
沉淀:创建 Lint 规则 / Pre-commit Hook / CI 检查
效果:问题在提交前自动拦截,减少人工负担

场景 3:复杂流程被多次执行

1
2
3
触发:某个多步骤操作被团队重复执行
沉淀:封装为 Skill / Command / Agent
效果:一键触发标准化流程,新人也能执行专家级操作

场景 4:解决了一个有价值的问题

1
2
3
触发:花了较长时间解决某个棘手问题
沉淀:结构化记录到 context/experience/ 目录
效果:下次遇到类似问题,Agent 自动加载相关经验

这些场景的共同特点是:在问题解决的当下立即沉淀,而不是事后补文档。

Claude 团队的复合工程应用案例

以下是 Every 团队和 Anthropic 内部使用复合工程的真实案例:

案例 1:”@claude,把这个加到 claude.md 里”

当有人在 PR 里犯错,团队会说:”@claude,把这个加到 claude.md 里,下次就不会再犯了。”或者:”@claude,给这个写个测试,确保不会回归。”通过这种方式,错误转化为系统的免疫能力。

案例 2:100% AI 生成的测试和 Lint 规则

Claude Code 内部几乎 100% 的测试都是 Claude 写的。坏的测试不会被提交,好的测试留下来。Lint 规则也是 100% Claude 写的,每次有新规则需要,直接在 PR 里说一句:”@claude,写个 lint 规则。”

案例 3:十年未写代码的经理

经理 Fiona 十年没写代码了,加入团队第一天就开始提交 PR。不是因为她重新学会了编程,而是因为 Claude Code 里积累了所有团队的实践经验——系统”记得”怎么写代码。

案例 4:内置记忆系统

把每次实现功能的过程——计划怎么制定的、哪些部分需要修改、测试时发现了什么问题、哪些地方容易遗漏——全部记录下来,编码回所有的 prompts、sub-agents、slash commands。这样下次别人做类似功能时,系统会自动提醒:”注意,上次这里有个坑。”

成果:一个自我进化的开发伙伴

这一范式带来的最终效果是惊人的。它将 AI 从一个被动执行命令的工具,转变为一个能够从经验中持续学习、并让整个开发流程效率不断”复利”增长的开发伙伴。

为什么这解决了古老的知识管理问题

传统的知识管理困境:

1
2
3
4
5
6
7
8
方式 1:写文档
问题:没人看。写完就过时。维护成本高。

方式 2:靠人传授
问题:老人离职知识断层。新人上手慢。传授效率低。

方式 3:代码注释
问题:注释会过时。只能解释"是什么",难以解释"为什么这么做"和"以前踩过什么坑"。

复合工程的答案:把知识编码进工具,让工具在正确的时刻主动提醒你

1
2
3
4
5
6
7
8
9
不是:写一份"商品发放注意事项"文档,期望大家会看
而是:在 context/experience/商品发放历史问题.md 里记录,
Agent 在执行商品发放需求时自动加载,主动提醒

不是:靠老人口头传授"Apollo 配置有个坑"
而是:把坑编码到 skill 里,执行时自动校验

不是:在代码里写注释"这里要注意 XX"
而是:让 AI 在生成代码前就已经知道要注意 XX

关键设计模式

从 Compound Engineering Plugin 中可以提炼出三个核心设计模式:

模式 核心思想 价值
并行代理 多角度分析时启动多个专业代理,合并结果后继续 提高分析覆盖度和效率
意图路由 入口统一,根据意图自动路由到具体工作流 降低用户认知负担
知识复合 问题解决 → 文档化 → 未来查找 → 团队变聪明 边际成本递减

我的实践:基于工具架构的知识复合

基于复合工程理念,我设计了一套 AI 工程工具架构来实现知识的持续沉淀与复用:

工具架构

1
2
3
4
5
6
7
用户输入 → Command(入口)→ Agent(决策层)→ Skill(执行层)

意图识别、流程路由

调用具体 Skill 执行

experience-index(经验检索)
  • Command:用户交互入口,如 /req-dev/optimize-flow
  • Agent:自主决策,智能判断意图,可调用多个 Skill
  • Skill:固化流程,执行具体操作步骤

知识复合的两条路径

1
2
3
4
5
6
7
8
9
10
路径 1:经验沉淀(/optimize-flow)
用户发现规律 → experience-depositor Agent → 识别规则类型 → 写入规则文件

context-rules.md(上下文映射)
risk-rules.md(风险识别)
service-rules.md(服务补全)
pattern-rules.md(代码模式)

路径 2:经验检索(experience-index Skill)
需求分析/方案设计/代码编写前 → 自动检索匹配规则 → 加载相关 Context、提示风险、建议服务

复利效应示例

1
2
3
4
5
6
7
第 1 次做支付需求:45 分钟(边做边踩坑)
↓ 沉淀规则:/optimize-flow "支付需求要加载 payment-service.md 并提示资金安全"

第 2 次做支付需求:15 分钟(experience-index 自动加载背景、提示风险)
↓ 沉淀更多规则:错误处理模式、服务依赖关系

第 N 次做支付需求:5 分钟(系统已积累完整的支付领域知识)

与传统文档的本质区别

1
2
传统文档:写完没人看,看了也找不到对的时机
AI 工程化:experience-index 在正确的时刻自动检索,主动推送给 Agent

这就是为什么”知识应该沉淀到工具”不是一句口号,而是有实际 ROI 的工程决策。

对长期任务工程设计的启示

Compound Engineering Plugin 为 AI 工程化提供了极好的参考蓝图:

维度 启示
任务分解 阶段化执行(Plan → Work → Review → Compound),并行化处理,状态持久化
质量保障 多角度并行审查,分级处理(P1/P2/P3),持续验证(边做边测)
知识管理 即时文档化(趁上下文新鲜),分类存储(按问题类型),交叉引用(关联 Issue、PR)
工具设计 工具提供能力而非行为,Prompt 定义意图和流程,让代理决定如何达成目标

极简主义:设计理念如何影响我的实践

Claude Code 团队的实践给了我另一个启发:

“最好的工具,就是没有工具。”

他们的做法:

  • 只给模型一样东西:bash
  • 每周都在删工具,因为新模型不需要了
  • 减少模型的选择,就是增加模型的能力
  • “模型吞噬脚手架”——曾经的外部辅助,逐渐被模型吸收

产品极简主义:不是”越来越丰富”,而是”越来越纯粹”。每一代模型发布,工具都会变得更简单,因为复杂性转移到了模型内部。

这个理念深刻影响了我做 AI 工程化的设计思路

  1. 入口极简化:整个系统只有两个命令入口——/req-dev/optimize-flow。不是因为功能少,而是把复杂性藏到了 Agent 的智能路由里。用户不需要记住十几个命令,只需要表达意图,Agent 会判断该调用哪个 Skill。
  2. Skill 而非工具堆叠:speckit/openspec 倾向于提供更多工具、更多模板、更多约束。我选择相反的方向——把能力编码为 Skill,让 Agent 在需要时自动调用,而不是让用户手动选择”现在该用哪个工具”。
  3. 上下文自动加载:Claude Code 团队说”人类和 AI 看同样的输出,说同样的语言,共享同一个现实”。我把这个原则应用到上下文管理——不是让用户手动指定”加载哪些背景资料”,而是让 Agent 根据当前阶段自动加载相关的 context/。用户感受不到”上下文加载”这个动作,但 AI 已经具备了完整的信息。
  4. 删除优先于添加:每次迭代时,我会问自己”有哪些东西可以删掉?”而不是”还能加什么功能?”。AGENTS.md 从最初的长篇大论,精简到现在只放通用规范和目录指针,具体流程全部下沉到 Skill 里。
  5. 双重用户设计:Claude Code 为工程师和模型同时设计界面。AI 工程化也是——/req-dev 命令人可以手动调用,Agent 也可以在流程中自动调用子 Skill。同一套能力,两种调用方式,没有冗余。

当前实践的目标:让工具尽可能”隐形”——用户只需要说”我要做一个商品发放需求”,系统自动加载上下文、自动识别阶段、自动调用对应 Skill、自动沉淀经验。用户感受不到在”使用工具”,只是在”完成工作”。

注:关于工具消失的行业发展趋势,详见第九节”未来展望”。

上下文工程:AI 能力的前提是信息完整性

参考:Anthropic - Effective Context Engineering for AI Agents

什么是上下文工程?

上下文(Context) 指的是在从大语言模型(LLM)采样时包含的一组 token——不仅仅是提示词,还包括系统提示、工具定义、对话历史、检索到的文档等所有进入模型的信息。

上下文工程 是指在 LLM 推理过程中,策划和维护最优 token 集合的策略集合。它代表了 LLM 应用构建方式的根本转变:

提示词工程(旧范式) 上下文工程(新范式)
关注如何编写有效的提示词 管理整个上下文状态
主要针对一次性分类或文本生成任务 针对多轮推理和长时间运行的智能体
“找到正确的词语和短语” “什么样的上下文配置最可能产生期望行为?”

核心指导原则

找到最小可能的高信号 token 集合,最大化期望结果的可能性

为什么不重视上下文工程会导致严重问题?

很多团队把 AI 辅助编程的失败归咎于”模型不够强”或”提示词没写好”,但真正的根因往往是上下文工程的缺失。Anthropic 的研究揭示了几个关键问题:

问题 1:上下文腐蚀(Context Rot)

研究发现:随着上下文窗口中 token 数量增加,模型准确回忆信息的能力会下降

1
2
3
4
上下文腐蚀的恶性循环:
加载更多信息 → 窗口膨胀 → 信息检索精度下降 → 行为异常

人发现问题 → 加更多上下文纠正 → 窗口更膨胀 → 更差

这不是断崖式下降,而是梯度下降——模型在长上下文中仍然能力强大,但信息检索和长程推理的精度会持续降低。

问题 2:注意力预算耗尽(Attention Budget Exhaustion)

LLM 就像人类有限的工作记忆一样,拥有”注意力预算”:

1
2
3
4
5
6
7
8
9
Transformer 架构的约束:
├── 每个 token 都要关注所有其他 token,产生 n² 个成对关系
├── 训练数据中短序列比长序列更常见,模型对长上下文依赖的经验较少
└── 位置编码插值虽允许处理更长序列,但会降低 token 位置理解的精度

结果:
├── 每引入一个新 token 都会消耗注意力预算
├── 低质量的 token 会"稀释"高质量信息
└── 关键信息可能被噪声淹没

问题 3:speckit/openspec 的上下文盲区

回顾第二节的 speckit 困境,从上下文工程角度重新审视:

问题现象 上下文工程视角的根因
人 review 时逐步想起遗漏告诉 AI 历史经验没有编码为可检索的上下文
45 分钟完成需求,边际成本恒定 每次都是”冷启动”,没有上下文复用
上下文窗口频繁爆满 没有分层加载策略,一次性塞入过多信息
AI 行为异常,半途而废 上下文腐蚀导致关键信息被”遗忘”

问题 4:工具设计不当导致上下文污染

Anthropic 指出一个常见失败模式:

“臃肿的工具集,覆盖过多功能或导致使用哪个工具的决策点模糊不清”

判断标准:如果人类工程师无法明确说出在给定情况下应该使用哪个工具,AI 智能体也不能做得更好。

1
2
3
4
5
工具设计不当的后果:
├── 工具描述冗长 → 消耗上下文预算
├── 工具边界模糊 → AI 决策困难,产生更多试错对话
├── 工具返回冗余信息 → 上下文快速膨胀
└── 最终:窗口爆满,任务失败

有效上下文工程的核心原则

基于 Anthropic 的实践和我们的落地经验,总结以下原则:

原则 1:分层式信息组织

1
2
3
4
5
6
7
8
context/
├── business/
│ └── 活动业务边界.md ← 概要层(意图识别时加载)
├── tech/
│ └── Apollo配置规范.md ← 技术层(方案设计时加载)
└── experience/
├── 商品发放历史问题.md ← 经验层(实施前加载)
└── 雅典娜配置注意事项.md ← 详细层(配置时加载)

原则 2:”即时”上下文策略(Just-in-Time Context)

不是预先加载所有可能相关的信息,而是维护轻量级索引,在运行时动态加载:

1
2
3
4
5
6
7
传统方式(预加载):
启动 → 加载所有相关文档(20000 tokens)→ 开始工作 → 窗口已满一半

即时策略:
启动 → 加载索引文件(500 tokens)→ 识别当前阶段 → 按需加载(3000 tokens)

窗口保持精简,信息高度相关

Claude Code 的实践:使用 globgrep 等原语允许即时导航和检索文件,而不是预先加载完整数据对象到上下文中。

原则 3:上下文压缩与笔记系统

对于长时间运行的任务:

1
2
3
4
5
6
7
8
9
10
压缩(Compaction):
├── 将接近上下文窗口限制的对话内容总结
├── 保留:架构决策、未解决的 bug、实现细节
├── 丢弃:冗余的工具输出或消息
└── 用摘要重新初始化新的上下文窗口

结构化笔记(Structured Note-taking):
├── 智能体定期将笔记写入上下文窗口外的持久化存储
├── 稍后根据需要拉回上下文窗口
└── 实现跨压缩步骤的连贯性

原则 4:工具设计的上下文效率

1
2
3
4
5
6
7
8
9
10
11
好的工具设计:
├── 自包含:不依赖"记住"之前的对话
├── 返回精简:只返回 token 高效的必要信息
├── 边界清晰:用途明确,减少决策成本
└── 发挥模型优势:利用模型擅长的能力

坏的工具设计:
├── 返回完整数据库查询结果(可能数千行)
├── 工具描述长达数百 token
├── 多个工具功能重叠,边界模糊
└── 强迫模型做它不擅长的事情

上下文工程与 AI 工程化的关系

理解了上下文工程,就能理解 AI 工程化架构设计的”为什么”:

AI 工程化设计 上下文工程原理
context/ 分层目录 分层式信息组织,按阶段按需加载
Skill 封装固定流程 稳定执行过程,避免提示词遗漏导致的上下文不完整
Subagent 架构 主 Agent 保持精简,子任务独立窗口
状态文件传递 不依赖”记忆”,依赖结构化状态
经验沉淀机制 将知识编码为可检索上下文,而非依赖人脑

本质规律

1
2
3
AI 的决策质量 ∝ 可用信息的完整性 × 信息的信噪比
↑ ↑
不是越多越好 高信号、低噪声才有效

这意味着:

  • 与其让人在 review 时逐步想起遗漏告诉 AI
  • 不如建立系统化的上下文管理,让 AI 自动获取精简且高信号的信息

实践:AI 工程化的设计与落地

AI 工程化是什么

经过反复思考和实践,我提炼出了 AI 工程化的定义:

智能化管理工作信息,以上下文工程的理解管理整个工作场景,借助AI的能力,降低人对已识别问题的处理成本

组成部分

1. 脚手架(Git 仓库形式)

  • 把规范转为基础的目录结构
  • 附带基础的初始化命令
  • 存放业务线的上下文信息(业务背景、技术背景等)
  • 随项目独立迭代的资源文件

2. 工具包(插件形式)

  • 提供 AI 工程需要的 cmd、skill、mcp、agent、hook 等
  • 在插件市场迭代,分版本管理
  • update 即可升级最新的规范、能力集成

为什么分脚手架和工具包?

  • 插件市场内容会迭代、分版本,需要灵活升级
  • 脚手架项目初始化后,随项目迭代,是独立的 git 仓库
  • 脚手架适合存放基础资源文件和业务上下文信息
  • 工具包适合封装通用能力和规范

Image 1

核心架构:Agent + Skill 分层设计

1
2
3
4
5
用户输入 → Command → Agent(决策层)→ Skill(执行层)

意图识别、流程路由

调用具体 Skill 执行
  • Agent:自主决策层,负责意图识别、流程路由、上下文管理
  • Skill:过程执行层,负责固定流程任务的具体执行
  • Command:用户交互入口,通过 Agent 路由到具体执行

当前系统设计

  • 5 个 Agents:phase-router、requirement-manager、design-manager、implementation-executor、experience-depositor
  • 12 个 Skills:req-create、req-change、experience-index、design-create、design-change、workspace-setup、design-implementation、code-commit、requirement-completer、requirement-archiver、meta-maintainer、index-manager
  • 2 个 Commands/req-dev(需求研发统一入口)、/optimize-flow(流程优化沉淀)

目录结构:位置即语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
your-project/
├── AGENTS.md # 项目记忆入口(每次会话自动加载)
├── .codebuddy/ # AI 自动化配置
│ ├── agents/ # Agent 定义(决策层)
│ ├── commands/ # 命令入口
│ └── skills/ # Skill 定义(执行层)
├── context/ # 项目知识库(长期记忆)
│ ├── business/ # 业务领域知识
│ ├── tech/ # 技术背景
│ │ └── services/ # 服务分析文档
│ └── experience/ # 历史经验
├── requirements/ # 需求管理
│ ├── INDEX.md # 需求索引
│ ├── in-progress/ # 进行中需求
│ └── completed/ # 已完成需求
└── workspace/ # 代码工作区(Git 忽略)

三个核心约束

  1. 入口短小:AGENTS.md 只放通用规范 + 目录指针,不写具体流程步骤
  2. 位置即语义:requirements/ 放需求产物,context/ 放可复用上下文,workspace/ 放代码
  3. 复利沉淀:每次执行命令,除了产出当前结果,还要让”下一次更快、更稳”

经验沉淀的技术实现

前面 4.1 节讲了复合工程的理念和三层沉淀机制,这里聚焦具体怎么实现

触发时机:什么时候沉淀?

1
2
3
4
5
6
7
8
不是:做完需求后专门花时间"写总结"
而是:在流程关键节点自动触发沉淀

具体触发点:
├── 需求完成时 → requirement-completer skill 自动提取可复用经验
├── 遇到问题解决后 → 用户说"记住这个坑" → experience-depositor agent 记录
├── 代码提交时 → code-commit skill 检查是否有值得记录的模式
└── 流程优化时 → /optimize-flow 命令专门用于沉淀和优化

沉淀格式:记录什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# context/experience/商品发放-钱包选择问题.md

## 问题描述
商品发放时选错钱包类型,导致用户领取失败

## 触发条件
- 需求涉及商品发放
- 商品类型为虚拟商品

## 解决方案
虚拟商品必须发到虚拟钱包,实物商品发到实物钱包
具体判断逻辑见 Apollo 配置:xxx.wallet.type

## 校验方式
检查 goods_type 与 wallet_type 的匹配关系

## 关联文档
- context/tech/Apollo配置规范.md
- context/tech/services/商品服务技术总结.md

检索机制:怎么在对的时候加载?

检索由 experience-index Skill 统一负责,在需求分析、方案设计、代码编写前自动调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Agent 的上下文加载逻辑:

1. 意图识别阶段
phase-router 识别意图,路由到对应 Agent

2. 经验检索阶段
Agent 调用 experience-index Skill,传入场景描述
Skill 检索四类规则文件:
├── context-rules.md → 匹配需加载的背景文档
├── risk-rules.md → 匹配风险提示
├── service-rules.md → 匹配服务依赖建议
└── pattern-rules.md → 匹配代码规范

3. 返回结构化结果
{
"context": { "files": ["商品发放历史问题.md"] },
"risk": { "alerts": [{"level": "high", "message": "注意钱包类型"}] },
"service": { "suggestions": ["商品服务", "钱包服务"] },
"pattern": { "files": ["error-handling.md"] }
}

4. Agent 主动提醒
"注意:历史上商品发放有钱包选择问题,请确认..."

规则沉淀入口:通过 /optimize-flow 命令,调用 experience-depositor Agent 将新规则写入对应规则文件。

演进路径:从文档到 Skill 到 Command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
阶段 1:纯文档(被动)
context/experience/xxx.md
→ AI 读取后提醒,但需要人确认

阶段 2:校验 Skill(半自动)
skill/product-distribution-validator
→ 自动校验配置,发现问题直接报错

阶段 3:完整 Command(全自动)
cmd/implement-product-distribution
→ 一个命令:加载背景 + 校验 + 生成 + 提醒 + 沉淀新经验

演进判断标准:
- 同类需求做了 5 次以上 → 考虑封装 Skill
- Skill 被调用 10 次以上 → 考虑封装 Command
- 不要过早抽象,让实践驱动演进

与 speckit 的本质区别

1
2
3
4
5
6
7
8
9
speckit 的知识流向:
人脑 → Spec 文档 → 代码
↑__________|
下次还要从人脑开始

AI 工程化的知识流向:
人脑 → context/ → Skill → Command
↑_________|________|
知识留在工具链里,下次直接复用

时间成本的量化对比

前面 2.5 节从”问题-方案”角度做了概念对比,这里从时间成本角度量化差异:

执行次数 speckit/openspec AI 工程化 累计节省
第 1 次 45 分钟 45 分钟(建立 context/) 0
第 2 次 45 分钟(人重新想) 15 分钟(部分复用) 30 分钟
第 5 次 45 分钟(还是要想) 5 分钟(大量复用) 130 分钟
第 10 次 45 分钟(…) 3 分钟(高度自动化) 315 分钟

关键差异

  • 知识位置:speckit 在人脑(每次想),AI 工程化在 context/+skill/
  • 新人上手:speckit 依赖老人传授,AI 工程化第一天就能用
  • 边际成本:speckit 恒定,AI 工程化递减

深度对比:为什么传统 SDD 工具不够用

前面 2.5 节从”问题-方案”角度概述了 AI 工程化的优势,本节深入分析 speckit 和 openspec 的技术设计缺陷,帮助理解为什么需要新的解决方案。

speckit 的核心缺陷

问题 1:流程过于理想化

speckit 的 Constitution → Specify → Plan → Tasks → Implement 流程假设:

  • 需求是清晰的
  • 可以一次性规划
  • 按阶段线性推进

但企业真实场景是:

  • 需求动态变化
  • 多方并行博弈
  • 持续扯皮调整

问题 2:无法处理”考古”需求

speckit 从零开始定义,但真实开发必须”考古”:

  • 历史坑点在哪?
  • 现有能力有哪些?
  • 配置规范是什么?

问题 3:知识不会沉淀

1
2
3
每次执行:Constitution → Specify → Plan → Tasks → Implement

每次从头开始

缺失机制:

  • ❌ 实施过程中发现的坑不会被记录
  • ❌ 排查信息丢失
  • ❌ 下次遇到类似问题还得重新排查

问题 4:宪章系统的僵化

9 条不可变原则固然保证质量,但:

  • ✅ 适合标准化项目(Demo、开源库)
  • ❌ 不适合企业定制场景(历史债务、框架限制、合规要求)

openspec 的核心缺陷

问题 1:Delta 机制的理论美好与现实骨感

假设需求可以”提案化”,但企业真实场景是多线并行、动态调整、持续扯皮。

问题 2:Fail-Fast 的代价

理论上保证一致性,实际上成为阻塞点。人的认知窗口有限,很难手动解决复杂冲突。

问题 3:强依赖命名的脆弱性

产品、运营、研发对同一个需求有不同表述,命名不一致导致归档失败。

问题 4:Archive 只是”合并”,不是”学习”

1
2
3
4
5
6
F(CurrentSpec, DeltaSpec) → NewSpec

缺失的维度:
F(CurrentSpec, DeltaSpec, Context, Lessons) → NewSpec + Knowledge
↑ ↑
实施上下文 经验教训

共性问题:忽视人的现实工作模式

问题 1:忽视认知负担管理

两个工具都假设人能理解并遵循复杂流程、维护大量结构化文档、记住所有规范和约束。

但现实是:土办法最管用。工具应该适配人的工作模式,而不是强行改变它。

问题 2:忽视”执行过程”的价值

只关注”规范”和”结果”,忽视”过程”中的知识价值。

问题 3:忽视复利效应的关键性

1
2
3
4
5
传统工具:帮你"做事"
复合工程:帮你"越做越快"

传统工具:每次都是新的开始
AI 工程化:每次都站在上次的肩膀上

问题 4:Spec 详细程度的悖论

规范驱动开发有一个根本性的矛盾:

1
2
Spec 越详细 → 越接近代码本身 → 维护两份"代码"
Spec 越简略 → 越难指导 AI → 失去规范的意义

详细 Spec 的问题

  • 当 Spec 详细到可以精确指导 AI 生成代码时,它本身就变成了另一种形式的”代码”
  • 你需要同时维护 Spec 和 Code 两套产物,且要保持同步
  • 代码改了 Spec 要改,Spec 改了代码要改——双倍维护成本

AI 工程化的解法:不追求详细 Spec,而是分层概要 + 代码指针

1
2
3
4
5
6
7
8
9
10
11
12
AI 工程化的上下文组织:
├── 服务概要:这个服务做什么、边界在哪
├── 业务概要:核心业务流程、关键概念
├── 模块概要:模块职责、依赖关系
├── 接口概要:对外接口、调用方式
└── 代码指针:具体细节在 xxx/xxx.go 的 xxx 函数

不维护:
├── ❌ 详细的数据结构定义(代码里有)
├── ❌ 完整的接口参数说明(代码里有)
├── ❌ 具体的实现逻辑描述(代码里有)
└── ❌ 任何可以从代码直接获取的信息

核心原则:概要层帮助 AI 快速定位,细节层直接读代码。避免维护一份”像代码一样详细的 Spec 文档”——那只是换了个格式的代码,没有降低复杂度,反而增加了同步成本。


进阶能力:插件、Skill、MCP 的融合

对于大多数研发同学来说,可能还停留在 speckit、openspec 这类规范驱动工具的认知上。但 AI 工程化把更多能力融合在了一起:

Skill:可复用的能力单元

Skill 是过程执行层的基本单元,每个 Skill 负责一个具体的固定流程任务:

1
2
3
4
5
6
7
.codebuddy/skills/
├── req-create/ # 需求创建
│ ├── SKILL.md # 技能定义
│ └── templates/ # 模板资源
├── design-create/ # 方案创建
├── workspace-setup/ # 环境搭建
└── code-commit/ # 代码提交

Skill 的特点:

  • 单一职责:每个 Skill 只做一件事
  • 可复用:多个流程可以调用同一个 Skill
  • 可组合:复杂流程由多个 Skill 组合完成
  • 可演进:Skill 可以独立升级,不影响其他部分

Agent:自主决策层

Agent 负责意图识别、流程路由、上下文管理:

1
2
3
4
5
6
.codebuddy/agents/
├── phase-router.md # 阶段路由,意图识别
├── requirement-manager.md # 需求全生命周期管理
├── design-manager.md # 方案全生命周期管理
├── implementation-executor.md # 开发实施执行
└── experience-depositor.md # 经验沉淀(独立上下文)

Agent 与 Skill 的分工:

  • Agent:决定”做什么”
  • Skill:执行”怎么做”

多 Agent 协作:从上下文窗口爆满到高效分工

在实践 AI 工程化的过程中,我们遇到了一个关键瓶颈:上下文窗口爆满

问题的根源

早期使用 speckit 等工具时,最痛苦的体验是:

1
2
3
4
5
6
7
8
执行复杂需求时:
├── 加载业务背景(5000 tokens)
├── 加载技术上下文(8000 tokens)
├── 加载历史经验(3000 tokens)
├── 当前对话记录(持续增长)
└── ...

窗口频繁爆满 → 强制截断 → 丢失关键上下文 → AI 行为异常

Anthropic 工程团队精准描述了这个问题:

“想象一个软件项目由轮班工程师负责,每个新工程师到来时对上一班发生的事情毫无记忆。”

解决方案:Subagent 架构

借鉴 Anthropic 的双 Agent 架构思想,我们设计了 主 Agent + Subagent 的协作模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
传统模式(单一 Agent):
用户输入 → 一个大 Agent 处理所有事情 → 上下文持续膨胀 → 窗口爆满 → 任务失败

Subagent 模式:
用户输入 → 主 Agent(决策层)

意图识别 + 任务拆分

┌─────────┼─────────┐
↓ ↓ ↓
Subagent1 Subagent2 Subagent3
(独立窗口) (独立窗口) (独立窗口)
└─────────┼─────────┘

结果汇总 → 主 Agent 继续

核心优势

特性 说明
独立上下文窗口 每个 Subagent 有自己的上下文空间,不会互相污染
专注单一任务 每个 Subagent 只处理一件事,认知负担小
并行执行 多个 Subagent 可以同时工作,提升效率
结构化状态传递 通过文件传递结果,而非依赖”记忆”

效果对比

指标 单 Agent 模式 Subagent 模式
窗口爆满频率 70%(复杂需求几乎必爆) 5%(偶发于极端场景)
任务完成率 60%(经常中途失败) 95%(可靠完成)
上下文利用效率 30%(大量冗余信息) 80%(按需加载)

状态传递机制

Subagent 之间不共享上下文窗口,通过结构化状态文件保证信息传递:

1
2
3
4
5
6
7
8
9
核心文件:
├── requirements/INDEX.md # 需求状态索引
├── requirements/in-progress/ # 进行中的需求详情
└── context/session/ # 会话级临时上下文

工作流程:
1. Subagent 启动时:读取状态文件,快速理解当前状态
2. Subagent 执行中:专注自己的任务
3. Subagent 结束时:更新状态文件,提交"干净的交接"

核心原则:每个 Subagent 只完成一个”原子任务”,不是一个工程师连续工作 48 小时,而是轮班工程师每人 4 小时但交接清晰。

与 speckit 的本质差异

1
2
3
4
5
6
7
speckit:依赖"一个 Agent 记住所有事情"
Constitution → Specify → Plan → Tasks → Implement
上下文持续累积,到 Implement 阶段时窗口已经很满

Subagent:依赖"结构化的状态传递"
每个阶段独立的 Subagent,独立的上下文窗口
状态通过文件传递,而非上下文累积

前者是人脑模型(记忆有限),后者是团队协作模型(交接清晰)。

MCP:外部系统集成

MCP(Model Context Protocol)让 AI 能够直接对接外部系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
基础集成:
├── TAPD MCP(需求管理)
│ ├── 自动获取需求详情
│ ├── 关联相关需求
│ └── 更新需求状态
├── 工蜂 MCP(代码管理)
│ ├── 自动创建分支
│ ├── 提交代码变更
│ └── 创建合并请求
└── iWiki MCP(知识管理)
├── 检索历史技术方案
├── 获取业务背景文档
└── 关联团队知识库

MCP 的价值:

  • 自动化操作:不需要人手动操作 TAPD、工蜂、iWiki
  • 信息同步:AI 自动获取最新信息
  • 减少错误:避免手动操作的遗漏和错误

插件市场:能力的分发与升级

工具包以插件形式发布到插件市场:

  • 版本管理:每个版本独立,可回滚
  • 灵活升级:update 即可获得最新能力
  • 团队共享:团队成员共享同一套能力集

与脚手架的配合:

  • 脚手架存放业务上下文(随项目迭代)
  • 工具包提供通用能力(独立版本管理)

落地策略:从零到一的实践路径

前面各节从理论角度阐述了 AI 工程化的设计,本节聚焦具体怎么落地。以 2.5 节提到的”商品发放”场景为例,展示完整的实践路径。

冷启动:新项目接入

冷启动是 AI 工程化的核心优势之一。传统工具的知识在人脑,需要传授;AI 工程化的知识在工具链里,开箱即用。

步骤 1:安装 AgentProjectKit 插件(5 分钟)

首先需要添加插件市场并安装 AgentProjectKit:

1
2
3
4
5
# 安装 AgentProjectKit 插件
/plugin install agent-project-kit@tmap-codebuddy-plugin

# 验证安装
/plugin list

步骤 2:脚手架初始化(15 分钟)

1
2
# 初始化 AI 工程项目
/agent-project-kit:init-project

命令会自动完成:

  1. 克隆 AI 工程项目模板
  2. 引导配置项目基本信息(业务线名称、定位等)
  3. 初始化 AGENTS.md 项目记忆文件

步骤 3:加载服务上下文(30 分钟)

这是冷启动的关键步骤。/agent-project-kit:load-service 命令实现项目级别长期记忆初始化

1
2
3
4
# 加载相关服务,生成技术总结
/agent-project-kit:load-service
/agent-project-kit:load-service
/agent-project-kit:load-service

/agent-project-kit:load-service 的工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
用户执行 /agent-project-kit:load-service 

1. 克隆服务代码到 workspace/loadservice/ 目录

2. 分析服务架构、业务逻辑、API 接口:
- 业务定位、核心职责、技术栈
- 依赖关系、对外接口、数据模型
- 关键模块、配置要点、常见坑点

3. 生成技术文档到 context/tech/services/ 目录

结果:AI 获得该服务的完整上下文,后续任何涉及该服务的需求
都会自动加载这份上下文

为什么这很重要?

  • speckit/openspec:每次需要描述服务背景时,依赖人记住并手动描述
  • AI 工程化:一次 /agent-project-kit:load-service,永久复用,新成员也能立即获得”老兵视角”

步骤 4:开始需求研发

使用 /req-dev 命令开始你的第一个需求:

1
2
3
4
5
# 创建新需求
/req-dev 实现用户认证功能

# 或者指定已有需求继续工作
/req-dev REQ-001

工具包自带常用研发工具集成(MCP),开箱即用:

MCP 集成 功能 传统方式
TAPD MCP 自动获取需求详情、关联需求、更新状态 手动复制粘贴需求内容
工蜂 MCP 自动创建分支、提交代码、创建 MR 手动操作 Git 命令
iWiki MCP 检索历史技术方案、业务背景文档、团队知识库 手动搜索翻阅 Wiki 页面

MCP 集成的价值

  • 不是”又多了几个工具要学”,而是”AI 自动帮你操作这些系统”
  • 需求来了 → AI 自动从 TAPD 拉取详情 → 自动检索 iWiki 历史方案 → 自动生成方案
  • 人只需要 review 和确认

冷启动效果对比

阶段 speckit/openspec AI 工程化
学习工具 1-2 小时 5 分钟(插件安装)
初始化项目 手动搭建 15 分钟(/agent-project-kit:init-project)
了解服务架构 2-4 小时(需老人讲解) 30 分钟(/agent-project-kit:load-service 自动分析)
准备总计 4-7 小时 50 分钟
首次工作质量 不稳定(依赖记忆和传授) 稳定(context/ 提供完整信息)

关键差异

  • speckit/openspec:工具是”空壳”,知识在人脑,需要传授
  • AI 工程化:工具包含”知识”(context/+MCP),新人第一天就能高质量工作

持续迭代:知识的复利沉淀

第 1 个需求:建立 context/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
需求:实现 12 月活动的商品发放

执行过程中发现问题:
- Apollo 配置有特殊格式要求
- 雅典娜 20 种商品类型,配置方式各不同
- 钱包选择要区分虚拟/实物
- 敏感 接口有合规要求

知识沉淀:
人:"@agent,记住这些坑"

自动生成/更新 context/:
├── context/tech/Apollo配置规范.md
├── context/experience/雅典娜配置注意事项.md
├── context/experience/商品发放历史问题.md
└── context/business/跨团队协作.md

耗时:45 分钟(首次建立)

第 2 个需求:复用 context/

1
2
3
4
5
6
7
8
9
10
11
12
需求:实现春节活动的商品发放(类似场景)

AI 自动加载 context/,自动提醒历史坑点
人 review:"嗯,都考虑到了" ✓

新发现:春节活动需要限制地域

"@agent,记住地域限制"

context/ 自动更新

耗时:15 分钟(大量复用,少量新增)

第 6-10 个需求:封装为 skill

1
2
3
4
5
6
7
8
9
10
11
12
当 context/ 足够完善,封装为能力层:

skill/product-distribution-helper:
- 自动加载所有商品发放相关 context/
- 自动校验 Apollo 配置格式
- 自动检查雅典娜商品类型
- 自动提醒钱包选择、地域限制
- 自动生成监控配置

使用:/implement-product-distribution → 一键完成

耗时:3 分钟(高度自动化)

团队协作:知识的共享与传承

新成员第一天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
speckit/openspec:
1. 学习工具用法(1-2 小时)
2. 了解服务架构(需老人讲解,2-4 小时)
3. 熟悉流程规范(1 小时)
4. 开始工作:依赖记忆和老人传授,首次质量不稳定
总计:4-7 小时准备 + 不稳定的首次质量

AI 工程化:
1. 脚手架初始化(15 分钟)
2. 工具包安装(5 分钟)
3. 立即开始工作:
- context/ 提供服务上下文
- MCP 自动集成 TAPD/工蜂/Apollo
- cmd/skill 引导完成任务
- 首次就能高质量完成
总计:20 分钟准备 + 稳定的首次质量

团队效应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
5 人团队,各做 2 次商品发放:

speckit:5 人 × 2 次 × 45 分钟 = 450 分钟

AI 工程化:
第 1 人第 1 次:45 分钟 → context/ 建立
第 1 人第 2 次:15 分钟
第 2 人第 1 次:15 分钟(复用第 1 人 context/)
第 2 人第 2 次:10 分钟
...
第 5 人第 2 次:5 分钟

总计:126 分钟
节省:450 - 126 = 324 分钟(72%)

未来展望:工具终将消失

第 4.2 节讨论了极简主义如何影响当前设计,本节从行业发展趋势角度展望工具的演进方向。

模型吞噬脚手架

随着模型能力的提升,很多外部辅助会被模型内化:

1
2
3
4
5
Opus 4.1 需要的东西,Sonnet 4.5 已经内化了

系统提示可以删 2000 个 tokens

工具每周都在变简单

这意味着什么? 今天我们在 context/、Skill、Agent 中编码的知识和流程,未来可能直接被模型”学会”。AI 工程化的架构设计需要为这种迁移做好准备——当某个 Skill 不再需要时,能够平滑删除而不影响整体。

多 Agent 架构的演进方向

从”工具调用”到”团队协作”

当前的 AI 辅助编程主要是”人调用 AI”模式:

1
人 → 发指令 → AI 执行 → 人检查 → 人发下一个指令

Subagent 架构开启了新的可能:

1
人 → 设定目标 → 主 Agent 拆解 → 多个 Subagent 协作 → 主 Agent 汇总 → 人验收

未来可能演进为:

1
人 → 设定目标 → Agent 团队自主协作数小时/数天 → 人验收最终结果

长时间运行 Agent 的关键挑战

Anthropic 的实践揭示了几个核心挑战:

挑战 当前解法 未来方向
上下文窗口限制 Subagent 分解 + 状态文件传递 更高效的 compaction + 更智能的上下文选择
任务连续性 结构化状态文件(JSON/Markdown) 更丰富的”工作记忆”机制
质量保证 端到端测试 + 人工 Review 专门的 QA Agent + 自动化验收
错误恢复 状态文件支持断点续做 更智能的错误分析和自动修复

Agent 专门化 vs 通用化的权衡

一个开放问题:应该用一个强大的通用 Agent,还是多个专门化的 Agent?

1
2
3
4
5
6
7
8
9
通用 Agent 路线:
├── 优势:简单,不需要协调
├── 劣势:上下文负担重,需要"知道所有事情"
└── 适合:简单任务、短时间任务

专门化 Agent 路线:
├── 优势:每个 Agent 更专精,上下文更精简
├── 劣势:需要协调机制,状态传递成本
└── 适合:复杂任务、长时间任务、团队协作场景

我们的选择:对于企业级复杂场景,专门化 Agent 更适合。原因是:

  1. 企业场景本身就是”团队协作”,Agent 架构应该反映这一现实
  2. 上下文窗口是硬约束,专门化可以更高效利用
  3. 专门化 Agent 更容易独立迭代和优化

与人类团队的类比

最好的 Agent 架构设计,灵感来自人类高效团队的工作方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
人类团队:
├── 产品经理:理解需求、拆解任务
├── 技术 Leader:设计方案、分配工作
├── 开发工程师:实现功能
├── 测试工程师:验证质量
└── 每个人有自己的专业领域,通过"会议"和"文档"协调

Agent 团队:
├── phase-router:理解意图、路由任务
├── design-manager:设计方案
├── implementation-executor:实现功能
├── test-agent(计划中):验证质量
└── 每个 Agent 有自己的专业 Prompt,通过"状态文件"协调

Anthropic 工程团队的洞察:”这些实践的灵感来自于了解高效软件工程师每天做什么。”

当前范式:Claude 做一步,你检查,批准,它继续。

未来范式:

1
2
3
4
5
当模型可以自主工作几天甚至几周:
早上:"我想完成 X"
晚上:看结果

中间的过程?它自己处理。

人的角色从”操作者”变成”监督者”,从”指令发出者”变成”目标设定者”。

AI 工程化的定位:在这个转型过程中,AI 工程化是”过渡期基础设施”——帮助团队在当前阶段高效工作,同时为未来的全自动化积累知识和经验。

研发工作的本质变化

AI 工程化不只是引入新工具,而是重新定义了研发的工作方式。这种变化已经在 AI 技术最前沿的团队中发生。

首先要避免的认知误区

工程师在使用 AI 时最常见的两种误解:

误区 表现 结果
AI 是”银弹” 期望 AI 自动理解需求、写出完美代码 过度依赖,缺乏监督,质量不稳定
AI 是”思考替代品” 把 AI 当作可以替代人类思考的工具 不理解业务,一直捣鼓 AI,适得其反

正确的定位是:AI 是强大的执行工具,但决策权和判断力必须留在人手中。

来自 OpenAI 与 Anthropic 的实践经验

理解 AI 的真实能力边界

参考 OpenAI 团队使用 Codex 构建 Sora 安卓应用的经验,将 AI 定位为**”一位新入职的资深工程师”**:

需要人类指导 表现卓越
无法推断隐性上下文(团队偏好、内部规范) 快速理解大型代码库,精通主流编程语言
缺乏真实用户体感(无法感知”滚动不顺畅”) 热衷于编写单元测试,能根据 CI 日志修复问题
深层架构判断力不足(本能是”让功能跑起来”) 支持大规模并行,同时探索多种方案

三步协作工作流(借鉴 OpenAI 与 Anthropic 经验):

阶段 人的职责 AI 的职责
奠定基石 定义架构、编写范例代码、设定标准 学习并遵循
共同规划 校准理解、确认方案 总结现状、生成设计文档
执行交付 架构把关、质量审查 编码实现、测试修复

Anthropic 内部调查数据(2025年8月,132名工程师,20万条使用记录):

  • 工程师在 60% 的工作中使用 AI,实现 50% 的生产力提升,年同比增长 2-3 倍
  • 27% 的 AI 辅助工作是原本不会完成的任务(如交互式仪表板、探索性工作)
  • 工程师倾向于委托易于验证、定义明确、代码质量不关键、重复无聊的任务

“我可以非常胜任前端、事务性数据库的工作…而以前我会害怕触碰这些东西。” —— 后端工程师

“我以为我真的很享受编写代码,但实际上我只是享受编写代码带来的结果。” —— 高级工程师

核心理念:寻找 AI 的”舒适区”

工程师的核心工作之一,已经从纯粹的编码转变为识别 AI 的能力边界,并将复杂任务转化为落入 AI “舒适区”内的子任务

  • 低标准、高容错场景:任务对精确度要求不高,容忍多次失败。AI 尝试 N 次只要一次成功,就是显著提效
  • 迭代式开发场景:形成”AI 初步实现 → 人验证修正 → 快速反馈”的闭环,不追求一次完美

工作模式的具体变化

工作内容的迁移

工作环节 传统模式 AI 工程化模式 角色变化
需求理解 反复阅读文档、追问产品 Agent 自动加载 context/,主动提示 信息收集者 → 信息确认者
方案设计 从零构思、翻阅历史代码 基于模板生成,AI 提示已知风险 方案起草者 → 方案审核者
代码实现 逐行编写、查文档、调试 AI 生成初版,人 review 调整 代码生产者 → 代码把关者
知识沉淀 写文档(经常忘记) /optimize-flow 即时沉淀 文档维护者 → 经验触发者

时间分配的重构

1
2
3
4
5
传统研发:                         AI 工程化后:
├── 40% 信息收集 ├── 10% 信息确认
├── 30% 重复劳动 ├── 10% 结果审核
├── 20% 核心决策 → ├── 50% 核心决策
└── 10% 知识沉淀 └── 30% 知识沉淀

一个具体的对比——以”商品发放需求”为例:

1
2
3
4
5
6
7
8
传统模式的一天:                              AI 工程化模式的一天:
09:00-10:30 阅读需求文档,追问产品 09:00-09:30 /req-dev,确认需求边界
10:30-12:00 翻阅历史代码,理解逻辑 09:30-10:30 review AI 方案,调整决策点
14:00-15:30 询问老人"以前怎么做" 10:30-12:00 review AI 代码,优化核心逻辑
15:30-18:00 写代码,边写边查文档 14:00-15:00 AI 辅助测试,修复问题
18:00-19:00 遇到配置问题,排查 15:00-15:30 /optimize-flow 沉淀经验
19:00-20:00 继续写代码 15:30-17:00 处理下一个需求
产出:完成 60%,知识留在脑子里 产出:完成 100%,经验沉淀到 context/

能力要求的升级

能力维度 传统要求 AI 工程化要求
编码能力 熟练编写各类代码 能判断 AI 生成代码的质量和风险
知识储备 记住各种细节和坑点 知道如何组织知识让 AI 能用
问题解决 自己动手排查 会描述问题让 AI 辅助分析
效率提升 写更多代码、加更多班 设计更好的 Skill、沉淀更多经验

新的核心竞争力体现为三种能力:

  1. 系统理解能力:AI 能实现功能,但只有人能判断它是否以正确方式融入系统
  2. AI 协作能力:设计上下文、拆解计划、通过反馈循环持续优化
  3. 设计质量标准:当”写出能工作的代码”门槛降低,架构设计和交付质量成为区分标准

监督悖论:有效使用 AI 需要监督能力,而监督能力可能因过度依赖 AI 而退化。Anthropic 的一些工程师故意在没有 AI 的情况下练习以”保持敏锐”。

本质洞察

黄仁勋有一个精准的判断:**AI 改变的是”任务”,而非”职业”**。

  • 被 AI 接管的任务:信息检索、样板代码、格式化、重复配置
  • 人依然主导的核心:系统设计、架构决策、质量判断、创新突破

AI 工程化的价值,就是让这种”任务迁移”在团队中系统化落地——通过 context/ 让信息检索自动化,通过 Skill 让重复流程标准化,通过经验沉淀让知识持续复利。

最终目标:让研发把时间花在”只有人能做的事”上,而不是”AI 也能做的事”上。

工具隐形化:从”使用工具”到”完成工作”

工具消失的含义:不是工具不存在了,而是工具变得如此无缝,你感受不到它的存在。

1
2
就像现在你用搜索引擎,不会想"我在使用一个信息检索系统"。
你只是在找答案。工具隐形了。

隐形化的三个层次

层次一:操作隐形——从”记住命令”到”表达意图”

1
2
3
4
5
6
7
8
9
10
过去:记住 20 个命令,选择正确的那个
├── /speckit.constitution
├── /speckit.specify
├── /speckit.plan
├── /speckit.tasks
└── ...

现在:只说你要什么
├── "/req-dev 实现商品发放" → Agent 自动判断是创建还是继续
└── 不需要知道底层调用了哪些 Skill

层次二:知识隐形——从”想起经验”到”系统提醒”

1
2
3
4
5
6
7
8
9
过去:做需求时,人要想起历史上有什么坑
├── "上次商品发放好像有个钱包问题..."
├── "Apollo 配置格式是什么来着..."
└── 认知负担在人身上

现在:experience-index 自动检索,主动提醒
├── "检测到商品发放场景,已加载相关经验..."
├── "风险提示:注意钱包类型匹配"
└── 知识在系统里,人只需确认

层次三:流程隐形——从”遵循步骤”到”自然完成”

1
2
3
4
5
6
7
8
9
过去:严格按 Constitution → Specify → Plan → Tasks → Implement 执行
├── 人要知道"现在该执行哪个阶段"
├── 人要判断"前置条件是否满足"
└── 流程感知在人身上

现在:Agent 自主决策流程路由
├── 人说"继续做 REQ-001"
├── phase-router 自动判断当前阶段和下一步
└── 人感受到的是"工作在推进",而非"在执行流程"

AI 工程化的隐形化进度

维度 当前状态 目标状态
命令入口 ✅ 2 个命令覆盖全流程 自然语言直接触发
上下文加载 ✅ experience-index 自动检索 完全无感知加载
阶段流转 ✅ phase-router 自动路由 Agent 自主推进多步
经验沉淀 🔄 需要 /optimize-flow 触发 自动识别并沉淀
跨会话连续性 🔄 依赖状态文件 无缝断点续做

隐形化的终极形态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
今天:
人:"我要做一个商品发放需求"
AI:执行一步,等待确认
人:确认,继续
AI:执行下一步,等待确认
...

明天:
人:"我要做一个商品发放需求"
AI:分析、设计、实现、测试、提交 PR
人:Review 最终结果

后天:
人:(在 TAPD 创建需求单)
AI:(自动感知、自动完成、自动提交 Review)
人:(只在关键决策点介入)

最后一步:你不再”使用”工具,你只是在思考业务问题,而工具已经把代码写好了。

写在最后:从第一性原理出发

回顾这段历程,我最大的收获是:不要为了用工具而用工具

speckit 和 openspec 都是优秀的工具,它们定义的流程、模板、检查清单都很有价值。但正如 2.5 节(AI 工程化如何破局)的对比所示,它们解决的是”规范化”问题,而企业真实场景的核心问题是:

  1. 上下文缺失:AI 看不到历史经验、业务边界、配置规范
  2. 知识不沉淀:每次都从头开始,边际成本恒定
  3. 范围太窄:只管单个仓库,无法覆盖跨服务、跨系统的复杂场景

AI 工程化试图解决这些问题:

1
2
3
上下文工程 → 让 AI 自动获取完整信息
复合工程 → 让每次实践都降低下次成本
项目级方案 → 管理所有仓库和外部系统

核心思路

1
2
3
4
5
6
7
8
9
能够落地的最高效流程 → 已存在于高效的人的行为过程中

把高效流程 AI 化 → 推广到全团队应用

细节流程在具体业务线中迭代 → 自定义探索

实践中发现问题 → 提取可复用信息 → AI 工程化融入工具

下次通用场景使用时可复用

最后想说的是

AI 工程化不是要替代 speckit 或 openspec,而是在它们的基础上,融合上下文工程、复合工程、插件市场、MCP 集成等能力,形成一套更适合企业复杂场景的解决方案。

如果你也在探索 AI 辅助研发,希望这篇文章能给你一些启发:

  1. 从真实工作场景出发,而不是从工具出发
  2. 把知识编码进工具,而不是只写文档
  3. 追求边际成本递减,而不是固定成本
  4. 让工具适配人,而不是让人适配工具

工具的终极形态是消失。在那一天到来之前,我们要做的是让工具越来越”懂”我们的工作,越来越”记得”我们的经验,越来越”自然”地融入我们的日常。

这就是 AI 工程化的意义所在。

参考资料

我用上了央视同款的 AI 同传耳机,发现它比「翻译」强多了|CES 2026

作者 苏伟鸿
2026年1月8日 17:00

在 2026 年的 CES 全球消费电子展上,AI 硬件无疑是不可忽视的一支——

小至能根据指令作画的 AI 画框,大到能叠衣服的家务机器人……AI 已经无处不在。

这些产品大多概念强于功能,外观吸睛但定位模糊。不过,CES 也总有一些其貌不扬却足以颠覆行业格局的产品。譬如这款来自深圳厂商「时空壶」的 W4 系列耳机 ,即便是放在众多 AI 耳机当中,也是独树一帜的存在。

它没有带摄像头,不能唤起 Chat-GPT,当然也无法回答你的问题,却是我在 CES 上用得最多、最满意的产品。

这副耳机只做好一件事:翻译,更精准地讲,是「同传」。

我原本觉得,Google 翻译已经足够好用,何必多此一举?

但戴上 W4 之后,我发现它根本就不是一个「同传耳机」。

「沟通」而非「翻译」

时空壶创始人田力告诉 APPSO,他们做的不是一个「AI 翻译」耳机,而是一个帮助「沟通」的助手。

这两者看似没有区别,又泾渭分明:翻译是中介的行为,架起「桥梁」;而沟通则是河流本身,天生畅通无阻。

▲ 左:时空壶创始人田力

在田力眼中,除了准确率,还应该关注两人之间交流是否自然,愿不愿意让人在一种很舒服的状态上沟通,这些都是传统的翻译方案所忽略的地方。

时空壶的核心理念,就是要将技术「隐形」,让跨语言交流也能丝滑无碍。

时空壶 W4 的功能,可以简单分成两种类型:单向同传和双向同传;同时耳机也有两种翻译模式:

  • 聚焦翻译速度的「快速」
  • 聚焦语境理解的「准确」

「现场收听」是单向同传:用户戴上耳机,用手机当麦克风,即时听到对方讲话的译文。

这种模式最适合发布会、演讲或课堂等场景,即使面对不同口音,W4 的「快速」模式也能在约 2 秒内准确传达语义。

我在 CES 期间逛展的时候,就高强度开着这个功能,能够很方便地听到周边的展台在宣讲什么内容。在仔细逛展之前,现场收音的中文同传就能帮我快速了解情况,很多时候我不需要费尽心思地听讲,就能知道旁边在展出什么,该不该去逛逛,大大提升了逛展的效率。

当然,市面上大部分手机翻译应用虽然体验没有那么自然,但也都能实现不错的大语种单向翻译。时空壶的「英雄场景」,当然是「双人对话」的双向同传。

游戏《赛博朋克 2077》里有个小细节我很喜欢:来自世界各地的人聚在同一座城市,各说各的语言,每个人体内都有「翻译器」,能直接把对方的话实时译成母语。你在游戏中,经常能看到某个人说着一门看不懂听不懂的语言,然后字幕闪烁的一瞬间,变成了中文。

时空壶 W4 带来的,也是类似的体验:

把耳机分成两半,手机上选好语种,双方佩戴后各说各的语言,耳机即可实时播放对应译文,延迟很小,只有两三秒。

传统翻译软件需要等一句话说完才能翻译,而且只能在屏幕上查看,节奏不自然,更多适合旅游等轻度场景。

而时空壶 W4 系列最有价值的地方,就是让跨语言交流,从「低头看屏幕」变成了「抬头听对话」。

以往用母语和翻译交流,往往避免不了「母语羞耻」,时空壶 W4 却消除了这种感觉:

戴上耳机,就能让对话更自然流畅,双方都不被工具束缚,只专注于交流本身,我更自信、也更愿意表达。

如此一来,我们往往更愿意把话讲深,因为你不需要再担心对方听不懂。

和传统翻译软件需要等一句话说完再处理不同,时空壶 W4会在说话过程中识别相对完整的语义片段,边说边翻译,延迟被控制在 3 秒以内。

在使用时空壶 W4 时,往往是话音刚落,对方也快听完了译文,大幅减少了尴尬的等待,交流节奏更连贯。如果没听清语音播报的译文,随时也能看屏幕参考。

这还不是时空壶的极限,切换到「精准」模式后,它能识别并翻译医疗、商务、法律等专业术语。

比如我和在英国留学、人工智能专业的 AI 编辑同事聊天,她的英语语速很快,内容里有大量专业术语,但 W4 都能准确翻译——如果没有时空壶,这类对话即便是我听得懂英语,也很难跟上她的节奏,更别说真正理解所有专业内容了。

我曾以为手机翻译已足够好,翻译效果早已将「机翻味」尽数褪去,但时空壶的同传耳机,依然让我意识到,跨语种沟通的体验,不该停留在「翻译」,而应是「沟通」。

我们常说翻译的精髓在于「信达雅」,有了对比我才发现「信达雅」之间也有高低快慢之分。

一旦有差距,创新就有了空间。

定位和技术是护城河

其实早在生成式 AI 浪潮之前,「翻译机」已经存在,但它是一个非常垂直和隐形的领域,并且在大众认知中,它理应被智能手机取代。

正是定位为「沟通助手」而非「翻译工具」,时空壶更自然的对话体验,使其迅速脱颖而出,形成了不同于竞品的优势。

其他翻译机,本质上是双方对着屏幕说话,同时还要迁就机器的限制;时空壶真正去掉了交际之间的「屏幕」,终于能够面对面交流,语速快慢、口音差别、长短句子都能说。

截止 2025 年底,时空壶已经在全球获得了 150 多万用户,覆盖 170 多个国家,还是亚马逊相关品类的销量第一。

在这种产品更流行的北美地区,市占率也超过 70%,这些实打实的数据证明,尽管是扎根于小众赛道,时空壶却已经有了「破圈」的势头。

进入生成式 AI 时代, OpenAI、字节跳动等大厂发力 AI 耳机,苹果、华为等传统耳机厂商也在借助 AI 重塑产品,实时翻译则是一个不应该错过的场景,毕竟它实现难度不高,宣传效果却很好。

这时压力就给到了时空壶,作为一家更垂直的「小厂」,他们突围的方式很简单,却没有捷径——将技术作为护城河。

只有复杂的技术,才能带来简洁的体验。

时空壶 CEO 田力认为,「跨语言沟通」是一个很长的技术链条,需要详细进行拆解:通信、降噪、唤醒、语音的压缩、断句、识别,其实看起来最重要的翻译,反而是最后一个环节,因为翻译的质量,同样取决于最基础的收音。

时空壶 W4 耳机搭载了一种名为「骨声纹」的技术,在主流 TWS 市场相当少见:

有点类似骨传导技术,时空壶 W4 上通过捕捉说话时颅骨传导的声纹信号来拾音,并非取代麦克风,而是多引入一个信号源,辅助耳机在在嘈杂环境增强区分人声和背景音。

像 CES 这样人多口杂的环境,骨声纹技术能够有效提取出对话双方的声音,进而提供更为确凿的翻译效果,这尤为难得可贵。

早在 6 年前,时空壶也针对耳机的蓝牙通道进行魔改,同时开发了自己的人声识别算法,就是为了实现在各种嘈杂环境都能准确收音、传输。

在翻译技术方面,田力的目标很简单:不管是用第三方,还是自研,都必须要让用户感受到「好用」。

在比对过 Google、微软的翻译引擎之后,时空壶发现要实现目标的体验,必须自己改造模型,Babel OS 应运而生,专注于 AI 同传的「语义切分」和「语境感知」,增强了大模型不擅长的小语种能力,还自建了「精准」模式用到的专业术语库。

我在现场体验过的这副 W4 耳机,就支持超过 40 种语言,其中不乏泰卢固语、希伯来语这样的小语种,还覆盖了诸如印度英语、中文粤语这样的大语种分支,在真正的商务、专业场合,细分需求往往是关键的胜负手。

可以看出,凭借软件和硬件技术的积累,以及产品定义上的差距,时空壶已经形成了足够稳固的护城河,在有强专业需求的 to B 领域更是如此。

巨头们做产品的逻辑是「加法」,为现有产品叠加功能,为产品找到 AI 落地的空间。

时空壶更像是为「跨语言交流」这个场景,重新写了一个解法的算式,田力认为这也是时空壶和大公司产品的主要差别。

我们逻辑是,我们其实不在乎它是不是耳机,也不在乎它是不是 AI 的热点,我们在乎的是那个场景,那个问题有没有被真正的解决。

我们的逻辑是,它是一个可以帮助人们沉浸式交流的一个穿戴设备,只是耳机这个形态刚好能满足。

时空壶 W4 不适合拿来听歌,外观也迥异于传统 TWS 耳机,但正是这种对形态和功能的改造,让它更适合「沟通」

田力觉得,用户不是靠「说服」,而是被「打动」的,时空壶完美契合了用户「沟通」的需要,那就会逐渐形成用户对品牌清晰的认知。

在 CES 上,一位美国的本土用户告诉田力,他现在每天都在用时空壶耳机,与他跨国的妻子聊一两个小时的天。其实为了解决夫妻两人的沟通问题,他尝试过非常多的翻译软件和产品,但效果都不尽如人意,直到遇见了时空壶,帮他真正地解决了跨语言沟通的问题。

▲ 对比传统耳机,时空壶 W4 会长很多

我们几乎能下定论,即便未来大厂大举进军 AI 实时翻译,每个耳机、手机都塞进了翻译大模型,时空壶这样的厂商依然能够凭借精准定位和长期技术积累,在专业领域依然占据优势,也拥有更大的创新空间。

专注于做专业的事情,这是当下能挖到第一桶金的 AI 创业公司们,最早达成一致的共识。

当下最好的「AI 硬件」

在 2016 年创业之初,时空壶并没有将自己视为一种「AI 硬件」,更多是一种瞄准专门场景的解决方案。

但 APPSO 认为,时空壶 W4,就是一个 AI 硬件应有的模样。

时空壶的发展仍在继续,W4 在便利性和准确性上还有提升空间。时空壶 CEO 田力参照自动驾驶的定级,将 AI 翻译分为 L1–L5 等级。

在他看来,目前时空壶 W4 翻译耳机约处于 L3 水平,「可用」但非「好用」。

未来的 L4 / L5 等级,将具备同理心、能完全承担跨语言交际的任务。

进入 2026 年,首批 AI 硬件产品已经陆续交卷完毕,大量在发布会上阐述未来图景的产品,已经前仆后继折戟沉沙,没有太多例外。

如果说很多初创企业是「射了箭再画靶」,是带着 AI 去找需要解决的问题,那时空壶就是先找到了「跨语种沟通」这个靶心,AI 大语言模型只是射出的利箭。

而他们箭筒里的箭,可不止一支。

其实早在几年前,时空壶这套解决方案,就已经被不少大品牌所验证:

2023 年,时空壶与万豪、希尔顿等酒店品牌达成合作,提供耳机租赁服务;

2025 年,在深圳全球招商大会上,时空壶也成为了官方指定 AI 同传服务,为超过 300 个项目推介提供翻译支持;

你甚至能在新闻联播上,也能看到对时空壶这家公司长达 4 分钟的采访,他们也只讲透一件事。

属于 AI 的宏大叙事还在继续,但对「人」的关注可能还要更多一些——先不要谈那些天花乱坠的交互和未来,告诉我现在 AI 能为我们产生哪些价值吧?

创业刚刚迈入十周年的时空壶,选择做未来不变的东西。在采访时,这位年轻的 CEO 始终对事业的前景深信不疑:

口译其实是个数百亿美元规模的市场,但请得起口译的人凤毛麟角,这还只是冰山之上的一个小角,潜藏在冰山下面的需求巨大。

人和人交流的需求总是不变的,不管是硬件、软件,还是什么 AR、VR 或各种 AI 技术,你还是会希望在跟一个创业者交流时,两个人是像我们这样坐在会议室聊天。

这件事现在是这样,十年前是这样,五十年前这样,一百年前也这样。对我们来说,我们关注的就是这个不变的问题,有没有得到解决。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


干翻 ChatGPT,市值超苹果,这就是 AI 圈最大爽文

作者 莫崇宇
2026年1月8日 16:56

今天上午,智谱 以「全球大模型第一股」的身份登陆港交所主板,而几乎前后脚,远在大洋彼岸的 Google 母公司 Alphabet 股价逆市上涨 2.5%,市值攀升至 3.89 万亿美元,这是自 2019 年以来,其市值首次超过苹果。

两件看似独立的事,背后却指向同一个趋势:AI 带来的想象力,终究要回归到商业价值的验证上。其中,Google 的市值反超尤为值得关注——仅用三年时间完成了在 AI 赛道上的逆风翻盘。

究其原因,根据外媒多篇报道,我们也可以拆解出 Google 的 AI 三板斧:技术打底、资源合并、商业落地。三步环环相扣,构成了 Google AI 战略的完整闭环。

三年磨一剑,Google AI 实现逆风翻盘

Google 真正的转机,始于一次关键的内部资源整合。而这一切的基础,早在十多年前就已埋下伏笔。

2011 年,Google 成立了 Google Brain 研究部门,由计算机科学家杰夫·迪恩(Jeff Dean)联合创办。这个部门研发的神经网络技术,后来成为所有大型语言模型的核心底层支撑,为 Google 的 AI 布局打下了坚实的技术基础。

数年后,Google 收购了位于伦敦的 AI 研究实验室 DeepMind。该实验室由国际象棋神童德米斯·哈萨比斯(Demis Hassabis)联合创立,其开发的 AI 系统在生物分子研究领域取得重大突破,哈萨比斯也因此在 2025 年获得诺贝尔奖,这进一步强化了 Google 在 AI 基础研究领域的优势。

2023 年初,杰夫·迪恩与德米斯·哈萨比斯牵头,推动 DeepMind 与 Google Brain 合并,并向董事会提交了「打造公司史上最强 AI 模型」的战略计划,明确将多模态技术作为核心发展方向。

此次整合并非一帆风顺,两大团队的文化差异引发了不少摩擦。但 Google 拥有一项 OpenAI 难以比拟的优势:稳定且充沛的利润现金流,能够持续为 AI 研发提供充足的资金支持;而 OpenAI 则高度依赖外部投资,资金链的稳定性相对较弱。

2023 年底,Google 正式发布首款 Gemini 模型,这是其资源整合后的首个核心成果,标志着其多模态 AI 战略进入落地阶段。

与 OpenAI 主打文本训练的 ChatGPT 不同,手握全球充沛数据的 Google,为 Gemini 构建了涵盖文本、代码、音频、图像及视频的多模态训练体系。这一策略虽然延长了开发周期,且首款 Gemini 在多项核心指标上仍落后于 ChatGPT,但却为后续 Gemini 3 系列的反超奠定了基础。

值得一提的是,就在 Google 上下焦头烂额的时候,已经退休的联合创始人谢尔盖·布林,在一次聚会上被 OpenAI 研究员 Daniel Selsam 当面问住了:你为什么不全职投身 AI?ChatGPT 的发布难道没激发你的热情吗?

作为 Google(现 Alphabet)的联合创始人之一,谢尔盖·布林自 2019 年卸任总裁,一直退居幕后,Daniel 的提问让布林深受触动,于是决定重返公司一线。

尽管如今看来有些英雄主义的马后炮,但伴随着布林以「创始人模式」全面回归,Google 也顺势完成了一次「再创业」式的组织调整。

除了直接参与 Gemini 模型的问题排查与技术优化。他还推动 Google 完成了一笔价值 27 亿美元的收购,将 AI 领域顶尖研究员丹尼尔·德·弗雷塔斯(Daniel De Freitas)与 Transformer 论文作者之一诺姆·沙泽尔(Noam Shazeer)纳入麾下。如今,二人已成为 Gemini 项目的核心领导者,大幅提升了团队的技术攻坚能力。

更关键的是,Google 在硬件层面的前瞻布局,构建了「软件+硬件」的全产业链优势,这一点是多数竞争对手难以企及的。

早年间,Google 启动了自主 AI 芯片研发项目。当时团队预判,未来语音识别、图像处理等 AI 应用所需的算力,将远超现有水平。最终研发出的「张量处理单元」(TPU)芯片,不仅算力更强,耗电量还低于传统 CPU 与 GPU,成本优势十分显著。

Google 的这一押注最终得到了回报。TPU 芯片不仅成为 Google AI 业务的核心支撑,更对整个 AI 行业产生了颠覆性影响,逐渐成为行业主流的 AI 算力解决方案之一。其最新推出的 AI 芯片「Ironwood」,进一步降低了 AI 模型的运行成本,直接转化为 Google 在 AI 商业化领域的核心竞争力。

一根神秘香蕉,点燃全民 AI 狂欢

2025 年,图片生成模型领域出现了两次病毒式传播事件:一次是 ChatGPT 的 AI 吉卜力画风走红全网,另一次则是 Google 的 Nano Banana(Gemini 2.5 Flash Image)。后者的意外爆火,彻底扭转了市场对 Google AI 产品竞争力的认知。而这款产品的名字,背后还有一个小插曲。

据报道,当时 Google AI 项目经理娜伊娜·拉伊桑哈尼(Naina Raisinghani)负责将这款模型上传到大模型竞技场 LM Arena 平台。凌晨两点半的办公室里空无一人,她即兴组合了两位朋友给自己的昵称,最终确定了「Nano Banana」这个名字。

这款名字独特的产品,很快引发了市场热潮。短短数天内,Nano Banana 就登顶 LM Arena 平台性能排行榜,在社交网络上广泛传播,其影响力远超 Google 内部的预期。

负责 Gemini 应用及 Google 实验室的乔希·伍德沃德(Josh Woodward),将 Nano Banana 的发布定义为「一场成功的灾难」。全球用户在短时间内生成了数百万甚至数十亿张图像,直接导致 Google 算力告急,最终不得不通过「紧急借用」服务器时间来缓解压力。

但这场算力危机,恰恰印证了 Nano Banana 的核心吸引力。2025 年 9 月,Gemini AI 应用成功登顶苹果 App Store 下载榜——这一马斯克多年梦寐以求的位置,成为 Google AI 业务走向大众市场的重要里程碑。

两个月后,Google 推出了迄今为止最强版本的 Gemini 3 系列模型,其多项核心指标超越 ChatGPT。这一突破直接推动 Alphabet 股价大幅飙升,也让 OpenAI CEO 奥特曼紧急拉响「红色警报」,亲自下令改进 ChatGPT。

尽管 OpenAI 随后推出了更强版本的模型,但从市场反馈来看,其表现并未达到预期,未能逆转与 Gemini 的差距。

根据昨天 SimilarWeb 发布的最新数据显示,2026 年 1 月 Gemini 全球网页端流量份额突破 21.5%;这也是 Gemini 首次突破 20% 的市场份额,而 ChatGPT 的份额从 2025 年 1 月 86% 的统治级水平暴跌至 64.5%。

短短三年间,攻守之势异也。

Google 能够快速巩固优势,另一关键在于其将 AI 技术与核心搜索业务深度整合的能力——这是 OpenAI 等初创 AI 企业难以复制的壁垒。

OpenAI 需要从零开始搭建用户触达渠道,而 Google 则可以直接将 AI 能力注入其市场份额超 90% 的搜索引擎。在流量红利的加持下,Gemini 的用户增长速度远超预期:2025 年 10 月,其月活跃用户数量突破 6.5 亿,较当年 7 月的 4.5 亿增长近 50%。

如今,AI 业务已成为 Google 新的核心增长引擎。其收入结构日趋多元化,涵盖搜索广告增量、面向个人与企业的付费版 Gemini,以及自主研发 AI 芯片的销售等多个板块。

从英伟达市值突破 5 万亿美元,到 Google 凭借 AI 实现市值反超,这些现象都说明 AI 仍是当前科技行业最具想象力的赛道。但赛道上的竞争不仅是技术和产品的比拼,也是人才的争夺——就在 Google 高歌猛进之际,苹果正经历近十年最密集的核心人才「离职潮」。

这场离职潮覆盖了 AI、设计等多个核心业务板块,就连支撑苹果生态重要一环的 Safari 浏览器团队,也未能幸免。

昨天傍晚,The Browser Company 首席执行官乔希·米勒(Josh Miller)便在社交平台高调官宣:苹果 Safari 浏览器首席设计师马可·特里维里奥(Marco Triverio)已正式加盟其团队。

特里维里奥曾长期担任 iOS 与 macOS 版 Safari 的首席设计师,主导了 Safari 隐私控制、标签页交互、导航模式等核心功能的设计与优化。值得注意的是,另一位 Safari 首席设计师 Charlie Deets,也已更早一步加盟 The Browser Company。

当然,若仅因人才流失就看衰苹果,显然低估了这家公司的深厚底蕴。

它手握全球最稳固的硬件生态,iPhone、Mac 搭建的用户护城河,至今仍是多数企业难以企及的;充足的现金流,足以支撑它在 AI 赛道耐心布局、持续投入;更别说苹果在硬件研发、用户体验上的积累,本就是 AI 落地消费端的关键筹码。

如果 Google 的翻盘证明了技术积累的价值,那么苹果的故事,同样还没到盖棺定论的时候。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


刚刚,ChatGPT 发布 AI 医疗功能!能读病历做诊断,支持苹果健康

作者 莫崇宇
2026年1月8日 08:30

2026 年,OpenAI 决定认真入局医疗健康领域了。

就在刚刚,OpenAI 重磅推出了 ChatGPT 健康(ChatGPT Health),能够把你的医疗记录、健康 App、甚至是 Apple 健康数据全给串起来,然后用 AI 帮你看懂那些复杂到头大的体检报告、准备就医问题清单、甚至规划饮食运动。

根据 OpenAI 自己透露的数据,健康咨询已经成了 ChatGPT 最高频的使用场景之一,全球每周有超过 2.3 亿人在上面问健康问题。于是,OpenAI 索性直接做个专门的健康版本。

当然,OpenAI 也强调,ChatGPT 健康只是辅助工具,不能替代医生看病,这话既是「免责金牌」,也是 AI 医疗绕不开的「紧箍咒」,更专业的诊断,还得靠临床医生拍板。

▲ 从 ChatGPT 的侧边栏菜单中选择「健康」

单说数据打通这事,OpenAI 这次是真没糊弄。

ChatGPT Health 最拿得出手的卖点,就是能把你那些散落在各处的健康信息整合起来。

以前看个体检报告,可能要在医院门户网站找半天;想对比下最近的血糖变化,又得翻 Apple 健康的各种图表;要是还想结合运动数据分析,那更是要在好几个 App 之间来回切换。

现在 ChatGPT 健康把这些全给连上了——电子病历通过 b.well 这个美国最大的医疗数据平台接入,Apple 健康、MyFitnessPal 这些健身营养 App 也能直接授权连接。

连好之后就简单了,你就能直接问它「我最近胆固醇有啥变化」或者「明天体检我该问医生什么问题」,它会基于你的实际数据给建议。

除了帮你看懂数据,还能帮你准备就医材料、对比保险方案、甚至制定饮食计划。比如你刚生完孩子想恢复运动,它能结合你的身体状况推荐 Peloton 上的合适课程;要是你在用 GLP-1 减肥药还想增肌,它能直接给你排一周食谱,然后通过 Instacart 生成购物清单。

为了不让 ChatGPT 健康变成「网络赤脚医生」,OpenAI 这次找了来自 60 个国家、数十个专科的 260 多位执业医生参与开发。

这些医生在过去两年里给了超过 60 万次反馈,专门教 AI 什么情况该紧急建议去医院、什么时候可以先观察、怎么把复杂的医学术语说得让人能听懂。

OpenAI 还专门搞了个叫 HealthBench 的评估框架,直接用医生定的评分标准给 AI 打分,重点看三件事:安全不安全、说不说得明白、会不会适时让你去看医生。

当然,健康数据的安全,是所有用户最在乎的底线,因此 ChatGPT 健康直接搞了个独立空间,你的健康对话、连接的 App、上传的文件全都和普通聊天记录隔离存储。

健康对话还有独立记忆,你在健康空间聊的东西不会跑到其他聊天里去。反过来也一样,健康空间访问不了你在普通 ChatGPT 里的对话和文件。

当然也有个小例外:要是需要,ChatGPT 可能会参考你最近聊的生活变化,比如搬家、换工作,让健康建议更贴合你的实际情况,但核心的健康信息,绝对不会流回普通聊天。

除了隔离存储,ChatGPT 健康还加了专门的加密和安全审查机制。

所有能接入健康空间的 App 都得通过 OpenAI 的额外安全审查,只能收集必要的最少数据。

你授权了哪些 App、分享了什么数据,随时都能在设置里查看和撤销。OpenAI 还特别强调,健康对话不会用来训练模型。

那什么时候才能用上呢?ChatGPT 健康现在只对一小部分用户开放,想用的话得先加入候补名单。OpenAI 说接下来几周会逐步扩大开放范围,最终会在网页端和 iOS 上向所有用户提供。

而且有些功能还是有地域限制的,比如电子病历接入目前只在美国可用,Apple 健康连接也必须得在 iOS 系统上完成。这意味着国内用户短期内可能还用不上完整功能。

从功能上看,ChatGPT 健康能做的事情确实不少——解读化验结果、准备就诊问题、分析可穿戴设备数据、总结护理说明、对比保险方案。

但实用性到底如何,还得看实际体验。真要是身体不舒服,大多数人估计还是会选择直接去医院,ChatGPT 健康能否真正融入用户的健康管理流程,还得看它能不能在「方便」和「可靠」之间找到平衡点。

相比几年前的「互联网医疗」,越来越多互联网大厂开始入局 AI 健康赛道, 以前是生病了找医生,现在是 AI 主动管理你的各项指标,也就是从「挂号问诊」变成了「健康管家」。

比如前不久蚂蚁阿福月活跃用户已突破 1500 万,是目前国内用户量最大的 AI 健康类 App 之一。 也足以说明了当下普通人对健康的强烈需求。

值得一提的是,OpenAI 应用 CEO Fidji Simo 本人就患有体位性心动过速综合征 (POTS) 以及子宫内膜异位症 (Endometriosis),这也让其成为相关领域的积极倡导者。

今天凌晨,她也阐述了自己的经历:

「去年这个时候,我因为肾结石住院,后来又感染了。主管医生快速查看了我的病历,给我开了治疗这类感染的常用抗生素。虽然护士已经准备好给我用药,但我还是请她给我几分钟时间适应一下这种药物。

由于我多年来一直患有慢性疾病,我已经将很多健康记录上传到了 ChatGPT。我询问鉴于我的病史,我是否应该服用这种抗生素,ChatGPT 指出,这种抗生素可能会使我几年前患过的一次非常严重的感染复发。

当我跟住院医师提起这件事时,我以为她会反对,没想到她反而松了口气。她告诉我,这种药可能会给我带来非常严重的并发症,她很高兴我发现了这一点。我自然而然地问她,为什么医院里其他人没发现这个问题,她解释说,她查房时平均每个病人只有大约 5 分钟的时间,而且病历的整理方式也无法清晰地反映出这类风险。」

而 OpenAI CEO Sam Altman 也多次在公开场合表示,AI 在阅读病历、分析数据和做出诊断方面,很快就会(或者已经)超过大多数人类医生。

但有意思的是,即便认可 AI 的诊断能力,Altman 也反复强调自己不会把健康完全交给 AI:「我可能是个老古董,但我真的不想把医疗命运全托付给没有人类医生参与的 ChatGPT。」 毕竟医疗健康领域也不全然是技术问题,还包裹着信任、伦理和人情味,这些,也恰恰是真人医生难以被替代的巨大优势。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


❌
❌