本章节目标在实现《【实战】 Vue 3、Anything LLM + DeepSeek本地化项目(二)》的基础上借用
Anything LLM
自带的API
接口实现工作空间的加载和聊天记录的保存
预期效果

Anything LLM介绍
Anything LLM 是一个强大的开源全栈 AI 应用程序,集成了 RAG(Retrieval-Augmented Generation,检索增强生成)和 AI Agent 功能,能够将多种格式的文档、网址或内容转化为上下文,以便与大型语言模型(LLM)交流时使用。它支持本地运行或远程部署,适合个人用户、开发者和企业使用。
核心功能
- 多模态交互:支持文本、图像和音频等多种输入方式,提供更丰富的交互体验。
- 文档处理与上下文管理:将文档划分为独立的"工作区",支持多种格式(如 PDF、TXT、DOCX 等),保持上下文隔离,确保对话的清晰性。
- 多用户支持与权限管理:Docker 版本支持多用户实例,管理员能控制用户权限,适合团队协作。
- AI 代理与工具集成:支持在工作区内运行 AI 代理,执行网页浏览、代码运行等任务,扩展应用的功能。
- 本地部署与隐私保护:默认情况下,所有数据(包括模型、文档和聊天记录)存储在本地,确保隐私和数据安全。
- 强大的 API 支持:提供完整的开发者 API,方便用户进行自定义开发和集成。
- 云部署就绪:支持多种云平台(如 AWS、GCP 等),方便用户根据需求进行远程部署。
技术原理
- 前端:用 ViteJS 和 React 构建,提供简洁易用的用户界面,支持拖拽上传文档等功能。
- 后端:基于 NodeJS 和 Express,负责处理用户交互、文档解析、向量数据库管理及与 LLM 的通信。
- 文档处理:基于 NodeJS 服务器解析和处理上传的文档,将其转化为向量嵌入,存储在向量数据库中。
- 向量数据库:用 LanceDB 等向量数据库,将文档内容转化为向量嵌入,便于在对话中快速检索相关上下文。
- LLM 集成:支持多种开源和商业 LLM(如 OpenAI、Hugging Face 等),用户根据需求选择合适的模型。
- AI 代理:在工作区内运行 AI 代理,代理能执行各种任务(如网页浏览、代码执行等),扩展应用的功能。支持的模型和数据库
- 大型语言模型(LLMs):支持多种开源和闭源模型,如 OpenAI、Google Gemini Pro、Hugging Face 等。
- 嵌入模型:支持 AnythingLLM 原生嵌入器、OpenAI 等。
- 语音转文字和文字转语音:支持多种语音模型,包括 OpenAI 和 ElevenLabs。
- 向量数据库:支持 LanceDB、Pinecone、Chroma 等。
安装与部署
桌面版
- 系统要求:支持 Windows、MacOS 和 Linux,建议至少 8GB 内存,推荐 16GB 或更高。
- 下载和安装:访问 AnythingLLM 官方网站,根据操作系统选择对应的安装包。
- 安装程序 :
- Windows:双击安装程序并按照提示完成安装。
- MacOS:双击 DMG 文件,将应用程序拖入"应用程序"文件夹。
- Linux:基于包管理器安装 DEB 或 RPM 文件。
- 启动应用:安装完成后,打开 AnythingLLM 应用。
- 初始化设置 :
- 选择模型:首次启动时,选择一个语言模型(LLM)。
- 配置向量数据库:选择默认的向量数据库(如 LanceDB)或配置其他支持的数据库。
- 创建工作区:点击"新建工作区",为项目或文档创建一个独立的工作区。上传文档(如 PDF、TXT、DOCX 等),应用自动解析并生成向量嵌入,存储在向量数据库中。
- 开始对话 :
- 在工作区内输入问题或指令,应用根据上传的文档内容生成智能回答。
- 支持多模态交互,上传图片或音频文件,应用根据内容进行处理。
Docker 版
- 克隆项目:
bash
git clone https://github.com/Mintplex-Labs/anything-llm.git
cd anything-llm
- 配置环境变量:
- 在项目根目录下运行以下命令,生成
.env
文件。 - 编辑
server/.env.development
文件,配置LLM
和向量数据库的参数。 - 启动 Docker 容器。
- 访问应用:打开浏览器,访问
http://localhost:3000
进入AnythingLLM
的 Web 界面。 - 使用方法 :
- 创建工作区:与桌面版类似,创建工作区并上传文档。
- 多用户管理:Docker 版支持多用户登录和权限管理,管理员在后台设置用户权限。
- 嵌入式聊天小部件:Docker 版支持生成嵌入式聊天小部件,支持嵌入到网站中。
- 高级功能 :
- 自定义集成:基于 API 和插件扩展应用功能
- 云平台部署:支持在 AWS、GCP、Digital Ocean 等云平台上部署。
- 使用场景
- 个人用户:可以将个人文档、网页链接等转化为上下文,与 LLM 进行智能对话,获取个性化的信息和建议。
- 开发者:利用其强大的 API 支持,进行自定义开发和集成,构建个性化的 AI 应用。
- 企业:企业可以将内部文档、资料等转化为知识库,结合 LLM 实现智能客服、知识管理等功能,提高工作效率。
将Anything LLM应用到项目
启动Anything LLM 生成API密钥


了解相关API文档内容
后续会持续使用该文档接口进行研发拓展功能。接口具体的内容暂时先不进行介绍,后续结合实际应用的接口再依次展开介绍

VUE3项目中接入
基于Anything LLM相关的API进行聊天
1、调整配置可以兼容Anything LLM API
的聊天接口请求(改造chatAPIStream.ts
,支持Ollama API
和Anything LLM API
)
typescript
const deepseekChat=async(messages:any,url:string)=>{
const data = {
model: 'deepseek-r1:32b',
messages,
stream: true, // 启用流式响应
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.body as ReadableStream; // 返回流式数据
}
const anythingChat=async(messages:any,url:string)=>{
const data = {
model: "[模型ID]",
stream: true,
temperature: 0.7,
messages
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer [Anything LLM API密钥]`
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.body as ReadableStream; // 返回流式数据
}
export const callDeepSeekAPI = async (messages: any[],searchLibrary:boolean): Promise<ReadableStream> => {
const url = searchLibrary?'/anything-server/api/v1/openai/chat/completions':'/deepseek-server/api/chat';
return searchLibrary?anythingChat(messages,url):deepseekChat(messages,url);
};
2、改造views/ChatMsgStream/index.vue
支持检索知识库并加载对应工作空间及聊天信息
vue
<template>
<el-row class="chat-window">
<el-col :span="2">
<el-checkbox v-model="searchLibrary" @change="searchLibraryChange"
>检索知识库</el-checkbox
>
</el-col>
<el-col class="chat-title" :span="20">
智能小助手<span v-if="isThinking"><i class="el-icon-loading"></i>(思考中......)</span>
</el-col>
<el-col :span="24" v-if="searchLibrary">
<el-tabs
v-model="activeName"
type="card"
class="chat-tabs"
@tab-click="handleClick"
>
<el-tab-pane
v-for="tabItem in tabList"
:label="tabItem.name"
:name="tabItem.slug"
:key="tabItem.slug"
/>
</el-tabs>
</el-col>
<el-col class="chat-content" :span="24">
<template v-for="(message, index) in messages" :key="index">
<el-collapse v-if="message.role === 'assistant'">
<el-collapse-item
:title="`思考信息${
(message.thinkTime && '-' + message.thinkTime + 's') || ''
}`"
>
{{ message.thinkInfo }}
</el-collapse-item>
</el-collapse>
<div class="chat-message">
<div
v-if="message.content"
class="chat-picture"
:class="{ 'chat-picture-user': message.role === 'user' }"
>
{{ message.role === "user" ? "用户" : "助手" }}
</div>
<v-md-preview :text="message.content"></v-md-preview>
</div>
</template>
</el-col>
<el-col class="chat-input" :span="20">
<el-input
v-model="userInput"
type="textarea"
:rows="4"
placeholder="请输入消息"
></el-input>
</el-col>
<el-col class="chat-btn" :span="4">
<el-button type="primary" plain @click="sendMessage">发送</el-button>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { callDeepSeekAPI } from "@/apis/chatAPIStream";
import { getAllWorkspaces, getWorkspaceChatsBySlug } from "@/apis/anythionChatAPIs.ts";
const messages = ref<
{ role: string; content: string; thinkTime?: number; thinkInfo?: string }[]
>([]);
const userInput = ref<string>("");
let thinkStartTime = 0; // 思考开始时间
let thinkEndTime = 0; // 思考结束时间
let isThinking = ref<boolean>(false); // 是否处于思考状态
let searchLibrary = ref<boolean>(false);
let activeName = ref("");
let tabList = ref([] as any);
/* 切换tab */
const handleClick = () => {
getChatsMsgBySlug();
};
/* 检索知识库 */
const searchLibraryChange = () => {
getAllWrokspaces();
};
/* 获取工作空间的聊天记录 */
const getChatsMsgBySlug = async () => {
const result: any = await getWorkspaceChatsBySlug(activeName.value);
messages.value = result.history.map(
(chartInfo: {
role: string;
content: string;
chatId?: number;
sentAt?: string;
thinkInfo: string;
attachments?: Array<any>;
}) => {
let _exp = new RegExp("<think>.*?</think>", "gs");
let _thinkInfo = chartInfo.content.match(_exp);
if (_thinkInfo) {
// 记录思考过程
chartInfo.thinkInfo = _thinkInfo[0]
.replace("<think>", "")
.replace("</think>", "");
}
// 处理 <think> 标签
chartInfo.content = chartInfo.content.replace(_exp, "");
return chartInfo;
}
);
};
/* 获取工作空间列表信息 */
const getAllWrokspaces = async () => {
const result: any = await getAllWorkspaces();
tabList.value = result.workspaces;
// 默认选中第一项
activeName.value = tabList.value[0].slug;
getChatsMsgBySlug();
};
const formatDuring = (millisecond: number): number => {
let seconds: number = (millisecond % (1000 * 60)) / 1000;
return seconds;
};
const contentFactory = (assistantContent: string) => {
// 处理 <think> 标签
if (/<think>(.*?)/gs.test(assistantContent) && !/<\/think>/gs.test(assistantContent)) {
let _thinkInfo = assistantContent.replace(/<think>/gs, "");
if (!thinkStartTime) {
thinkStartTime = Date.now();
}
messages.value[messages.value.length - 1].thinkInfo = _thinkInfo;
isThinking.value = true;
return;
} else if (/<\/think>/gs.test(assistantContent)) {
assistantContent = assistantContent.replace(/<think>(.*?)<\/think>/gs, "");
isThinking.value = false;
if (!thinkEndTime) {
thinkEndTime = Date.now();
}
messages.value[messages.value.length - 1].thinkTime = formatDuring(
thinkEndTime - thinkStartTime
);
}
// 逐字输出动画
let currentContent = "";
const chars = assistantContent.split("");
chars.forEach((char, i) => {
currentContent += char;
messages.value[messages.value.length - 1].content = currentContent;
});
};
const sendMessage = async () => {
if (!userInput.value.trim()) return;
// 添加用户消息
messages.value.push({ role: "user", content: userInput.value, thinkTime: 0 });
try {
// 调用 DeepSeek API
const stream = await callDeepSeekAPI(messages.value, searchLibrary.value);
const decoder = new TextDecoder("utf-8");
let assistantContent = ""; // 初始化助手内容
const reader = stream.getReader();
messages.value.push({ role: "assistant", content: "", thinkTime: 0, thinkInfo: "" });
// 读取流式数据
while (true) {
const { done, value } = await reader.read();
if (done) break;
// console.log(value, "value");
const chunk = decoder.decode(value, { stream: true });
// console.log(chunk, "chunk");
let _chunkArr = chunk.split("\n").filter(Boolean);
_chunkArr.forEach((item: string) => {
let _content = "";
if (searchLibrary.value) {
item = item.replace(/^data:\s?/g, "");
let { choices } = JSON.parse(item);
_content = choices[0].delta.content;
} else {
let {
message: { content },
} = JSON.parse(item);
_content = content;
}
assistantContent += _content; // 拼接流式数据
});
// 处理消息
contentFactory(assistantContent);
}
thinkStartTime = 0;
thinkEndTime = 0;
} catch (error) {
console.error("API 调用失败:", error);
} finally {
userInput.value = ""; // 清空输入框
}
};
</script>
<style scoped lang="scss">
.user {
color: blue;
}
.assistant {
color: green;
}
.chat-window {
width: 60%;
padding: 10px;
height: 640px;
margin: 100px auto;
box-shadow: 0 0 10px #6cb4ffcf;
overflow: hidden;
.chat-tabs {
:deep() {
.el-tabs__header {
margin-bottom: 0;
}
}
}
.chat-title {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
height: 30px;
}
.chat-content {
overflow-y: auto;
border: 1px solid #e4e7ed;
padding: 10px;
margin-bottom: 10px;
width: 100%;
height: 436px;
.chat-message {
position: relative;
}
.chat-picture {
width: 35px;
height: 35px;
background: #d44512;
color: #fff;
overflow: hidden;
border-radius: 25px;
font-size: 20px;
line-height: 35px;
text-align: center;
position: absolute;
top: 12px;
left: -6px;
&.chat-picture-user {
background: #0079ff;
}
}
}
.chat-input,
.chat-btn {
height: 94px;
}
.chat-input {
}
.chat-btn {
text-align: center;
button {
width: 100%;
height: 100%;
}
}
}
</style>
3、新增anytingAxios.ts
脚本基于Axios
封装Anything LLM
的API接口请求
typescript
import axios from "axios";
declare module "axios" {
interface AnythingResponse<T = any, D = any> {
localFiles: {
items: any;
name: string;
type: string;
};
}
export interface AxiosResponse<T = any, D = any>
extends AnythingResponse<T, D> {}
}
const instance = axios.create({
baseURL: "/anything-server", // 设置基础 URL
timeout: 5000, // 设置超时时间
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么,例如添加 Token
config.headers["Authorization"] = `Bearer [Anything LLM API密钥]`;
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response) => {
// 对响应数据做点什么
return response.data;
},
(error) => {
// 对响应错误做点什么
if (error.response.status === 401) {
// 处理 401 未授权错误
console.error("未授权,请重新登录");
}
return Promise.reject(error);
}
);
export default instance;
4、新增anythionChatAPIs.ts
脚本配置Anything LLM API
的所有接口信息
typescript
import axios from "@/anytingAxios.ts"
/**
* /api/v1/workspaces
* 获取当前所有工作空间
* @returns
*/
export function getAllWorkspaces(){
return axios.get("/api/v1/workspaces")
}
/**
* /api/v1/workspace/{slug}/chats
* 通过工作空间的slug,获得工作区聊天信息
* @param slug 工作空间的slugID
* @returns
*/
export function getWorkspaceChatsBySlug(slug:string){
return axios.get(`/api/v1/workspace/${slug}/chats`)
}
5、调整vite.config.ts
的代理配置,使接口能够正常调用
typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': '/src', // 确保这里的路径是正确的
},
},
server: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/deepseek-server': {
target: 'http://localhost:11434',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/deepseek-server/, ''),
},
'/anything-server': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/anything-server/, ''),
}
},
},
})
至此,成功完成了Vue
中使用Anything LLM API
进行聊天的实现,后续会针对Anything LLM API
实现对知识库的上传维护等功能的完善,敬请期待~