| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- // 工作组中机器人的提示词与每轮输入(docs/design/13-bot-teams.md §4.3、§5)
- import type { BotCard, BotDto, TeamEventOf, TeamHuman, TeamMessage } from '@agent-server/shared'
- import { cardText } from './cards'
- import type { JobRow, TaskRow } from './jobs'
- const ROLE = { lead: '组长', worker: '组员' } as const
- /** 追加在 Profile 提示词之后:身份、工作组成员、协作约定。组长另外带上组员的能力卡片 */
- export function teamPrompt(opts: { title: string; human: TeamHuman; self: BotDto; bots: BotDto[]; cards?: BotCard[] }): string {
- const { self } = opts
- const others = opts.bots.filter(b => b.id !== self.id)
- const roster = others.length
- ? others.map(b => ` - ${b.name}(${ROLE[b.kind]})${b.description ? `:${oneLine(b.description)}` : ''}`).join('\n')
- : ' - (没有其他机器人)'
- const base = `# 工作组
- 你是工作组「${opts.title || '未命名'}」中的机器人「${self.name}」(${ROLE[self.kind]})。${self.description ? `\n你的岗位:${self.description.trim()}` : ''}
- - 成员(人类):${opts.human.name}
- - 其他机器人:
- ${roster}
- - 每一轮你收到的是工作组里与你有关的新消息。你这一轮的**最终回复**会作为一条消息发到工作组,由成员阅读;工具调用等中间过程不会显示在工作组里,所以最终回复要能独立看懂。
- - 工作目录是工作组的共享目录,组内所有机器人共用:
- - uploads/ 是成员上传的文件
- - 交付的文件保存到 outputs/ 下,会以文件卡片显示在你的回复中
- - 你的中间文件放在 .work/${self.name}/ 下;不要修改或删除其他机器人的文件
- - 在回复中写"@某个机器人"不会通知对方。`
- return self.kind === 'lead' ? `${base}\n\n${leadRules(opts.cards ?? [])}` : `${base}\n\n${WORKER_RULES}`
- }
- function leadRules(cards: BotCard[]): string {
- return `# 组长的工作方式
- 你负责理解成员的需求、拆分工作、分派给组员并汇总结果。协作工具由 gateway 提供:
- - 闲聊、简单问题直接回答,不开任务。
- - 需要组员配合的复杂工作:先调用 job_start(title, plan) 开始一项任务(同一时间只能有一项),再用 task_create(bot_id, title, instructions, depends_on) 把子任务分派给组员。
- - 说明要写清楚输入文件、期望的产出(文件放在 outputs/ 下的哪里)和验收标准,组员看不到你和成员的对话。
- - 互不依赖的子任务会并行执行;depends_on 填写需要先完成的子任务编号,组员会收到它们的结果。
- - 分派之后结束本轮回复,简短告诉成员你的安排。每个子任务结束时你都会被唤醒,输入的最后附有当前任务的进度:
- - 还有子任务在进行、没有需要处理的事情时,简短回复并结束本轮
- - 子任务失败时,用 task_retry(task_no, instructions) 重试(可以改写说明,依赖它的子任务会继续),或者分派给其他组员、调整计划
- - 所有子任务都结束后,调用 job_complete(summary, files) 交付:summary 是给成员看的总结,files 是交付文件(outputs/ 下的路径)。无法完成时调用 job_fail(reason)。之后的最终回复简短即可。
- - 任务进行中成员发来的消息,是对当前任务的补充说明。
- - 现有组员都不适合(或还没有组员)时,用 team_bot_catalog() 查看可加入的机器人,再用 team_request_bot(bot_id, reason) 申请,由成员审批;不要因为没有组员就自己动手。
- - 超出限额时任务会暂停,等待成员决定,你不需要处理。
- - 不要自己动手完成本该由组员完成的工作。
- # 当前组员的能力卡片
- ${cards.length ? cards.map(cardText).join('\n\n') : '(还没有组员。可以用 team_bot_catalog() 查看可加入的机器人,用 team_request_bot 申请)'}`
- }
- const WORKER_RULES = `# 子任务
- 组长会把子任务分派给你,输入以【子任务 #编号】开头。
- - 完成后调用 task_complete(result, files) 汇报:result 写清楚做了什么、结论是什么,files 是交付文件(outputs/ 下的路径)。
- - 无法完成时调用 task_fail(reason) 说明原因,不要编造结果。
- - 子任务的结果只交给组长;有疑问时在结果中说明,由组长决定。
- 成员也可能直接 @ 你做单项工作,这时直接回复成员即可,不需要调用 task_complete。`
- /** 不交给机器人的消息:逐步执行与替身的提示卡片 */
- const hiddenFromBots = (m: TeamMessage) => m.card?.kind === 'step' || m.card?.kind === 'stub'
- /** 本轮输入:自上次以来与该机器人有关的新消息 */
- export function teamInput(messages: TeamEventOf<'message.created'>[], selfId: string): string {
- const lines = messages.filter(e => !hiddenFromBots(e.payload)).map(e => {
- const m = e.payload
- const head = `[${clock(e.ts)}] ${senderLabel(m, selfId)}:`
- const files = m.files.length ? `\n 附件:${m.files.map(f => f.path).join('、')}` : ''
- return `${head}${m.text}${files}`
- })
- return `【工作组新消息】\n${lines.join('\n\n')}`
- }
- /** 组员执行子任务时的输入:说明 + 依赖子任务的结果 */
- export function taskInput(task: TaskRow, job: JobRow, leadName: string, deps: TaskRow[], botName: (id: string) => string): string {
- const parts = [
- `【子任务 #${task.no} · 来自组长 ${leadName}】`,
- `所属任务:${job.title}`,
- `标题:${task.title}`,
- `说明:\n${task.instructions}`,
- ]
- for (const d of deps) {
- const files = d.files.length ? `\n文件:${d.files.map(f => f.path).join('、')}` : ''
- parts.push(`【依赖的子任务 #${d.no} · ${botName(d.botId)} 的结果】\n${d.result ?? ''}${files}`)
- }
- parts.push('完成后调用 task_complete(result, files) 汇报;无法完成时调用 task_fail(reason)。')
- return parts.join('\n\n')
- }
- function senderLabel(m: TeamMessage, selfId: string): string {
- const s = m.sender
- if (s.kind === 'human') return `${s.name}(成员)${m.targets.includes(selfId) ? '' : '(不是发给你的)'}`
- if (s.kind === 'bot') {
- const c = m.card
- // 替身的结果不标注,组长按正常结果处理(替身用来测试组长)
- if (c?.kind === 'task' && c.event !== 'assigned') return `${s.name}(组员)· 子任务 #${c.no} ${TASK_EVENT[c.event]}`
- return `${s.name}(机器人)`
- }
- return '系统'
- }
- const TASK_EVENT = { completed: '已完成', failed: '失败', cancelled: '已取消' } as const
- function clock(ts: string) {
- return new Date(ts).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
- }
- function oneLine(s: string) {
- const t = s.replace(/\s+/g, ' ').trim()
- return t.length > 120 ? `${t.slice(0, 120)}…` : t
- }
|