美业门店AI智能体销售转化系统的核心目标是通过技术手段解决传统美业“客户需求洞察难、销售话术标准化低、意向客户筛选效率低”三大痛点,最终提升客单价与转化效率。系统采用“前端交互层-智能体核心层-数据支撑层”三层架构,结合大模型、多模态感知、数据驱动等技术,实现从客户触达到成交的全流程智能化。
下面以“美业门店使用智能体提升销售转化效率”的完整落地为主线,从系统架构设计 → 核心模块代码实现 → 数据库设计 → 部署与优化策略 → 完整系统工作流等方面,给出可直接工程化落地的方案与示例代码(Node.js/NestJS + React + PostgreSQL + Redis + 向量库 + LLM API)。


一、场景与整体架构设计

这一部分概述业务场景与整体技术架构,帮助你从顶层把握系统形态和模块划分。

1.1 业务场景拆解(美业门店+智能体)

  • 目标:提升门店销售转化效率与客单价,降低人工咨询与培训成本。
  • 典型场景
    • 线上咨询引流:用户在小程序 / H5 / 企业微信中与“智能美容顾问”聊天,进行皮肤状况问诊、项目推荐。
    • 套餐与话术推荐:智能体根据用户画像、历史消费与门店活动,自动给出推荐套餐,并给店员提供销售话术。
    • 预约与到店转化:智能体引导用户完成预约、付定金,并在到店前后做提醒与二次营销。
    • 服务后复购:根据项目效果周期,自动触发复购提醒及个性化推荐。

1.2 技术架构总览

采用典型的“前后端分离 + 多智能体编排 + RAG + 工具调用”的架构。

        ┌───────────────── 前端(小程序/H5/PC) ─────────────────┐
        │                                                     │
        │  Chat UI   预约页面   个人中心   店员工作台          │
        └───────────────▲───────────────────────────────┘
                        │ HTTP/WebSocket
                        ▼
               ┌─────────────────────────────┐
               │        API 网关 / BFF      │
               └───────▲───────────▲───────┘
                       │           │
                REST   │           │ WebSocket
                       │           │
         ┌─────────────┴───────────┴────────────────┐
         │                应用后端                   │
         │  (NestJS 微服务 / 模块化单体均可)          │
         │                                          │
         │  - Auth & User Service                   │
         │  - Customer Profile Service              │
         │  - Conversation / Session Service        │
         │  - Agent Orchestrator (智能体编排中心)    │
         │  - Tool Service(项目/库存/预约/券等工具)   │
         │  - Product & Project Catalog Service     │
         │  - Recommendation & Scoring Service      │
         │  - Workflow Engine (CRM 事件流/营销触发)  │
         └──────────▲─────────────▲───────────────┘
                    │             │
       SQL/事务     │             │ 缓存/队列/状态
                    │             │
           ┌────────┴───────┐   ┌─┴─────────────┐
           │ PostgreSQL     │   │ Redis / MQ    │
           │ (业务&日志)    │   └───────────────┘
           └───────────────┘
                    ▲
                    │向量检索/RAG
                    ▼
         ┌───────────────────────────┐
         │ 向量数据库(Milvus/pgvector)│
         └───────────────────────────┘
                    ▲
                    │LLM 调用
                    ▼
          ┌─────────────────────┐
          │ LLM Provider(OpenAI │
          │  / DeepSeek / 自建) │
          └─────────────────────┘

1.3 智能体角色设计

  • 用户侧智能体(“美容顾问” Agent)
    • 负责对C端顾客进行问诊、项目推荐、预约引导。
  • 店员侧智能体(“销售助理” Agent)
    • 给店员提供话术、搭配方案、异议处理建议。
  • 后台运营智能体(“运营分析” Agent)
    • 对转化漏斗进行分析,自动生成活动建议和A/B方案。

二、数据库设计(PostgreSQL+向量库)

这一部分给出主数据模型和核心表结构,保证智能体能“有记忆、有数据可查”。

2.1 核心业务表(关系型)

以 PostgreSQL 为例,简化但可直接落地。

-- 顾客基础信息
CREATE TABLE customers (
    id              BIGSERIAL PRIMARY KEY,
    external_id     VARCHAR(64),         -- 小程序 openid / 手机号等
    name            VARCHAR(50),
    gender          VARCHAR(10),
    birthday        DATE,
    phone           VARCHAR(20),
    skin_type       VARCHAR(50),         -- 干/油/混合/敏感 等
    skin_issues     TEXT,                -- 主要皮肤问题描述
    tags            TEXT[],              -- ['高客单','敏感肌','痘肌']
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 门店/项目/产品
CREATE TABLE stores (
    id          BIGSERIAL PRIMARY KEY,
    name        VARCHAR(100),
    address     TEXT,
    city        VARCHAR(50),
    phone       VARCHAR(20),
    biz_hours   VARCHAR(100),
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE projects (
    id              BIGSERIAL PRIMARY KEY,
    store_id        BIGINT REFERENCES stores(id),
    name            VARCHAR(100),
    category        VARCHAR(50), -- 如: '清洁','补水','抗衰'
    duration_min    INT,
    price           NUMERIC(10,2),
    description     TEXT,
    contra          TEXT,        -- 禁忌/不适用人群
    effects         TEXT,        -- 预期效果
    tags            TEXT[],
    is_active       BOOLEAN DEFAULT TRUE,
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

-- 会话与消息
CREATE TABLE conversations (
    id              BIGSERIAL PRIMARY KEY,
    customer_id     BIGINT REFERENCES customers(id),
    channel         VARCHAR(20),   -- 'wechat','h5','in_store'
    status          VARCHAR(20),   -- 'active','closed'
    started_at      TIMESTAMPTZ DEFAULT NOW(),
    ended_at        TIMESTAMPTZ
);

CREATE TABLE messages (
    id              BIGSERIAL PRIMARY KEY,
    conversation_id BIGINT REFERENCES conversations(id),
    role            VARCHAR(20), -- 'user','assistant','staff'
    content         TEXT,
    meta            JSONB,       -- 如 tool_calls, scores 等
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

-- 预约与订单
CREATE TABLE appointments (
    id              BIGSERIAL PRIMARY KEY,
    customer_id     BIGINT REFERENCES customers(id),
    store_id        BIGINT REFERENCES stores(id),
    project_id      BIGINT REFERENCES projects(id),
    schedule_time   TIMESTAMPTZ,
    status          VARCHAR(20),   -- 'pending','confirmed','completed','cancelled','no_show'
    channel         VARCHAR(20),   -- 'ai_agent','staff','miniapp'
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE orders (
    id              BIGSERIAL PRIMARY KEY,
    customer_id     BIGINT REFERENCES customers(id),
    total_amount    NUMERIC(10,2),
    status          VARCHAR(20),  -- 'unpaid','paid','refunded'
    source          VARCHAR(20),  -- 'ai_agent','offline','online'
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE order_items (
    id              BIGSERIAL PRIMARY KEY,
    order_id        BIGINT REFERENCES orders(id) ON DELETE CASCADE,
    project_id      BIGINT REFERENCES projects(id),
    quantity        INT,
    price           NUMERIC(10,2)
);

2.2 智能体与知识库相关表

-- 预设话术、项目说明、门店SOP文档等,用于RAG检索
CREATE TABLE kb_documents (
    id           BIGSERIAL PRIMARY KEY,
    store_id     BIGINT REFERENCES stores(id),
    title        VARCHAR(255),
    content      TEXT,
    doc_type     VARCHAR(50),   -- 'sop','faq','script','project_detail'
    tags         TEXT[],
    created_at   TIMESTAMPTZ DEFAULT NOW()
);

-- 向量表 (以 pgvector 为例)
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE kb_embeddings (
    id           BIGSERIAL PRIMARY KEY,
    kb_doc_id    BIGINT REFERENCES kb_documents(id) ON DELETE CASCADE,
    chunk_index  INT,
    embedding    vector(1536),   -- 维度依据模型
    created_at   TIMESTAMPTZ DEFAULT NOW()
);

-- 对话级别的“智能体决策日志”
CREATE TABLE agent_actions (
    id              BIGSERIAL PRIMARY KEY,
    conversation_id BIGINT REFERENCES conversations(id),
    step_index      INT,
    agent_name      VARCHAR(50),  -- 'beauty_consultant','sales_assistant'
    tool_called     VARCHAR(100),
    tool_input      JSONB,
    tool_output     JSONB,
    reasoning       TEXT,
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

三、后端核心模块与代码实现(NestJS 示例)

这一部分通过关键模块代码展示如何从零实现“智能美容顾问”智能体及工具调用。

3.1 NestJS 项目结构

backend/
  src/
    main.ts
    app.module.ts
    common/
      config/
      interceptors/
      guards/
    modules/
      auth/
      customers/
      projects/
      conversations/
      agents/
      tools/
      kb/
      workflow/

3.2 Conversation 模块(维护会话与消息)

3.2.1 conversation.entity.ts
// src/modules/conversations/entities/conversation.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn } from 'typeorm';
import { Customer } from '../../customers/entities/customer.entity';
import { Message } from './message.entity';

@Entity('conversations')
export class Conversation {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Customer, (c) => c.conversations)
  customer: Customer;

  @Column({ length: 20 })
  channel: string; // 'wechat','h5'

  @Column({ length: 20, default: 'active' })
  status: string;

  @CreateDateColumn()
  startedAt: Date;

  @Column({ type: 'timestamptz', nullable: true })
  endedAt: Date | null;

  @OneToMany(() => Message, (m) => m.conversation)
  messages: Message[];
}
// src/modules/conversations/entities/message.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm';
import { Conversation } from './conversation.entity';

@Entity('messages')
export class Message {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Conversation, (c) => c.messages)
  conversation: Conversation;

  @Column({ length: 20 })
  role: 'user' | 'assistant' | 'staff';

  @Column({ type: 'text' })
  content: string;

  @Column({ type: 'jsonb', nullable: true })
  meta: any;

  @CreateDateColumn()
  createdAt: Date;
}
3.2.2 conversation.service.ts
// src/modules/conversations/conversations.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Conversation } from './entities/conversation.entity';
import { Message } from './entities/message.entity';

@Injectable()
export class ConversationsService {
  constructor(
    @InjectRepository(Conversation)
    private readonly convRepo: Repository<Conversation>,
    @InjectRepository(Message)
    private readonly msgRepo: Repository<Message>,
  ) {}

  async createOrGetActive(customerId: number, channel: string) {
    let conv = await this.convRepo.findOne({
      where: { customer: { id: customerId }, status: 'active', channel },
    });
    if (!conv) {
      conv = this.convRepo.create({
        customer: { id: customerId } as any,
        channel,
      });
      await this.convRepo.save(conv);
    }
    return conv;
  }

  async addMessage(conversationId: number, role: 'user' | 'assistant' | 'staff', content: string, meta?: any) {
    const msg = this.msgRepo.create({
      conversation: { id: conversationId } as any,
      role,
      content,
      meta,
    });
    return this.msgRepo.save(msg);
  }

  async getMessages(conversationId: number, limit = 20) {
    return this.msgRepo.find({
      where: { conversation: { id: conversationId } },
      order: { id: 'DESC' },
      take: limit,
    });
  }
}

3.3 智能体编排模块(Agent Orchestrator)

这一模块负责根据对话上下文选择合适的智能体角色、构造 Prompt、调用 LLM 与工具。

3.3.1 Agent 定义与工具接口
// src/modules/agents/types.ts
export type ToolCall = {
  name: string;
  arguments: any;
};

export interface AgentContext {
  customerId: number;
  conversationId: number;
  channel: string;
  lastMessages: { role: string; content: string }[];
}

export interface AgentResult {
  reply: string;
  toolCalls?: ToolCall[];
}
// src/modules/tools/tools.interface.ts
export interface Tool {
  name: string;
  description: string;
  schema: any; // JSON Schema
  execute(args: any): Promise<any>;
}
3.3.2 工具实现示例(项目推荐 / 预约检查)
// src/modules/tools/tools.service.ts
import { Injectable } from '@nestjs/common';
import { Tool } from './tools.interface';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Project } from '../projects/entities/project.entity';
import { AppointmentsService } from '../appointments/appointments.service';

@Injectable()
export class ToolsService {
  private tools: Map<string, Tool> = new Map();

  constructor(
    @InjectRepository(Project)
    private readonly projectRepo: Repository<Project>,
    private readonly appointmentsService: AppointmentsService,
  ) {
    this.registerTools();
  }

  private registerTools() {
    // 项目推荐工具
    this.tools.set('recommend_projects', {
      name: 'recommend_projects',
      description: '根据肌肤问题、预算等推荐合适的门店项目',
      schema: {
        type: 'object',
        properties: {
          skin_issues: { type: 'string' },
          budget: { type: 'number' },
          store_id: { type: 'number' },
        },
        required: ['skin_issues', 'store_id'],
      },
      execute: async (args) => {
        const qb = this.projectRepo
          .createQueryBuilder('p')
          .where('p.store_id = :storeId', { storeId: args.store_id })
          .andWhere('p.is_active = true');

        // 简单示例:根据关键词过滤
        if (args.skin_issues?.includes('痘')) {
          qb.andWhere(':tag = ANY(p.tags)', { tag: '祛痘' });
        }
        if (args.budget) {
          qb.andWhere('p.price <= :budget', { budget: args.budget });
        }

        const projects = await qb.orderBy('p.price', 'ASC').limit(5).getMany();
        return { projects };
      },
    });

    // 预约创建工具
    this.tools.set('create_appointment', {
      name: 'create_appointment',
      description: '为用户创建一次门店项目预约',
      schema: {
        type: 'object',
        properties: {
          customer_id: { type: 'number' },
          store_id: { type: 'number' },
          project_id: { type: 'number' },
          schedule_time: { type: 'string', description: 'ISO 时间' },
        },
        required: ['customer_id', 'store_id', 'project_id', 'schedule_time'],
      },
      execute: async (args) => {
        return this.appointmentsService.createFromAgent(args);
      },
    });
  }

  getTool(name: string): Tool | undefined {
    return this.tools.get(name);
  }

  listTools(): Tool[] {
    return Array.from(this.tools.values());
  }
}

AppointmentsService.createFromAgent 示例:

// src/modules/appointments/appointments.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Appointment } from './entities/appointment.entity';
import { Repository } from 'typeorm';

@Injectable()
export class AppointmentsService {
  constructor(
    @InjectRepository(Appointment)
    private readonly repo: Repository<Appointment>,
  ) {}

  async createFromAgent(input: {
    customer_id: number;
    store_id: number;
    project_id: number;
    schedule_time: string;
  }) {
    const appointment = this.repo.create({
      customer: { id: input.customer_id } as any,
      store: { id: input.store_id } as any,
      project: { id: input.project_id } as any,
      scheduleTime: new Date(input.schedule_time),
      status: 'pending',
      channel: 'ai_agent',
    });
    return this.repo.save(appointment);
  }
}
3.3.3 LLM 客户端封装

以 OpenAI 接口为例(可替换为任意 LLM 提供商)。

// src/modules/llm/llm.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';

@Injectable()
export class LlmService {
  private apiKey = process.env.OPENAI_API_KEY!;
  private baseUrl = 'https://api.openai.com/v1';

  async chatCompletion(params: {
    model: string;
    messages: { role: string; content: string }[];
    tools?: any[];
  }) {
    const resp = await axios.post(
      `${this.baseUrl}/chat/completions`,
      {
        model: params.model,
        messages: params.messages,
        tools: params.tools,
        tool_choice: 'auto',
      },
      {
        headers: {
          Authorization: `Bearer ${this.apiKey}`,
        },
      },
    );
    return resp.data;
  }
}
3.3.4 Agent Orchestrator 实现
// src/modules/agents/agents.service.ts
import { Injectable } from '@nestjs/common';
import { ConversationsService } from '../conversations/conversations.service';
import { ToolsService } from '../tools/tools.service';
import { LlmService } from '../llm/llm.service';
import { AgentContext, AgentResult } from './types';
import { AgentActionsService } from '../agent-actions/agent-actions.service';

@Injectable()
export class AgentsService {
  constructor(
    private readonly convService: ConversationsService,
    private readonly toolsService: ToolsService,
    private readonly llmService: LlmService,
    private readonly agentActionsService: AgentActionsService,
  ) {}

  private buildSystemPrompt(): string {
    return `
你是一名资深的“智能美容顾问”,在美业门店工作,目标是:
1)帮助用户识别皮肤问题;
2)为用户推荐合适的门店项目和套餐;
3)在合适的时机引导用户预约和下单;
4)始终遵守安全与禁忌原则,不做医疗承诺。

回答要求:
- 使用亲切自然的中文,可以适当使用表情符号但不过度;
- 在推荐项目时,解释推荐理由(与用户问题、预算、肤质的关联);
- 避免夸大效果,不涉及医学诊断;
- 在准备预约时,可以调用工具完成预约创建;
- 如需查门店项目信息,请通过工具调用,不要编造。
`;
  }

  async handleUserMessage(params: {
    customerId: number;
    channel: string;
    userMessage: string;
  }): Promise<AgentResult> {
    const conv = await this.convService.createOrGetActive(params.customerId, params.channel);
    await this.convService.addMessage(conv.id, 'user', params.userMessage);

    const lastMessages = (await this.convService.getMessages(conv.id, 10))
      .reverse()
      .map((m) => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content }));

    const tools = this.toolsService.listTools().map((t) => ({
      type: 'function',
      function: {
        name: t.name,
        description: t.description,
        parameters: t.schema,
      },
    }));

    const messages = [
      { role: 'system', content: this.buildSystemPrompt() },
      ...lastMessages,
    ];

    const completion = await this.llmService.chatCompletion({
      model: 'gpt-4o-mini',
      messages,
      tools,
    });

    const choice = completion.choices[0];
    const msg = choice.message;

    // 是否调用工具?
    if (msg.tool_calls?.length) {
      const toolResults = [];
      for (const tc of msg.tool_calls) {
        const tool = this.toolsService.getTool(tc.function.name);
        if (!tool) continue;
        const args = JSON.parse(tc.function.arguments || '{}');
        const output = await tool.execute(args);

        await this.agentActionsService.logAction({
          conversationId: conv.id,
          agentName: 'beauty_consultant',
          toolCalled: tool.name,
          toolInput: args,
          toolOutput: output,
          reasoning: msg.content || '',
        });

        toolResults.push({ name: tool.name, output });
      }

      // 二次调用 LLM,总结工具结果并生成最终回复
      const toolMessages = toolResults.map((tr) => ({
        role: 'tool',
        content: JSON.stringify(tr.output),
        name: tr.name,
      })) as any;

      const finalCompletion = await this.llmService.chatCompletion({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: this.buildSystemPrompt() },
          ...lastMessages,
          msg,
          ...toolMessages,
        ],
      });

      const finalReply = finalCompletion.choices[0].message.content;
      await this.convService.addMessage(conv.id, 'assistant', finalReply);
      return { reply: finalReply };
    } else {
      const reply = msg.content;
      await this.convService.addMessage(conv.id, 'assistant', reply);
      return { reply };
    }
  }
}

AgentActionsService 简单记录日志:

// src/modules/agent-actions/agent-actions.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AgentAction } from './entities/agent-action.entity';
import { Repository } from 'typeorm';

@Injectable()
export class AgentActionsService {
  constructor(
    @InjectRepository(AgentAction)
    private readonly repo: Repository<AgentAction>,
  ) {}

  async logAction(params: {
    conversationId: number;
    agentName: string;
    toolCalled: string;
    toolInput: any;
    toolOutput: any;
    reasoning: string;
  }) {
    const count = await this.repo.count({
      where: { conversation: { id: params.conversationId } as any },
    });

    const action = this.repo.create({
      conversation: { id: params.conversationId } as any,
      stepIndex: count + 1,
      agentName: params.agentName,
      toolCalled: params.toolCalled,
      toolInput: params.toolInput,
      toolOutput: params.toolOutput,
      reasoning: params.reasoning,
    });

    await this.repo.save(action);
  }
}

3.4 知识库与向量检索(RAG)

3.4.1 文档入库与切片
// src/modules/kb/kb.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { KbDocument } from './entities/kb-document.entity';
import { KbEmbedding } from './entities/kb-embedding.entity';
import { LlmService } from '../llm/llm.service';
import { chunkText } from '../../utils/text-chunk';

@Injectable()
export class KbService {
  constructor(
    @InjectRepository(KbDocument)
    private readonly docRepo: Repository<KbDocument>,
    @InjectRepository(KbEmbedding)
    private readonly embRepo: Repository<KbEmbedding>,
    private readonly llmService: LlmService,
  ) {}

  async addDocument(input: {
    storeId: number;
    title: string;
    content: string;
    docType: string;
    tags?: string[];
  }) {
    const doc = this.docRepo.create(input);
    await this.docRepo.save(doc);

    const chunks = chunkText(input.content, 500); // 每段500字
    let idx = 0;
    for (const chunk of chunks) {
      const embedding = await this.generateEmbedding(chunk);
      const emb = this.embRepo.create({
        kbDoc: doc,
        chunkIndex: idx++,
        embedding,
      });
      await this.embRepo.save(emb);
    }
    return doc;
  }

  private async generateEmbedding(text: string): Promise<number[]> {
    // 调 Embedding 接口,伪代码
    const resp = await this.llmService.embedding({
      model: 'text-embedding-3-small',
      input: text,
    });
    return resp.data[0].embedding;
  }
}

chunkText 简单实现:

// src/utils/text-chunk.ts
export function chunkText(text: string, maxLen: number): string[] {
  const chunks: string[] = [];
  let start = 0;
  while (start < text.length) {
    chunks.push(text.slice(start, start + maxLen));
    start += maxLen;
  }
  return chunks;
}
3.4.2 向量检索接口
// src/modules/kb/kb.service.ts (补充)
import { DataSource } from 'typeorm';

@Injectable()
export class KbService {
  constructor(
    // ...前略
    private readonly dataSource: DataSource,
  ) {}

  // 查询与问题最相关的门店知识片段
  async searchRelevant(storeId: number, query: string, topK = 5) {
    const queryEmb = await this.generateEmbedding(query);

    const result = await this.dataSource.query(
      `
      SELECT d.id, d.title, d.content, d.doc_type,
        1 - (e.embedding <=> $1::vector) AS score
      FROM kb_embeddings e
      JOIN kb_documents d ON e.kb_doc_id = d.id
      WHERE d.store_id = $2
      ORDER BY e.embedding <=> $1::vector
      LIMIT $3
      `,
      [queryEmb, storeId, topK],
    );

    return result;
  }
}

在智能体中将 searchRelevant 作为一种“隐式工具”,在构造 Prompt 时把检索出的内容一起塞到系统 / 上下文中,使回答更贴合门店自身项目与话术。


四、前端关键实现(H5/小程序 Chat UI)

这一部分以 React H5 为例,展示如何将后端智能体能力以“聊天窗口 + 预约引导 UI”形式呈现给用户。

4.1 Chat 组件基本结构

// src/components/ChatWidget.tsx
import React, { useState, useEffect } from 'react';
import { sendMessageToAgent } from '../api/agent';

type Message = {
  id: string;
  role: 'user' | 'assistant';
  content: string;
};

export const ChatWidget: React.FC<{ customerId: number }> = ({ customerId }) => {
  const [messages, setMessages] = useState<Message[]>([
    {
      id: 'welcome',
      role: 'assistant',
      content: '你好呀,我是你的专属智能美容顾问~可以简单和我说说你的肤质和困扰吗?😊',
    },
  ]);
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSend = async () => {
    if (!input.trim() || loading) return;
    const userMsg: Message = {
      id: Date.now().toString(),
      role: 'user',
      content: input.trim(),
    };
    setMessages((prev) => [...prev, userMsg]);
    setInput('');
    setLoading(true);

    try {
      const resp = await sendMessageToAgent({
        customerId,
        channel: 'h5',
        message: userMsg.content,
      });
      const aiMsg: Message = {
        id: Date.now().toString() + '-ai',
        role: 'assistant',
        content: resp.reply,
      };
      setMessages((prev) => [...prev, aiMsg]);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="chat-widget">
      <div className="chat-messages">
        {messages.map((m) => (
          <div key={m.id} className={`msg msg-${m.role}`}>
            <div className="msg-bubble">{m.content}</div>
          </div>
        ))}
        {loading && (
          <div className="msg msg-assistant">
            <div className="msg-bubble typing">正在为你分析中...</div>
          </div>
        )}
      </div>
      <div className="chat-input">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="描述一下你的皮肤情况、预算或想了解的项目~"
        />
        <button onClick={handleSend} disabled={loading}>
          发送
        </button>
      </div>
    </div>
  );
};

对应的 API 封装:

// src/api/agent.ts
import axios from 'axios';

export async function sendMessageToAgent(payload: {
  customerId: number;
  channel: string;
  message: string;
}) {
  const resp = await axios.post('/api/agent/chat', payload);
  return resp.data;
}

五、API 接口设计(BFF 层)

这一部分展示对前端暴露的关键 REST 接口,包括聊天、预约确认等。

5.1 Chat 接口

// src/modules/agents/agents.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AgentsService } from './agents.service';

@Controller('agent')
export class AgentsController {
  constructor(private readonly agentsService: AgentsService) {}

  @Post('chat')
  async chat(@Body() body: { customerId: number; channel: string; message: string }) {
    const result = await this.agentsService.handleUserMessage({
      customerId: body.customerId,
      channel: body.channel,
      userMessage: body.message,
    });
    return result;
  }
}

5.2 店员工作台接口(生成话术示例)

// src/modules/agents/staff-assistant.service.ts
import { Injectable } from '@nestjs/common';
import { LlmService } from '../llm/llm.service';
import { CustomersService } from '../customers/customers.service';

@Injectable()
export class StaffAssistantService {
  constructor(
    private readonly llmService: LlmService,
    private readonly customersService: CustomersService,
  ) {}

  async generateSalesScript(params: {
    customerId: number;
    projectIds: number[];
  }) {
    const customer = await this.customersService.findById(params.customerId);
    // 查询项目信息略...
    const projectsDesc = '...';

    const systemPrompt = `
你是一名经验丰富的美业门店金牌顾问,现在你的任务是:
- 为店员生成销售话术,用于向${customer.name}介绍项目;
- 话术要自然、真诚,不夸大效果,要根据客户的肤质标签、年龄和价格敏感度进行调整;
- 如果有多个项目,帮店员设计“主推+附加项目”的组合策略。
`;

    const userPrompt = `
客户信息:
- 性别:${customer.gender}
- 肤质:${customer.skin_type}
- 主要困扰:${customer.skin_issues}
- 标签:${(customer.tags || []).join(',')}

待推荐项目:
${projectsDesc}

请输出:
1)开场白
2)项目介绍话术(包含效果+适合人群+次数建议)
3)套餐/组合推荐话术
4)异议处理话术(如价格、效果担心等)
`;

    const completion = await this.llmService.chatCompletion({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: userPrompt },
      ],
    });

    return { script: completion.choices[0].message.content };
  }
}

六、部署与优化策略

这一部分从部署架构、可用性、性能和成本角度给出实战建议。

6.1 部署基础(Docker + K8s/容器服务)

  • 容器化
    • backendfrontend、向量库(如 Milvus)分别构建 Docker 镜像。
  • API 网关 & HTTPS
    • 使用 Nginx/Ingress 替前端与后端提供 HTTPS 和路径路由。
  • 数据库与缓存
    • PostgreSQL 部署在云数据库或自建高可用集群。
    • Redis 用于:
      • 会话状态临时缓存;
      • 对话结果缓存(prompt + user input → output);
      • 队列(延迟任务、营销触达)。

6.2 性能优化

  • 会话上下文截断
    • 仅保留最近 N 轮对话 + 关键摘要,减少 token 消耗和延迟。
  • RAG 缩小检索范围
    • 根据门店 ID、项目类别先过滤,再做向量检索。
  • 预计算与缓存
    • 常用问题(“敏感肌适合什么项目?”)可预生成高质量回答,接入缓存。
  • 并发与限流
    • 对单客户/单 IP 的并发聊天数做限流,防止滥用。
    • 对 LLM 调用设置熔断和退避重试。

6.3 可靠性与监控

  • 日志与追踪
    • conversationsmessagesagent_actions 与业务日志对齐,支持链路追踪。
  • 指标监控
    • 关注:LLM 调用成功率、平均响应时延、RPS、token 消耗。
  • AB 实验与策略优化
    • 对不同 Prompt、不同推荐策略进行 AB 实验,比较:
      • 预约率;
      • 到店率;
      • 客单价;
      • 复购率。

七、完整系统工作流(从用户进线到成交)

这一部分通过“时序流程”来串联整个系统的工作机制。

7.1 用户侧咨询与推荐流程

[1] 用户进入门店小程序/H5 → 打开"智能顾问"
    ↓
[2] 前端创建/拉取 customerId (登录或匿名标识)
    ↓
[3] 用户输入皮肤问题
    ↓
[4] 前端调用 POST /api/agent/chat
    ↓
[5] 后端:
    - 根据 customerId + channel 找/建 conversation
    - 记录 user message
    - 准备最近N条对话上下文
    - 构造 system prompt (美容顾问角色 + 安全规范)
    - 构建 tools 列表: recommend_projects, create_appointment, ...
    ↓
[6] 调用 LLM(chatCompletion + tools)
    ↓
[7] LLM 判断是否需要调用工具:
    - 若需要: 返回 tool_calls,后端执行 ToolsService 相应方法
    - 记录 agent_actions (包括 reasoning 和输入输出)
    - 再次调用 LLM,总结工具结果 → 最终自然语言回复
    - 若不需要: 直接返回自然语言回复
    ↓
[8] 后端存 assistant message,并把 reply 返回前端
    ↓
[9] 前端渲染回复,如含有推荐项目,则可解析成卡片形式展示
    ↓
[10] 若回复中包含“可立即为你预约”等 CTA:
    - 前端展示“去预约”按钮
    - 点击后进入预约确认 UI (选择时间/门店/项目)
    - 点击确认 → 调用预约 API 或由智能体工具完成

7.2 预约与到店转化流程

[1] 用户在智能体引导下完成预约 (工具 create_appointment)
    ↓
[2] 预约写入 appointments 表,状态 pending
    ↓
[3] 工作流引擎监听预约事件 (如 MessageQueue)
    ↓
[4] 若门店采用人工确认:
    - 推送到店员工作台 → 店员确认时间/资源 → 更新 status=confirmed
    - 向用户发送确认通知 (模板消息/短信)
    ↓
[5] 到店前X小时:
    - 工作流引擎触发提醒 (由运营智能体生成个性化提醒文案)
    ↓
[6] 到店服务完成 → 订单与项目核销记录写入 orders/order_items
    ↓
[7] 服务后Y天:
    - 根据项目效果周期,工作流引擎触发“复购/维护”营销
    - 由运营智能体生成个性化文案 → 通过企业微信/小程序消息触达

7.3 店员工作台 + 销售助理智能体流程

[1] 店员在PC/Pad 打开"智能销售助理"
    ↓
[2] 选择某位顾客 + 选中准备主推的项目/套餐
    ↓
[3] 前端调用 /api/staff/assistant/script
    ↓
[4] 后端:
    - 查询顾客画像(customer表) + 历史消费(orders)
    - 查询项目详情(projects)与门店SOP(kb_documents)
    - 组合成 prompt → 调用 LLM
    ↓
[5] 返回销售话术建议:
    - 开场/项目介绍/组合搭配/异议处理
    ↓
[6] 店员在实际沟通中参考或稍作修改使用
    ↓
[7] 若店员有新的有效话术,可提交回知识库,进入 KB 文档并自动切片入向量库

八、补充:安全合规与可拓展方向

8.1 安全与合规

  • 用户隐私
    • 对手机号、敏感标签等字段做脱敏或加密存储;
    • 数据最小化:仅获取完成推荐与预约所需信息。
  • 医疗相关边界
    • 明确在 Prompt 中“非医疗、不做诊断,仅提供护理建议”;
    • 不输出具体药品推荐。

8.2 可扩展方向

  • 图像分析:接入皮肤照片上传与图像模型,自动识别痘痘/斑点/毛孔等。
  • 多门店多品牌场景:增加品牌/连锁维度;智能体根据品牌定位调整语气与推荐策略。
  • 自动训练与迭代:收集高转化对话,定期让运营智能体生成新 SOP/话术并人工审核入库。

九、总结

  • 技术上,通过 NestJS + PostgreSQL + Redis + 向量库 + LLM API,可以构建一个面向美业门店的、真正可落地的多智能体系统,覆盖顾客咨询、项目推荐、预约、店员销售辅助、运营分析等全链路。
  • 实现上,核心在于:合理的数据建模、稳健的会话与工具调用编排、贴合业务的 Prompt 与 RAG 方案,以及面向门店的可观测与可运营能力。
  • 按上述架构与代码骨架,你可以在现有技术栈基础上快速搭建 MVP,并在真实门店环境中通过 AB 测试和数据反馈,逐步打磨出高转化的“智能美容顾问”与“销售助理”。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐