feat: 添加智能体交流风格选项

- 将"模型行为"标签改为"智能体行为"
- 新增"智能体交流风格"二选一选项(默认/拟人)
- 拟人风格会注入human_like_style.txt提示词
- manage_personalization工具支持communication_style字段
- 添加user-pen.svg图标用于个性化工具和分类
This commit is contained in:
JOJO 2026-04-14 01:47:06 +08:00
parent 13400f62d5
commit 916f68e8e9
9 changed files with 116 additions and 9 deletions

View File

@ -792,7 +792,7 @@ class MainTerminalToolsDefinitionMixin:
"type": "function",
"function": {
"name": "manage_personalization",
"description": "管理用户个性化设置。支持读取所有配置或更新单个字段。可修改字段self_identifyAI自称最多20字、user_nameAI如何称呼用户最多20字、profession用户职业最多20字、tone交流语气最多20字、considerations注意事项列表字符串数组最多10项每项最多50字、theme主题配色classic-经典/light-明亮/dark-暗黑)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。",
"description": "管理用户个性化设置。支持读取所有配置或更新单个字段。可修改字段self_identifyAI自称最多20字、user_nameAI如何称呼用户最多20字、profession用户职业最多20字、tone交流语气最多20字、considerations注意事项列表字符串数组最多10项每项最多50字、theme主题配色classic-经典/light-明亮/dark-暗黑)、communication_style交流风格default-标准AI风格/human_like-拟人聊天风格)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
@ -803,7 +803,7 @@ class MainTerminalToolsDefinitionMixin:
},
"field": {
"type": "string",
"enum": ["self_identify", "user_name", "profession", "tone", "considerations", "theme"],
"enum": ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style"],
"description": "要更新的字段名仅action=update时需要"
},
"value": {

View File

@ -1311,7 +1311,7 @@ class MainTerminalToolsExecutionMixin:
try:
config = load_personalization_config(self.data_dir)
# 只返回可修改的字段
readable_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "enabled"]
readable_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style", "enabled"]
result = {k: v for k, v in config.items() if k in readable_fields}
# 构建详细返回信息
field_descriptions = {
@ -1321,7 +1321,8 @@ class MainTerminalToolsExecutionMixin:
"profession": f"用户职业: {result.get('profession') or '(未设置)'}",
"tone": f"交流语气: {result.get('tone') or '(未设置)'}",
"considerations": f"注意事项: {len(result.get('considerations') or [])}",
"theme": f"主题配色: {result.get('theme', 'classic')}"
"theme": f"主题配色: {result.get('theme', 'classic')}",
"communication_style": f"交流风格: {'拟人聊天风格' if result.get('communication_style') == 'human_like' else '标准AI风格'}"
}
details = "\n".join([f"- {field_descriptions.get(k, k)}: {v}" for k, v in result.items()])
logger.info("[_execute_manage_personalization] read成功")
@ -1346,7 +1347,7 @@ class MainTerminalToolsExecutionMixin:
# 验证字段是否允许修改
logger.info("[_execute_manage_personalization] 验证字段: field=%s", field)
allowed_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme"]
allowed_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style"]
if field not in allowed_fields:
logger.warning("[_execute_manage_personalization] 字段不允许修改: %s not in %s", field, allowed_fields)
return {"success": False, "error": f"字段 '{field}' 不允许修改,可修改字段: {allowed_fields}"}
@ -1381,6 +1382,13 @@ class MainTerminalToolsExecutionMixin:
elif value not in ALLOWED_THEMES:
validation_errors.append(f"theme 必须是以下之一: {list(ALLOWED_THEMES)}")
elif field == "communication_style":
# 交流风格验证
if not isinstance(value, str):
validation_errors.append("communication_style 必须是字符串")
elif value not in ["default", "human_like"]:
validation_errors.append("communication_style 必须是 'default''human_like'")
if validation_errors:
logger.warning("[_execute_manage_personalization] 验证失败: %s", validation_errors)
return {

View File

@ -45,6 +45,7 @@ DEFAULT_SHALLOW_KEEP_USER_TURN_TOOLS = 3
DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"enabled": False,
"communication_style": "default", # default / human_like
"self_identify": "",
"user_name": "",
"use_custom_names": False,
@ -164,6 +165,9 @@ def sanitize_personalization_payload(
return _sanitize_short_field(base.get(key))
base["enabled"] = bool(data.get("enabled", base["enabled"]))
# 交流风格: default / human_like
_comm_style = data.get("communication_style", base.get("communication_style", "default"))
base["communication_style"] = "human_like" if _comm_style == "human_like" else "default"
base["auto_generate_title"] = bool(data.get("auto_generate_title", base["auto_generate_title"]))
base["self_identify"] = _resolve_short_field("self_identify")
base["user_name"] = _resolve_short_field("user_name")
@ -497,6 +501,27 @@ def validate_context_compression_settings(config: Optional[Dict[str, Any]]) -> N
raise ValueError("深压缩触发上下文必须大于浅压缩触发上下文")
def _load_human_like_prompt() -> str:
"""加载拟人化风格提示词文件。"""
from config.paths import PROMPTS_DIR
try:
prompt_path = Path(PROMPTS_DIR) / "human_like_style.txt"
if prompt_path.exists():
return prompt_path.read_text(encoding="utf-8").strip()
except Exception:
pass
# 默认提示词
return (
"不要「像AI一样说话」而是尽可能的模仿真人的说话方式或是模仿用户输入的说话方式。\n"
"尽可能不要用markdown格式尤其是\n"
"- ###、##、#等一二三级标题\n"
"- 等列表形式\n"
"- 不必要的** **加粗\n"
"- 刻意的分点分层次的回答\n"
"用户如果不输入emoji就不要使用emoji回答。"
)
def build_personalization_prompt(
config: Optional[Dict[str, Any]],
include_header: bool = True
@ -525,6 +550,13 @@ def build_personalization_prompt(
for idx, item in enumerate(considerations, 1):
lines.append(f"{idx}. {item}")
# 拟人化交流风格
if config.get("communication_style") == "human_like":
human_like_prompt = _load_human_like_prompt()
if human_like_prompt:
lines.append("\n【交流风格要求】")
lines.append(human_like_prompt)
if len(lines) == (1 if include_header else 0):
# 没有任何有效内容时不注入
return None

View File

@ -0,0 +1,17 @@
不要「像AI一样说话」而是尽可能的模仿真人的说话方式或是模仿用户输入的说话方式
禁止使用的markdown格式
- ###、##、#等一二三级标题
- - 等列表形式
- 不必要的** **加粗
不要使用这些语言形式:
- 刻意的分点分层次的回答
- 刻意的 问题:回答 的格式
- 任何设问,反问的形式
- 任何最后补充的形式
- 任何包含“不卖关子”“我直说””我接住你“等的语言
用户如果不输入emoji就不要使用emoji回答
说话要自然、随意,像朋友聊天一样。不要过度礼貌或机械。可以用口语化的表达,适当使用语气词,让对话更有温度。

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M11.5 15H7a4 4 0 0 0-4 4v2m18.378-4.374a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/><circle cx="10" cy="7" r="4"/></svg>

After

Width:  |  Height:  |  Size: 395 B

View File

@ -1187,6 +1187,38 @@
</div>
<p class="behavior-hint" v-else>暂无可配置的工具类别</p>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">智能体交流风格</span>
<p class="field-desc">智能体和您交流时使用何种形式</p>
</div>
<div class="run-mode-options">
<button
type="button"
class="run-mode-card"
:class="{ active: form.communication_style === 'default' }"
:aria-pressed="form.communication_style === 'default'"
@click.prevent="setCommunicationStyle('default')"
>
<div class="run-mode-card-header">
<span class="run-mode-title">默认</span>
</div>
<p class="run-mode-desc">智能体会使用标准的AI风格的语言和您交流</p>
</button>
<button
type="button"
class="run-mode-card"
:class="{ active: form.communication_style === 'human_like' }"
:aria-pressed="form.communication_style === 'human_like'"
@click.prevent="setCommunicationStyle('human_like')"
>
<div class="run-mode-card-header">
<span class="run-mode-title">拟人</span>
</div>
<p class="run-mode-desc">智能体会像聊天一样模仿人类的语言风格和您交流</p>
</button>
</div>
</div>
</div>
<div class="personal-actions-row">
<div class="personal-form-actions card-aligned">
@ -1471,7 +1503,7 @@ const baseTabs = [
{ id: 'workspace', label: '工作区设置', description: '默认权限模式' },
{ id: 'usage', label: '用量统计', description: '对话累计数据' },
{ id: 'app-update', label: '软件更新', description: '检查并下载新版 APK' },
{ id: 'behavior', label: '模型行为', description: '工具提示与界面表现' },
{ id: 'behavior', label: '智能体行为', description: '交流风格与界面表现' },
{ id: 'skills', label: 'Skills', description: '可用技能开关' },
{ id: 'image', label: '图片压缩', description: '发送图片的尺寸策略' },
{ id: 'theme', label: '主题切换', description: '经典 / 明亮 / 夜间' },
@ -1850,6 +1882,10 @@ const setDefaultPermissionMode = (value: PermissionModeValue) => {
personalization.setDefaultPermissionMode(value);
};
const setCommunicationStyle = (value: 'default' | 'human_like') => {
personalization.setCommunicationStyle(value);
};
const checkModeModelConflict = (mode: RunModeValue, model: string | null): boolean => {
const found = (filteredModelOptions.value || []).find((item: any) => item.value === model);
const warnings: string[] = [];

View File

@ -7,6 +7,7 @@ type PermissionMode = 'readonly' | 'approval' | 'unrestricted';
interface PersonalForm {
enabled: boolean;
communication_style: 'default' | 'human_like';
auto_generate_title: boolean;
tool_intent_enabled: boolean;
skill_hints_enabled: boolean;
@ -77,6 +78,7 @@ const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
const defaultForm = (): PersonalForm => ({
enabled: false,
communication_style: 'default',
auto_generate_title: true,
tool_intent_enabled: true,
skill_hints_enabled: false,
@ -228,6 +230,7 @@ export const usePersonalizationStore = defineStore('personalization', {
: null) || 'kimi-k2.5';
this.form = {
enabled: !!data.enabled,
communication_style: data.communication_style === 'human_like' ? 'human_like' : 'default',
auto_generate_title: data.auto_generate_title !== false,
tool_intent_enabled: !!data.tool_intent_enabled,
skill_hints_enabled: !!data.skill_hints_enabled,
@ -672,6 +675,13 @@ export const usePersonalizationStore = defineStore('personalization', {
blockDisplayMode: mode
};
this.persistExperiments();
},
setCommunicationStyle(style: 'default' | 'human_like') {
this.form = {
...this.form,
communication_style: style
};
this.clearFeedback();
}
}
});

View File

@ -498,8 +498,8 @@ const DEFAULT_STEPS: TutorialStep[] = [
},
{
id: 'tab-behavior',
title: '模型行为',
description: '下一步自动切换到“模型行为”。',
title: '智能体行为',
description: '下一步自动切换到“智能体行为”。',
target: '[data-tutorial="personal-tab-behavior"]',
mode: 'info',
autoClick: true,
@ -507,7 +507,7 @@ const DEFAULT_STEPS: TutorialStep[] = [
},
{
id: 'page-behavior',
title: '模型行为内容',
title: '智能体行为内容',
description: '可配置工具显示、压缩策略等高级选项。',
target: '[data-tutorial="personal-content-shell"]',
mode: 'info',

View File

@ -35,6 +35,7 @@ export const ICONS = Object.freeze({
trash: '/static/icons/trash.svg',
triangleAlert: '/static/icons/triangle-alert.svg',
user: '/static/icons/user.svg',
userPen: '/static/icons/user-pen.svg',
wrench: '/static/icons/wrench.svg',
x: '/static/icons/x.svg',
zap: '/static/icons/zap.svg'
@ -43,6 +44,7 @@ export const ICONS = Object.freeze({
export const TOOL_ICON_MAP = Object.freeze({
close_sub_agent: 'bot',
create_file: 'file',
manage_personalization: 'userPen',
create_folder: 'folder',
create_sub_agent: 'bot',
delete_file: 'trash',
@ -75,6 +77,7 @@ export const TOOL_ICON_MAP = Object.freeze({
export const TOOL_CATEGORY_ICON_MAP = Object.freeze({
network: 'globe',
file_edit: 'pencil',
personalization: 'userPen',
read_focus: 'eye',
terminal_realtime: 'monitor',
terminal_command: 'terminal',