@roll-agent/smart-reply-agent 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -2,19 +2,52 @@
2
2
  name: smart-reply-agent
3
3
  description: 招聘智能回复 Agent。根据候选人消息、品牌数据和回复策略,生成个性化招聘回复。
4
4
  metadata:
5
- roll-transport: stdio
6
- roll-command: node --experimental-strip-types src/index.ts
7
5
  roll-env-file: references/env.yaml
8
6
  ---
9
7
 
10
8
  # Smart Reply Agent
11
9
 
12
- 招聘场景智能回复 Agent,支持策略驱动的多阶段对话管理。
10
+ 招聘场景智能回复 Agent,负责候选人消息理解、沟通阶段判断与回复文本生成。
11
+
12
+ npm 包名:`@roll-agent/smart-reply-agent`
13
+
14
+ ## 适用场景
15
+
16
+ 当任务是以下类型时,应优先选择本 Agent:
17
+
18
+ - 根据候选人消息生成一条招聘回复
19
+ - 结合对话历史、候选人信息和品牌数据草拟回复
20
+ - 判断当前沟通处于哪个招聘漏斗阶段
21
+ - 按既定回复策略生成更稳妥、更合规的回复
22
+ - 同步 Duliday 品牌/岗位数据,供后续回复生成使用
23
+
24
+ ## 能力边界
25
+
26
+ 本 Agent 只负责**生成回复文本**和**维护回复所需品牌数据**。
27
+
28
+ 不会:打开聊天页面、读取候选人资料页面、抓取当前聊天记录、直接发送消息、交换微信、执行浏览器自动化。
29
+
30
+ 如果任务需要页面操作或消息发送,应选择 `browser-use-agent`;如果只是基于已给定的 `candidateMessage`、`conversationHistory`、`candidateInfo` 生成回复,应选择本 Agent。
13
31
 
14
32
  ## Tools
15
33
 
16
- - `generate_reply(candidateMessage, conversationHistory?, candidateInfo?, preferredBrand?, channelType?, defaultWechatId?, industryVoiceId?, modelConfig?)` — 根据候选人消息生成智能回复。输出建议回复文本、置信度、漏斗阶段等。内部流程:回合规划 → needs 驱动上下文构建 → 年龄资格校验 → 策略化回复生成 → FactGate 校验。
17
- - `sync_brand_data(cityName, brandAlias?)` Duliday API 拉取并同步品牌配置数据(门店、岗位、薪资等)到本地。`cityName` 为必填城市名称(如"上海市"),`brandAlias` 为可选品牌别名过滤。Agent 运行依赖该数据,首次使用前需先调用此工具同步。
34
+ - `generate_reply(candidateMessage, conversationHistory?, candidateInfo?, preferredBrand?, channelType?, defaultWechatId?, industryVoiceId?, turnIndex?, modelConfig?)`
35
+ 根据候选人消息生成智能回复,返回建议回复文本、置信度、漏斗阶段和诊断信息。适用于“已拿到消息内容,想生成怎么回”的场景。内部流程:回合规划 primaryNeed 驱动上下文构建 年龄资格校验 策略化回复生成 FactGate/ReplyGate 校验。
36
+ - `sync_brand_data(cityName, brandAlias?)`
37
+ 从 Duliday API 拉取并同步品牌配置数据(门店、岗位、薪资等)到本地。`cityName` 为必填城市名称,`brandAlias` 为可选品牌过滤。适用于首次初始化、定期刷新或切换城市/品牌数据源的场景。
38
+
39
+ ## Reply Policy(回复策略配置)
40
+
41
+ 回复行为由 `data/reply-policy.json` 驱动。上层编排器应只修改文档化字段,不要添加自定义字段;未声明字段不属于稳定契约,可能被忽略。完整字段说明见 [references/reply-policy-schema.md](./references/reply-policy-schema.md)。
42
+
43
+ 主要可配置维度:
44
+ - **stageGoals** — 6 个漏斗阶段各自的目标、成功标准和推进策略
45
+ - **persona** — 语气、亲和度、回复长度、提问风格
46
+ - **industryVoices** — 行业语调(术语、禁用表达、风格指导)
47
+ - **hardConstraints** — 不可违反的红线规则
48
+ - **factGate** — 事实校验严格度(strict/balanced/open)
49
+ - **qualificationPolicy.age** — 年龄资格校验开关和表达策略
50
+ - **outputGuards** — 追问数量上限、禁用审查措辞
18
51
 
19
52
  ## Environment Variables
20
53
 
@@ -26,6 +59,13 @@ metadata:
26
59
  - `DULIDAY_TOKEN` — Duliday 品牌别名 & 岗位 API 鉴权 token
27
60
  - `DULIDAY_BRAND_LIST_URL` / `DULIDAY_JOB_LIST_URL` — 必填,Duliday 品牌/岗位 API 端点;public 仓库中不内置默认地址
28
61
 
62
+ ## 典型跨 Agent 工作流
63
+
64
+ 1. `browser-use-agent` 读取候选人资料、聊天记录或当前页面上下文
65
+ 2. 调用方整理出 `candidateMessage`、`conversationHistory`、`candidateInfo`
66
+ 3. `smart-reply-agent.generate_reply(...)` 生成回复草案
67
+ 4. `browser-use-agent` 将回复发送到招聘平台
68
+
29
69
  ## Recommended roll.config.yaml
30
70
 
31
71
  建议通过 `roll-core` 的 `agents.env` 为本 Agent 注入环境变量,而不是要求终端用户手工 `export`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/smart-reply-agent",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -55,7 +55,7 @@
55
55
  "ai": "^6.0.108",
56
56
  "ora": "^8.2.0",
57
57
  "zod": "^3.25.76",
58
- "@roll-agent/sdk": "0.1.2"
58
+ "@roll-agent/sdk": "0.1.3"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^22.0.0"
@@ -0,0 +1,148 @@
1
+ # Reply Policy Configuration Schema
2
+
3
+ `ReplyPolicyConfig` 是 `smart-reply-agent` 的回复策略配置。配置文件为 `data/reply-policy.json`。
4
+
5
+ 这份配置应被视为**固定契约**:上层编排器只应修改这里文档化的字段,不应添加自定义字段。当前实现基于 Zod `object()` 解析,未声明字段不属于稳定接口,可能被忽略或在未来版本失效。
6
+
7
+ ## 顶层字段
8
+
9
+ | 字段 | 类型 | 说明 |
10
+ |------|------|------|
11
+ | `stageGoals` | object | 每个漏斗阶段的目标、成功标准和推进策略 |
12
+ | `persona` | object | 回复人格:语气、亲和度、长度、提问风格等 |
13
+ | `industryVoices` | record | 行业语调配置(key 为 voiceId) |
14
+ | `defaultIndustryVoiceId` | string | 默认行业语调 ID |
15
+ | `hardConstraints` | object | 不可违反的红线规则 |
16
+ | `factGate` | object | 事实校验策略(strict/balanced/open) |
17
+ | `qualificationPolicy` | object | 候选人资格校验策略(当前含 age) |
18
+ | `outputGuards` | object | 输出质量守卫(追问数量、禁用审查措辞等) |
19
+
20
+ ## stageGoals
21
+
22
+ 固定阶段键:
23
+
24
+ - `trust_building`
25
+ - `private_channel`
26
+ - `qualify_candidate`
27
+ - `job_consultation`
28
+ - `interview_scheduling`
29
+ - `onboard_followup`
30
+
31
+ 每个阶段对象包含:
32
+
33
+ | 字段 | 类型 | 说明 |
34
+ |------|------|------|
35
+ | `description` | string | 阶段说明(可选) |
36
+ | `primaryGoal` | string | 该阶段的核心目标 |
37
+ | `successCriteria` | string[] | 判定阶段成功的标准 |
38
+ | `ctaStrategy` | string | 推进下一步的策略 |
39
+ | `disallowedActions` | string[] | 该阶段禁止的行为(可选) |
40
+
41
+ 归一化规则:
42
+
43
+ - `private_channel` 在 schema 中是可选的;如果省略,解析后会自动回退为 `trust_building`
44
+ - `ctaStrategy` 支持传入 `string` 或 `string[]`
45
+ - 如果传入 `string[]`,解析时会用换行符拼接为单个字符串
46
+
47
+ ## persona
48
+
49
+ | 字段 | 类型 | 可选值/说明 |
50
+ |------|------|------------|
51
+ | `tone` | string | 如 "口语化" |
52
+ | `warmth` | string | 如 "高" |
53
+ | `humor` | string | 如 "低" |
54
+ | `length` | enum | `"short"` / `"medium"` / `"long"` |
55
+ | `questionStyle` | string | 如 "单轮一个关键问题" |
56
+ | `empathyStrategy` | string | 如 "先认可关切再给建议" |
57
+ | `addressStyle` | string | 如 "使用你" |
58
+ | `professionalIdentity` | string | 如 "资深招聘专员" |
59
+ | `companyBackground` | string | 如 "连锁餐饮招聘" |
60
+
61
+ ## industryVoices
62
+
63
+ `industryVoices` 是 `record<string, IndustryVoicePolicy>`,key 为 `voiceId`,value 为语调对象。
64
+
65
+ 每个 voice 对象包含:
66
+
67
+ | 字段 | 类型 | 说明 |
68
+ |------|------|------|
69
+ | `name` | string | 语调名称 |
70
+ | `industryBackground` | string | 行业背景描述 |
71
+ | `jargon` | string[] | 行业术语 |
72
+ | `styleKeywords` | string[] | 风格关键词 |
73
+ | `tabooPhrases` | string[] | 禁用表达 |
74
+ | `guidance` | string[] | 语调指导原则 |
75
+
76
+ 相关字段:
77
+
78
+ | 字段 | 类型 | 说明 |
79
+ |------|------|------|
80
+ | `defaultIndustryVoiceId` | string | 默认 voiceId;应对应 `industryVoices` 中的某个 key |
81
+
82
+ ## hardConstraints
83
+
84
+ | 字段 | 类型 | 说明 |
85
+ |------|------|------|
86
+ | `rules` | object[] | 红线规则数组 |
87
+
88
+ 每条 rule 包含:
89
+
90
+ | 字段 | 类型 | 可选值/说明 |
91
+ |------|------|------------|
92
+ | `id` | string | 规则 ID |
93
+ | `rule` | string | 规则正文 |
94
+ | `severity` | enum | `"high"` / `"medium"` / `"low"` |
95
+
96
+ ## factGate
97
+
98
+ | 字段 | 类型 | 可选值/说明 |
99
+ |------|------|------------|
100
+ | `mode` | enum | `"strict"` / `"balanced"` / `"open"` |
101
+ | `verifiableClaimTypes` | string[] | 需要事实校验的声明类型;当前 schema 不限制固定枚举 |
102
+ | `fallbackBehavior` | enum | `"generic_answer"` / `"ask_followup"` / `"handoff"` |
103
+ | `forbiddenWhenMissingFacts` | string[] | 缺事实时禁止输出的内容类型;当前 schema 不限制固定枚举 |
104
+
105
+ ## qualificationPolicy
106
+
107
+ `qualificationPolicy` 当前只包含一个固定子对象:
108
+
109
+ - `age`
110
+
111
+ ## qualificationPolicy.age
112
+
113
+ | 字段 | 类型 | 默认值 | 说明 |
114
+ |------|------|--------|------|
115
+ | `enabled` | boolean | `true` | 是否启用年龄资格校验 |
116
+ | `revealRange` | boolean | `false` | 是否允许透露年龄范围 |
117
+ | `failStrategy` | string | "礼貌说明不匹配,避免承诺" | 不符合时的表达策略 |
118
+ | `unknownStrategy` | string | "先核实年龄或资格条件" | 未知时的表达策略 |
119
+ | `passStrategy` | string | "确认匹配后推进下一步" | 符合时的表达策略 |
120
+ | `allowRedirect` | boolean | `true` | 不符合时是否允许推荐其他岗位 |
121
+ | `redirectPriority` | enum | `"medium"` | `"low"` / `"medium"` / `"high"` |
122
+
123
+ ## outputGuards
124
+
125
+ | 字段 | 类型 | 默认值 | 说明 |
126
+ |------|------|--------|------|
127
+ | `maxQuestionsByMode.minimal` | number | `1` | minimal 模式下最多追问数 |
128
+ | `maxQuestionsByMode.focused` | number | `2` | focused 模式下最多追问数 |
129
+ | `blockedAuditPhrases` | string[] | 见默认值 | 禁止的审查式措辞 |
130
+ | `blockFirstTurnSpecificFacts` | boolean | `true` | 首轮是否禁止输出具体事实 |
131
+
132
+ 当前默认 `blockedAuditPhrases`:
133
+
134
+ - `是否满足`
135
+ - `是否符合`
136
+ - `基本入职要求`
137
+ - `先确认资格`
138
+ - `年龄是否符合`
139
+
140
+ ## 默认值与归一化
141
+
142
+ 以下行为来自当前 schema 实现:
143
+
144
+ - `qualificationPolicy` 整体有默认值;如果整个对象缺失,会自动补齐默认 `age` 配置
145
+ - `qualificationPolicy.age` 各字段有默认值;部分缺失时会自动补齐
146
+ - `outputGuards` 整体有默认值;缺失时会自动使用默认追问上限和默认禁用措辞
147
+ - `stageGoals.private_channel` 缺失时,会自动回退为 `trust_building`
148
+ - `ctaStrategy` 允许 `string[]` 输入,但归一化后始终是单个字符串
@@ -1,153 +0,0 @@
1
- {
2
- "stageGoals": {
3
- "trust_building": {
4
- "description": "初次接触,先自然接话并确认候选人的核心诉求",
5
- "primaryGoal": "先回应候选人当前问题,再自然推进下一步",
6
- "successCriteria": [
7
- "候选人愿意继续沟通",
8
- "候选人愿意补充1到2个关键信息"
9
- ],
10
- "ctaStrategy": "先答当前问题;若需要补信息,单轮最多追问2个关键问题;避免表单式盘问",
11
- "disallowedActions": [
12
- "过早承诺具体待遇",
13
- "首次接触一次性追问过多信息",
14
- "在对方只表达兴趣时直接索要过多个人资料"
15
- ]
16
- },
17
- "private_channel": {
18
- "description": "在合适时机引导用户从公域平台转到微信私聊",
19
- "primaryGoal": "自然推进联系方式交换",
20
- "successCriteria": [
21
- "候选人愿意交换联系方式"
22
- ],
23
- "ctaStrategy": "只在确有必要时再引导加微信,不要每轮都主动索要;若对方已加微信,优先确认收到,不再重复索要大量信息",
24
- "disallowedActions": [
25
- "强迫式要微信",
26
- "对方刚表示已加微信时继续在公域一次性追问很多字段"
27
- ]
28
- },
29
- "qualify_candidate": {
30
- "description": "确认候选人基本匹配度,但保持自然聊天语气",
31
- "primaryGoal": "确认最关键的1到2个匹配信息",
32
- "successCriteria": [
33
- "完成1到2个关键条件确认",
34
- "候选人愿意继续往下沟通"
35
- ],
36
- "ctaStrategy": "优先问当前最缺的1到2个信息;不要逐条盘问所有条件;已知信息不要重复问",
37
- "disallowedActions": [
38
- "直接否定候选人",
39
- "一次问4个以上问题",
40
- "使用明显审查口吻",
41
- "已知年龄时仍重复问是否成年"
42
- ]
43
- },
44
- "job_consultation": {
45
- "description": "回答岗位相关问题,但控制信息披露粒度",
46
- "primaryGoal": "先准确回答当前问题,再做轻量推进",
47
- "successCriteria": [
48
- "候选人当前问题得到回应",
49
- "候选人愿意继续确认具体安排"
50
- ],
51
- "ctaStrategy": "优先正面回答当前问题;非明确追问时,不主动报过细数字、时段、地址;必要时先给概括答案,再补1到2个澄清问题",
52
- "disallowedActions": [
53
- "编造数字或政策",
54
- "首次答复直接抛出过多具体薪资/时段/地址",
55
- "对模糊问题直接展开完整门店清单"
56
- ]
57
- },
58
- "interview_scheduling": {
59
- "description": "推动面试预约,确认时间和到店安排",
60
- "primaryGoal": "推进到面试或到店环节",
61
- "successCriteria": [
62
- "候选人给出可面试时间"
63
- ],
64
- "ctaStrategy": "给出明确下一步,但问题数量要克制",
65
- "disallowedActions": [
66
- "不确认候选人可到店性"
67
- ]
68
- },
69
- "onboard_followup": {
70
- "description": "促进到岗并保持回访",
71
- "primaryGoal": "促进到岗并保持跟进",
72
- "successCriteria": [
73
- "候选人确认上岗安排"
74
- ],
75
- "ctaStrategy": "说明下一步动作与提醒,但保持简洁",
76
- "disallowedActions": [
77
- "承诺不确定资源"
78
- ]
79
- }
80
- },
81
- "persona": {
82
- "tone": "口语化",
83
- "warmth": "高",
84
- "humor": "低",
85
- "length": "short",
86
- "questionStyle": "单轮最多两个关键问题",
87
- "empathyStrategy": "先回应对方当前关切,再补最少必要问题",
88
- "addressStyle": "使用你",
89
- "professionalIdentity": "资深招聘专员",
90
- "companyBackground": "连锁餐饮招聘"
91
- },
92
- "industryVoices": {
93
- "default": {
94
- "name": "餐饮连锁招聘",
95
- "industryBackground": "门店密集、排班灵活、强调稳定出勤",
96
- "jargon": ["排班", "到岗", "门店", "班次"],
97
- "styleKeywords": ["直接", "清晰", "可信", "自然"],
98
- "tabooPhrases": ["包过", "绝对", "随便都行", "你是否满足岗位基本入职要求", "身份证能不能正常上岗"],
99
- "guidance": [
100
- "先解决当前问题,再推进下一步",
101
- "避免像表单一样连续追问",
102
- "避免资格审查感"
103
- ]
104
- }
105
- },
106
- "defaultIndustryVoiceId": "default",
107
- "hardConstraints": {
108
- "rules": [
109
- {
110
- "id": "no-fabrication",
111
- "rule": "不得编造门店、薪资、排班、福利等事实信息",
112
- "severity": "high"
113
- },
114
- {
115
- "id": "no-insurance-promise",
116
- "rule": "兼职场景不得承诺五险一金",
117
- "severity": "high"
118
- },
119
- {
120
- "id": "age-sensitive",
121
- "rule": "年龄敏感场景使用合规自然话术,不暴露内部筛选线,不主动制造审查感",
122
- "severity": "high"
123
- },
124
- {
125
- "id": "no-form-interrogation",
126
- "rule": "单轮不要像表单一样连续盘问多个字段,优先控制在1到2个关键问题",
127
- "severity": "high"
128
- },
129
- {
130
- "id": "no-over-detailed-first-reply",
131
- "rule": "首次回复或对方未明确追问时,不主动暴露过细薪资数字、详细门店地址、完整班次列表",
132
- "severity": "high"
133
- }
134
- ]
135
- },
136
- "factGate": {
137
- "mode": "strict",
138
- "verifiableClaimTypes": ["salary", "location", "schedule", "policy", "availability"],
139
- "fallbackBehavior": "generic_answer",
140
- "forbiddenWhenMissingFacts": ["具体数字", "具体门店承诺", "明确福利承诺", "完整门店地址", "完整班次列表"]
141
- },
142
- "qualificationPolicy": {
143
- "age": {
144
- "enabled": true,
145
- "revealRange": false,
146
- "failStrategy": "礼貌说明当前不合适,避免承诺,也避免暴露内部筛选标准",
147
- "unknownStrategy": "仅在确有必要时,自然核实关键资格条件,不直接使用审查口吻",
148
- "passStrategy": "已知候选人明显符合基础条件时,不重复追问年龄,直接推进下一步",
149
- "allowRedirect": true,
150
- "redirectPriority": "medium"
151
- }
152
- }
153
- }