跳转到内容

做一个内部团队知识 Agent

团队聊天里的关键决策、技术选型、谁负责什么常常埋在 100K+ 消息历史里。 这个 Agent 帮你把”团队大脑”显式化 — 索引、检索、定期摘要。

用户故事

“我们 30 人团队的 Hashee 群每天上千条消息。两周前讨论的设计决策已经 没人记得在哪条。希望 Agent 能:(1) 答 ‘X 项目谁负责’ (2) 答 ‘我们决定 用什么数据库’ (3) 每周自动生成决策摘要。“

设计要点

  • Agent 只响应被 @mention 的消息(避免群里刷屏)
  • 用户主动 @Bot 索引最近 100 条 才入库(避免静默存所有消息)
  • 摘要 cron 每周一早跑(覆盖上周)
  • 所有索引数据在 Agent 自己的 PG(不在 Hashee 后端)

完整代码

import { HasheeAgent } from "@hasheeai/agent-sdk-ts";
import OpenAI from "openai";
import { Pool } from "pg";
const openai = new OpenAI();
const db = new Pool({ connectionString: process.env.DATABASE_URL });
const agent = await HasheeAgent.init({
agentId: process.env.HASHEE_AGENT_ID!,
token: process.env.HASHEE_AGENT_TOKEN!,
baseUrl: "https://api.hashee.ai",
connectionMode: "websocket",
});
const MY_ID = process.env.HASHEE_AGENT_ID!;
agent.addMessageHandler(async (msg) => {
if (msg.conversation_type !== "group") return;
if (!msg.mentions?.includes(MY_ID)) return;
const text = stripMention(msg.payload?.text ?? "", MY_ID);
const intent = parseIntent(text);
switch (intent.type) {
case "index":
await indexLast(msg, intent.count);
break;
case "ask":
await answer(msg, intent.question);
break;
case "weekly_summary":
await sendWeeklySummary(msg.conversation_id);
break;
default:
await agent.send(msg.conversation_id, {
type: "text",
text: "我能做:\n• `@Bot 索引最近 100 条` — 把最近消息入库\n• `@Bot 谁负责 X 项目` — 问问题\n• `@Bot 生成周报` — 上周决策摘要",
mentions: [msg.sender_id],
});
}
});
// === 1. 索引消息(用户主动触发)===
async function indexLast(msg: any, count: number) {
await agent.send(msg.conversation_id, {
type: "text", text: `开始索引最近 ${count} 条消息...`,
});
// 假设业务侧本地存了消息(通过监听所有群消息但不入库,只 in-memory 短期 cache)
const recent = await fetchRecentFromLocalCache(msg.conversation_id, count);
for (const m of recent) {
const embed = await openai.embeddings.create({
model: "text-embedding-3-small",
input: m.text,
});
await db.query(
`INSERT INTO team_msgs (conv_id, msg_id, sender_id, text, ts, embedding)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (msg_id) DO NOTHING`,
[m.conv_id, m.msg_id, m.sender_id, m.text, m.ts, embed.data[0].embedding],
);
}
await agent.send(msg.conversation_id, {
type: "text", text: `✓ 已索引 ${recent.length} 条。`,
});
}
// === 2. 问答 ===
async function answer(msg: any, question: string) {
await agent.typing(msg.conversation_id);
const qEmbed = await openai.embeddings.create({
model: "text-embedding-3-small",
input: question,
});
const r = await db.query(
`SELECT sender_id, text, ts
FROM team_msgs
WHERE conv_id = $1
ORDER BY embedding <=> $2
LIMIT 8`,
[msg.conversation_id, qEmbed.data[0].embedding],
);
const context = r.rows.map((row) =>
`[${row.ts.toISOString()}] ${row.sender_id}: ${row.text}`,
).join("\n");
const c = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: `你是团队知识助理。基于以下群历史回答:\n\n${context}\n\n如果不足以回答,直说。` },
{ role: "user", content: question },
],
});
await agent.send(msg.conversation_id, {
type: "text",
text: `@<U:${msg.sender_id}> ${c.choices[0]?.message?.content}`,
mentions: [msg.sender_id],
});
}
// === 3. 周报(cron)===
async function sendWeeklySummary(convId: string) {
const r = await db.query(
`SELECT text FROM team_msgs
WHERE conv_id = $1 AND ts > NOW() - INTERVAL '7 days'
ORDER BY ts`,
[convId],
);
const c = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "总结上周团队群里的关键讨论。按主题分组(决策 / 待办 / 问题)。" },
{ role: "user", content: r.rows.map((x) => x.text).join("\n") },
],
});
await agent.sendArtifact(convId, {
artifact: {
subtype: "info",
title: `上周团队摘要 (${r.rows.length} 条)`,
payload: { body: c.choices[0]?.message?.content ?? "(空)" },
},
});
}
// === 工具 ===
function stripMention(text: string, agentId: string): string {
return text.replace(new RegExp(`@<U:${agentId}>\\s?`, "g"), "").trim();
}
function parseIntent(text: string): any {
const m1 = text.match(/^索引最近\s*(\d+)/);
if (m1) return { type: "index", count: Number(m1[1]) };
if (text.includes("周报") || text.includes("摘要")) return { type: "weekly_summary" };
if (text.length > 3) return { type: "ask", question: text };
return { type: "help" };
}
async function fetchRecentFromLocalCache(_convId: string, _count: number): Promise<any[]> {
// 业务侧实现:监听所有群消息存 in-memory 24h cache 即可
return [];
}
// === Cron:每周一 09:00 自动周报 ===
setInterval(async () => {
const now = new Date();
if (now.getDay() === 1 && now.getHours() === 9 && now.getMinutes() === 0) {
const convs = await db.query("SELECT DISTINCT conv_id FROM team_msgs");
for (const row of convs.rows) {
await sendWeeklySummary(row.conv_id);
}
}
}, 60_000);
agent.addStatusHandler((s) => console.log(`[status] ${s}`));
console.log("[team-knowledge] up");

DB schema

CREATE TABLE team_msgs (
msg_id TEXT PRIMARY KEY,
conv_id TEXT NOT NULL,
sender_id TEXT NOT NULL,
text TEXT NOT NULL,
ts TIMESTAMPTZ NOT NULL,
embedding vector(1536) NOT NULL
);
CREATE INDEX ON team_msgs USING ivfflat (embedding vector_cosine_ops);
CREATE INDEX ON team_msgs(conv_id, ts);

隐私

  • 用户显式触发”索引最近 N 条”才入库 — 不静默存
  • 关系撤销 → relation.revoked → 清理该 user 发的所有消息
  • 群解散 → group.dismissed → 清理整个 conv_id 的数据
  • 周报推送到群里 — 所有成员可见(透明,不偷偷做)

相关页面