技术选型
前端
- Vue3 + Vite
后端
- Python + FastAPI
模型接入
- OpenAI Python SDK + Moonshot base_url
Moonshot 支持使用 OpenAI SDK 进行接入,并通过 base_url 指向 Kimi API。(platform.moonshot.cn)
目录结构
ai-companion-agent/
├─ server/
│ ├─ app.py
│ ├─ requirements.txt
│ └─ .env
└─ web/
└─ 先用 vite 创建
第一步:后端
1)创建 server/requirements.txt
fastapi
uvicorn
python-dotenv
openai
2)创建 server/.env
MOONSHOT_API_KEY=你的月之暗面API_KEY
MOONSHOT_BASE_URL=https://api.moonshot.cn/v1
MOONSHOT_MODEL=kimi-k2-0905-preview
3)创建 server/app.py
import os
from typing import List, Literal, Optional
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from openai import OpenAI
load_dotenv()
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
client = OpenAI(
api_key=os.getenv("MOONSHOT_API_KEY"),
base_url=os.getenv("MOONSHOT_BASE_URL", "https://api.moonshot.cn/v1"),
)
MODEL_NAME = os.getenv("MOONSHOT_MODEL", "kimi-k2-0905-preview")
class Message(BaseModel):
role: Literal["system", "user", "assistant"]
content: str
class ChatRequest(BaseModel):
messages: List[Message]
session_id: Optional[str] = None
class ChatResponse(BaseModel):
reply: str
@app.get("/health")
def health():
return {"ok": True}
@app.post("/api/chat", response_model=ChatResponse)
def chat(req: ChatRequest):
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=[m.model_dump() for m in req.messages],
temperature=0.7,
)
reply = completion.choices[0].message.content or ""
return ChatResponse(reply=reply)
4)启动
进入 server 目录:
pip install -r requirements.txt
uvicorn app:app --reload --port 8000
浏览器打开:
http://127.0.0.1:8000/health
说明后端起来了。
第二步:前端创建聊天页面
1)创建项目
npm create vite@latest web
选择:
Vue
JavaScript
然后:
cd web
npm install
npm install axios
2)改 web/src/App.vue
<template>
<div class="page">
<div class="container">
<h1>AI Companion Agent</h1>
<div class="chat-box">
<div
v-for="(item, index) in messages"
:key="index"
:class="['msg', item.role]"
>
<div class="role">{{ item.role === 'user' ? '我' : 'AI' }}</div>
<div class="content">{{ item.content }}</div>
</div>
<div v-if="loading" class="msg assistant">
<div class="role">AI</div>
<div class="content">思考中...</div>
</div>
</div>
<div class="input-area">
<textarea
v-model="inputValue"
placeholder="输入你想说的话"
@keydown.enter.exact.prevent="sendMessage"
/>
<button :disabled="loading || !inputValue.trim()" @click="sendMessage">
发送
</button>
</div>
</div>
</div>
</template>
<script setup>
import axios from 'axios'
import { ref } from 'vue'
const inputValue = ref('')
const loading = ref(false)
const messages = ref([
{
role: 'system',
content: '你是一个温柔、聪明、会长期陪伴用户的AI伙伴。',
},
{
role: 'assistant',
content: '你好,我已经准备好了。你今天想聊什么?',
},
])
const sendMessage = async () => {
const text = inputValue.value.trim()
if (!text || loading.value) return
messages.value.push({
role: 'user',
content: text,
})
inputValue.value = ''
loading.value = true
try {
const res = await axios.post('http://127.0.0.1:8000/api/chat', {
messages: messages.value,
})
messages.value.push({
role: 'assistant',
content: res.data.reply,
})
} catch (error) {
messages.value.push({
role: 'assistant',
content: '请求失败,请检查后端或API Key配置。',
})
console.error(error)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24px;
box-sizing: border-box;
}
.container {
width: 900px;
margin: 0 auto;
background: #fff;
border-radius: 16px;
padding: 24px;
box-sizing: border-box;
}
h1 {
margin: 0 0 20px;
}
.chat-box {
height: 520px;
overflow-y: auto;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
background: #fafafa;
}
.msg {
margin-bottom: 16px;
}
.msg.system {
display: none;
}
.role {
font-size: 12px;
color: #666;
margin-bottom: 6px;
}
.content {
display: inline-block;
max-width: 75%;
line-height: 1.7;
padding: 12px 14px;
border-radius: 12px;
word-break: break-word;
}
.user .content {
background: #dbeafe;
}
.assistant .content {
background: #f3f4f6;
}
.input-area {
margin-top: 16px;
display: flex;
gap: 12px;
}
textarea {
flex: 1;
min-height: 100px;
resize: vertical;
border: 1px solid #d1d5db;
border-radius: 12px;
padding: 12px;
font-size: 14px;
outline: none;
}
button {
width: 100px;
border: none;
border-radius: 12px;
background: #111827;
color: #fff;
cursor: pointer;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>
3)启动
npm run dev
粗糙但是 能用
插曲 账户没钱了 直接 失败
现在这个版本拿到了什么
这个 V1 虽然简单,但已经有 3 个关键能力:
1. 真正接上了 Kimi
Moonshot 官方支持 OpenAI SDK 兼容接入,这样你后面切模型、加 tool calling 都很方便。(platform.moonshot.cn)
2. 已经是多轮对话
因为你把整个 messages 列表持续传给模型了。Moonshot 官方多轮对话指南也是这个思路。(platform.moonshot.cn)
3. 后续能自然扩展到 Agent
Moonshot 当前文档支持 tool calling,后续你要做搜索、记忆检索、天气工具都可以往上加。(platform.moonshot.cn)