掘金 人工智能 07月13日 10:28
aichat-core简化 LLM 与 MCP 集成的前端核心库(TypeScript)
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

aichat-core是一个基于TypeScript的前端核心库,旨在简化LLM(大型语言模型)与MCP(模型上下文协议)的集成,特别适用于构建AI对话应用。它封装了openai-sdk和mcp-sdk,提供了核心业务模型抽象、流式聊天逻辑、开箱即用的UI组件,如HTML代码编辑与预览、Markdown渲染和多轮对话展示组件。开发者可以专注于后端逻辑和UI定制,而无需处理LLM与MCP的复杂交互细节。该库支持OpenAI和MCP服务,并提供了详细的使用说明和示例代码。

💡 aichat-core 简化了前端项目中LLM与MCP服务的集成,降低了开发复杂度。

📦 该库封装了openai-sdk和mcp-sdk,并提供了核心业务模型与流程抽象,包括流式聊天核心逻辑。

🎨 aichat-core 提供开箱即用的 UI 组件,包括HTML代码编辑与预览、Markdown内容渲染和多轮对话展示的消息项组件。

⚙️ 开发者可以专注于后端业务逻辑实现和前端UI定制,无需处理LLM与MCP之间复杂的交互细节。

🛠️ 提供了LLMClient类,用于与OpenAI和MCP服务交互,支持流式聊天和工具调用。

aichat-core

B站视频:【实现】仿CherryStudio做一个Web端全能AI助手(!!!全网第一个讲思路和实现细节的UP!!!)

npm仓库如下:

配套脚手架如下:

1. 介绍

NPM仓库地址:www.npmjs.com/package/aic…简化 LLM 与 MCP 集成的前端核心库(TypeScript)

aichat-core 是一个前端核心库,旨在显著降低在项目中集成 OpenAI 和 MCP 服务的复杂度。它封装了 openai-sdkmcp-sdk,提供了:

    核心业务模型与流程抽象:预定义了关键业务模型,并实现了结合 MCP 协议的流式聊天核心逻辑。

    开箱即用的 UI 组件:封装了常用交互组件,包括:

      HTML 代码的实时编辑与预览

      Markdown 内容的优雅渲染

      支持多轮对话展示的消息项组件

核心价值:开发者只需专注于后端业务逻辑实现前端 UI 定制,而无需深入处理 LLM 与 MCP 之间复杂的交互细节。aichat-core 为您处理了底层的复杂性,让您更快地构建基于 LLM 和 MCP 的智能对话应用!

2. 使用

2.1 依赖项

install该模块,请注意安装以下对应版本的npm包

    "@modelcontextprotocol/sdk": "^1.15.0",    "codemirror": "5",    "github-markdown-css": "^5.8.1",    "openai": "^5.3.0",    "react-codemirror2": "^8.0.1",    "react-json-view": "^1.21.3",    "react-markdown": "^9.1.0",    "react-syntax-highlighter": "^15.6.1",    "rehype-katex": "^7.0.1",    "rehype-raw": "^7.0.0",    "remark-gfm": "^4.0.1",    "remark-math": "^6.0.0"

暴露出来的核心功能:

/** TS类型及核心业务实现客户端类封装 */import LLMCallBackMessage from "./core/response/LLMCallBackMessage";import LLMCallBackMessageChoice from "./core/response/LLMCallBackMessageChoice";import LLMCallBackToolMessage from "./core/response/LLMCallBackToolMessage";import LLMUsage from "./core/response/LLMUsage";import LLMThinkUsage from "./core/response/LLMThinkUsage";import LLMClient from "./core/LLMClient";import McpClient from "./core/McpClient";import McpTool from "./core/McpTool";import LLMUtil from "./core/LLMUtil";import ChatMarkDown from "./ui/chat-markdown";import CodeEditorPreview from "./ui/code-editor-preview";import ChatBubbleItem from "./ui/chat-bubble-item";export {  LLMCallBackMessage,  LLMCallBackMessageChoice,  LLMCallBackToolMessage,  LLMThinkUsage,  LLMUsage,  LLMClient,  McpClient,  McpTool,  LLMUtil,  ChatMarkDown,  CodeEditorPreview,  ChatBubbleItem,};

2.2 安装

 npm install aichat-core  && yarn add aichat-core

2.3 核心业务模型

LLMCallBackMessage.ts

/** AI响应消息回调对象 */export default interface LLMCallBackMessage {    /** 消息ID,一定是有的 */    id: string | number;    /** 消息创建时间,一定有的,只不过初始化的时候无,UI回显需要, 案例:1750923621 */    timed?: number;    /** 使用的llm模型,一定是有的 */    model: string;    /** 消息角色(system、assistant、user、tool)*/    role: string;    /** 集成antd design x Bubble的typing属性,true表示设置聊天内容打字动画,false则不使用 */    typing?: boolean;    /** 集成antd design x Bubble的loading属性,true表示聊天内容加载,false表示不加载 */    loading?: boolean;    /** 消息组,一次LLM响应可能包含多组消息,初始化的时候可空 */    choices?: LLMCallBackMessageChoice[];}

LLMCallBackToolMessage

/** 回调消息选择对象 */export default interface LLMCallBackMessageChoice {    /** 消息索引,从1开始(多轮对话,index是累加的) */    index: number;    /** 正常响应内容,一定有,就算LLM择取工具时,content也是有值的,只不过是空串 */    content: string;    /** 推理内容,不一定有,要看LLM是否具备 */    reasoning_content?: string;    /**     * -1 或 undefined :不带推理     *  0:思考中     *  1:思考结束     */    thinking?: number;    /** 回调工具消息数组 */    tools?: LLMCallBackToolMessage[];    /** 消耗的token统计 */    usage?: LLMMessageUsage;}

LLMCallBackToolMessage

/** 工具调用消息回调对象 */export default interface LLMCallBackToolMessage {  /** 调用id */  id:string;  /** 函数的索引 */  index: number;  /** 函数的名称 */  name: string;  /** 函数的参数,这个选填,如果能弄过来更好,便于后续错误调试 */  arguments?: any;  /** 函数调用的结果内容 */  content?: string;}

LLMClient.ts

/** * @description 用于与 OpenAI 和 MCP 服务进行交互,支持流式聊天和工具调用。 * @author appleyk * @github https://github.com/kobeyk * @date 2025年7月9日21:44:02 */export default class LLMClient {  // 定义常量类型(防止魔法值)  public static readonly CONTENT_THINKING = "thinking";  public static readonly TYPE_FUNCTION = "function";  public static readonly TYPE_STRING = "string";  public static readonly REASON_STOP = "stop";  public static readonly REASON_TOO_CALLS = "tool_calls";  public static readonly ROLE_AI = "assistant";  public static readonly ROLE_SYSTEM = "system";  public static readonly ROLE_USER = "user";  public static readonly ROLE_TOOL = "tool";  public static readonly THINGKING_START_TAG = "<think>";  public static readonly THINGKING_END_TAG = "</think>";  // llm大模型对象  llm: OpenAI;  // mcp客户端对象(二次封装),不一定有,一旦有,必须初始化和进行"三次握手"后才可以正常使用mcp服务端提供的能力  mcpClient?: McpClient;  // 模型名称  modelName: string;  // mcpServer服务器连接地址  mcpServer = "";  // mcp工具列表  tools: McpTool[] = [];  // 初始化mcp的状态,默认false,如果开启llm流式聊天前,这个状态为false,则报错  initMcpState = false;  // 思考状态(每一次消息问答结束后,thinkState要回归false)  thinkState = false;  /**   * 构造器   * @param mcpServer mcp服务器连接地址(目前仅支持streamable http,后续再放开stdio和sse),如果空的话,则表示外部不使用mcp   * @param mcpClientName mcpClient名称   * @param apiKey llm访问接口对应的key,如果没有,填写"EMPTY",默认值走系统配置,如需要指定外部传进来   * @param baseUrl llm访问接口的地址,目前走的是openai标准,大部分llm都支持,除了少数,后续可能要支持下(再说),默认值走系统配置,如需要指定外部传进来   * @param modelName llm模型的名称,这个需要发起llm聊天的时候指定,默认值走系统配置,如需要指定外部传进来   */  constructor(    mcpServer = "",    mcpClientName = "",    apiKey:string,    baseUrl:string,    modelName:string  ) {    // 使用llm,必须要实例化OpenAI实例对象    this.llm = new OpenAI({      apiKey: apiKey,      baseURL: baseUrl,      dangerouslyAllowBrowser: true, // web端直接调用openai是不安全的,这个要开启    });    // 使用llm,必须要指定模型的名称    this.modelName = modelName;    // 表示使用mcp来为llm提供外部工具调用参考    if (mcpServer !== "") {      this.mcpServer = mcpServer;      this.mcpClient = new McpClient(mcpClientName);    }  }    /**   * 流式聊天升级版(支持多轮对话)   * @param messages  消息上下文(多轮多话要把上一轮对话的结果加进去)   * @param onContentCallBack 文本内容回调函数   * @param onCallToolsCallBack 工具回调函数,如果传入空列表,则表示一轮工具调用结束   * @param onCallToolResultCallBack 工具调用执行结果回调函数,name->result键值对   * @param callBackMessage 回调消息大对象(包含的信息相当全)   * @param controllerRef 请求中断控制器   * @param loop 对话轮数   */  async chatStreamLLMV2(    messages: ChatCompletionMessageParam[],    onContentCallBack: (callBackMessage: LLMCallBackMessage) => void,    onCallToolsCallBack: (_toolCalls: LLMStreamChoiceDeltaTooCall[]) => void,    onCallToolResultCallBack: (name: string, result: any) => void,    callBackMessage: LLMCallBackMessage,    controllerRef: AbortController | null,    loop: number = 1 // 轮数  ) {    const stream = await this.genLLMStream(messages, controllerRef);    if (stream && typeof stream[Symbol.asyncIterator] === LLMClient.TYPE_FUNCTION) {      let _toolCalls: LLMStreamChoiceDeltaTooCall[] = [];      for await (const chunk of stream) {        /** 如果usage包含且内容不等于null,则说明本次结束 */        callBackMessage.timed = chunk.created;        callBackMessage.model = chunk.model;        let usage = chunk.usage;        if (usage) {          let _choices = callBackMessage.choices          if (_choices && _choices.length > 0) {            let _choiceMessage = this.filterChoiceMessage(callBackMessage, loop);            _choiceMessage.index = loop;            _choiceMessage.thinking = 2;            _choiceMessage.reasoning_content = "";            _choiceMessage.content = "";            _choiceMessage.usage = {              completion_tokens: usage.completion_tokens,              prompt_tokens: usage.prompt_tokens,              total_tokens: usage.total_tokens            }            let newCallBackMessage = JSON.parse(JSON.stringify(callBackMessage))            onContentCallBack(newCallBackMessage);          }          break;        }        /** 判断chunk.choices[0]是否有finish_reason字段,如果有赋值 */        let finishReason = chunk.choices[0].finish_reason ?? "";        if (finishReason !== "") {          await this.dealFinishReason(            finishReason,            _toolCalls,            messages,            onContentCallBack,            onCallToolsCallBack,            onCallToolResultCallBack,            callBackMessage,            controllerRef,            loop          );        }        let toolCalls = chunk.choices[0].delta.tool_calls;        /** 如果工具有值,则构建工具列表 */        if (toolCalls && toolCalls.length > 0) {          this.dealToolCalls(toolCalls, _toolCalls);        } else {          /** 否则流式展示消息内容 */          const { choices } = chunk;          await this.dealTextContent(choices, onContentCallBack, callBackMessage, loop);        }      }    } else {      console.error("Stream is not async iterable.");    }  }}

2.4 aichat-core使用案例

核心集成代码片段如下:

// 存储会话列表(默认default-0)const [conversations, setConversations] = useState(    DEFAULT_CONVERSATIONS_ITEMS); // 历史消息,一个对话对应一组历史消息const [messageHistory, setMessageHistory] = useState<Record<string, any>>(useMockData > 0 ? mockHistoryMessages : []);...// 实例化llmClient及初始化mcp服务(如果有mcp server 服务支撑的话)llmClient = new LLMClient(mcpServer, "demo", apiKey, baseUrl, modelName);await llmClient.initMcpServer();.../** 消息回调 (callBackMessage对象已经经过深拷贝)*/const onMessageContentCallBack = (callBackMessage: LLMCallBackMessage) => {    const _currentConversation = curConversation.current;    let _llmChoices = callBackMessage.choices;    let _messageId = callBackMessage.id;    if (!_llmChoices || _llmChoices.length == 0) {      return [];    }    setMessageHistory((sPrev) => ({      ...sPrev,      [_currentConversation]: sPrev[_currentConversation].map((message: LLMCallBackMessage) => {        if (_messageId != message.id) {          return message;        }        let _message: LLMCallBackMessage = {          id: _messageId,          timed: callBackMessage.timed,          model: callBackMessage.model,          role: callBackMessage.role,          typing: true,          loading: false,        }        /** 拿到原来的 */        let preMessageChoices = message.choices ?? [];        _llmChoices.forEach((updateChoice: LLMCallBackMessageChoice) => {          let _targets = preMessageChoices.filter((preChoice: any) => updateChoice.index == preChoice.index) ?? []          // 有值就动态修改值          if (_targets.length > 0) {            let _target = _targets[0];            let _reason_content = _target.reasoning_content ?? "";            let _content = _target.content ?? "";            _target.thinking = updateChoice.thinking;            if (updateChoice.reasoning_content && updateChoice.reasoning_content != "") {              _target.reasoning_content = _reason_content + updateChoice.reasoning_content;            }            if (updateChoice.content && updateChoice.content != "") {              _target.content = _content + updateChoice.content;            }            _target.tools = updateChoice.tools;            _target.usage = updateChoice.usage;          } else {            // 否则的话就添加一个            preMessageChoices.push(updateChoice)          }        })        // 最后别忘了给值(这个地方要用深拷贝)        _message.choices = JSON.parse(JSON.stringify(preMessageChoices));        return _message;      }),    }));};...// 发起流式聊天await llmClient.current.chatStreamLLMV2(    messages,    onMessageContentCallBack,    onCallToolCallBack,    onCallToolResultCallBack,    onInitCallBackMessage(messageId),    abortControllerRef);...

3. 最终效果图

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

aichat-core 前端 AI助手 LLM MCP
相关文章