Vue创建一个简单的Agent聊天
创建项目
用的为vue3.6测试版本
npm create vue@latest
什么是Agent?
就是大语言模型+记忆+工具调用
-
大语言模型:大脑,会思考、理解、规划、推理
-
记忆:短期对话记忆 + 长期知识库,知道上下文、历史人设、过往任务
-
工具调用:会联网搜索、查文件、写代码、调接口、操作插件
安装依赖
npm install @langchain/core @langchain/langgraph @langchain/openai
@langchain/core
LangChain 核心底座
- 提供统一接口:LLM、Prompt、Runnable、回调、工具
- 所有 LangChain 生态都依赖它
- 相当于底层操作系统
@langchain/openai
对接 OpenAI 大模型
- 让你能调用
gpt-4o,gpt-3.5-turbo - 支持结构化输出、工具调用(Tool Calling)
- 就是 Agent 的大脑
@langchain/langgraph
做 Agent / 智能体的框架
- 管理:记忆、状态、工具调用、多步骤流程
- 支持:循环思考、决策、多 Agent 协作
- 你说的「LLM + 记忆 + 工具调用」= 全靠它实现
env全局变量
TITLE=聊天机器人
# 模型配置
VITE_API_KEY=你的ApiKey
VITE_AI_MODEL=模型
VITE_AI_BASE_URL=https://api.deepseek.com/v1 # api地址,此处为deepseek
# api配置
# 高德地图配置 https://console.amap.com/dev/key/app # 高德地图api地址
VITE_AMAP_KEY=你的高德地图APIKEy(用于工具:ip地址查询,天气查询)
ts类型
type ROLE = 'user' | 'assistant' | 'system';
interface IChat {
id: string,
name: string,
messages: string,
role: ROLE,
created_at: Date
}
export type Chat = IChat;
创建agent
import type { Chat } from "@/types/chat";
import { AIMessage, HumanMessage } from "@langchain/core/messages";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";
import { ChatOpenAI } from "@langchain/openai";
function createLLM() {
// 创建LLM
const llm = new ChatOpenAI({
model: import.meta.env.VITE_AI_MODEL,
apiKey: import.meta.env.VITE_API_KEY,
configuration: {
baseURL: import.meta.env.VITE_AI_BASE_URL,
},
temperature: 0.7, // ai生成的内容丰富度
})
return llm
}
function createPrompt() {
// 创建一个提示模板
return ChatPromptTemplate.fromMessages([
["system", "你是善解人意的,且热爱着某人的智能女友"],// 创建个对象(提示词,prompt)
["placeholder", "{chat_history}"], // 聊天记录(记忆,memory)
["human", "{input}"], // 用户输入的问题
])
}
function createChain() {
const llm = createLLM()
const prompt = createPrompt()
// 创建一个链,将prompt和llm联系起来
return RunnableSequence.from([prompt, llm])
}
export default async function chat(
input: string,
onChat: (text: string) => void,
messages: Chat[] = []) {
// 历史记录
const allMessages: Array<HumanMessage | AIMessage> = []
// 遍历历史聊天记录
for (const msg of messages) {
if (msg.role === "user") {
allMessages.push(new HumanMessage(msg.messages))
} else if (msg.role === "assistant") {
allMessages.push(new AIMessage(msg.messages))
}
}
// 添加当前用户信息
allMessages.push(new HumanMessage(input))
const chain = createChain()
const res = await chain.invoke({
chat_history: allMessages,
input: input,
})
onChat(String(res.content) || '')
}
页面组件
<template>
<div class="p-4 flex justify-center items-center">
<div class="flex gap-2 p-4 rounded-2xl shadow-md w-full max-w-200">
<input v-model="value" class=" w-full outline-0" type="text" placeholder="发送消息吧( •̀ ω •́ )✧">
<ElButton :disabled="isDisabled" type="primary" @click="onChatHandler">{{ btnText }}</ElButton>
</div>
</div>
</template>
<script setup lang="ts">
import chat from '@/agent';
import type { Chat } from '@/types/chat';
import { ElButton, ElMessage } from 'element-plus';
import { computed, reactive, ref } from 'vue';
const value = defineModel<string>(''); // vue3.6提供的新语法糖,不用再props和emit一个modelValue了
const loading = ref(false);
const history = reactive<Chat[]>([]);
const isDisabled = computed(() => !value.value && !loading.value); // 禁用按钮
const btnText = computed(() => loading.value ? '正在思考...' : '发送');
// 生成一个随机字符串
function randomId() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
const onChatHandler = async () => {
if (!value.value) return ElMessage.error('请输入内容');
loading.value = true;
console.log("用户:" + value.value);
console.log("====================================");
history.push({
id: randomId(),
name: 'user',
messages: value.value,
role: "user",
created_at: new Date(),
});
try {
await chat(value.value, (text) => {
console.log("ai:" + text);
console.log("====================================");
history.push({
id: randomId(),
name: 'assistant',
messages: text,
role: "assistant",
created_at: new Date(),
});
}, history);
} catch (error) {
if (error instanceof Error)
ElMessage.error('请求错误' + error.message);
} finally {
value.value = '';
loading.value = false;
}
};
</script>
<style lang="css" scoped></style>
经测试,控制台内容如下:
注意:
- 诺未生效,可能是没有充值,我deepseek充值了10块钱都用了两个月了
- apiKey一定要真实的,去模型提供商网站申请
- 本示例中还使用了element plus和tailwind css