- **子 Agent 支持**: - 新增 `dispatch_subagent` 工具,支持分配特定任务给子 Agent,由其独立操作。 - 前端新增 `ChatSubAgent` 组件,支持子 Agent 任务状态展示(运行中/已完成)、工具调用进度及输出预览等。 - **技能管理扩展**: - 新增 Skill 编辑器页面(`SkillEditorView.vue`),支持创建、编辑、删除技能。 - 完善技能工具权限分组选择功能,按类型灵活分配工具。 - **工具增强**: - 新增工具接口:`get_outline_detail`、`get_character_detail`、`get_chapter_detail`,用于获取详情内容。 - 优化部分工具返回数据,仅输出基础信息以减少上下文体积。 - 后端添加 `SPECIAL_TOOLS` 支持,增强 `dispatch_subagent` 的特殊处理能力。 同时优化部分前后端交互,提升代码维护性与可读性。
This commit is contained in:
@@ -34,12 +34,23 @@ TOOL_DEFINITIONS: list[dict] = [
|
||||
},
|
||||
{
|
||||
"name": "list_outlines",
|
||||
"description": "列出当前小说的所有大纲节点,返回每个节点的 id、排序、摘要和详情",
|
||||
"description": "列出当前小说的所有大纲节点(仅返回 id、排序和摘要标题,不含详情)。如需查看某节点的详情内容,请使用 get_outline_detail 工具。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "get_outline_detail",
|
||||
"description": "查看指定大纲节点的完整详情内容",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"outline_id": {"type": "integer", "description": "大纲节点 ID"},
|
||||
},
|
||||
"required": ["outline_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "create_outline",
|
||||
"description": "为当前小说创建一个新的大纲节点",
|
||||
@@ -118,12 +129,23 @@ TOOL_DEFINITIONS: list[dict] = [
|
||||
# ── 角色工具 ──
|
||||
{
|
||||
"name": "list_characters",
|
||||
"description": "列出当前小说的所有角色,返回每个角色的 id、名称和描述",
|
||||
"description": "列出当前小说的所有角色(仅返回 id 和名称,不含描述详情)。如需查看某角色的完整描述,请使用 get_character_detail 工具。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "get_character_detail",
|
||||
"description": "查看指定角色的完整描述信息",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"character_id": {"type": "integer", "description": "角色 ID"},
|
||||
},
|
||||
"required": ["character_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "create_character",
|
||||
"description": "为当前小说创建一个新角色",
|
||||
@@ -163,12 +185,23 @@ TOOL_DEFINITIONS: list[dict] = [
|
||||
# ── 章节工具 ──
|
||||
{
|
||||
"name": "list_chapters",
|
||||
"description": "列出当前小说的所有章节,按排序顺序返回每个章节的 id、标题和内容摘要",
|
||||
"description": "列出当前小说的所有章节(仅返回 id、排序和标题,不含正文)。如需查看某章节的完整正文,请使用 get_chapter_detail 工具。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "get_chapter_detail",
|
||||
"description": "查看指定章节的完整正文内容",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chapter_id": {"type": "integer", "description": "章节 ID"},
|
||||
},
|
||||
"required": ["chapter_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "create_chapter",
|
||||
"description": "为当前小说创建一个新章节",
|
||||
@@ -227,8 +260,23 @@ TOOL_DEFINITIONS: list[dict] = [
|
||||
"required": ["file_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "dispatch_subagent",
|
||||
"description": "派遣一个子 Agent 执行特定任务。子 Agent 拥有独立的系统提示词和工具集(由 Skill 定义),适合将复杂任务拆分为子任务分别处理。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skill_id": {"type": "integer", "description": "要使用的 Skill ID(从可用 Skill 列表中选择)"},
|
||||
"task": {"type": "string", "description": "交给子 Agent 的具体任务描述"},
|
||||
},
|
||||
"required": ["skill_id", "task"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# dispatch_subagent 不由 execute_tool 处理,它在 chat.py 中特殊处理
|
||||
SPECIAL_TOOLS = {"dispatch_subagent"}
|
||||
|
||||
|
||||
def _filter_defs(allowed: list[str] | None) -> list[dict]:
|
||||
"""根据白名单过滤工具定义"""
|
||||
@@ -275,6 +323,7 @@ def execute_tool(name: str, arguments: dict, novel_id: int) -> str:
|
||||
"get_novel_info": _get_novel_info,
|
||||
"update_novel_info": _update_novel_info,
|
||||
"list_outlines": _list_outlines,
|
||||
"get_outline_detail": _get_outline_detail,
|
||||
"create_outline": _create_outline,
|
||||
"update_outline": _update_outline,
|
||||
"delete_outline": _delete_outline,
|
||||
@@ -282,10 +331,12 @@ def execute_tool(name: str, arguments: dict, novel_id: int) -> str:
|
||||
"get_world_setting": _get_world_setting,
|
||||
"save_world_setting": _save_world_setting,
|
||||
"list_characters": _list_characters,
|
||||
"get_character_detail": _get_character_detail,
|
||||
"create_character": _create_character,
|
||||
"update_character": _update_character,
|
||||
"delete_character": _delete_character,
|
||||
"list_chapters": _list_chapters,
|
||||
"get_chapter_detail": _get_chapter_detail,
|
||||
"create_chapter": _create_chapter,
|
||||
"update_chapter": _update_chapter,
|
||||
"delete_chapter": _delete_chapter,
|
||||
@@ -339,13 +390,12 @@ def _update_novel_info(session: Session, novel_id: int, args: dict) -> str:
|
||||
|
||||
|
||||
def _list_outlines(session: Session, novel_id: int, _args: dict) -> str:
|
||||
# 查询顶级节点
|
||||
"""列表仅返回 id + 排序 + 摘要标题(不含 detail),节省上下文。"""
|
||||
top_items = session.exec(
|
||||
select(Outline)
|
||||
.where(Outline.novel_id == novel_id, Outline.parent_id.is_(None)) # type: ignore
|
||||
.order_by(Outline.sort_order)
|
||||
).all()
|
||||
# 查询所有子节点,按 parent_id 分组
|
||||
all_children = session.exec(
|
||||
select(Outline)
|
||||
.where(Outline.novel_id == novel_id, Outline.parent_id.isnot(None)) # type: ignore
|
||||
@@ -357,17 +407,28 @@ def _list_outlines(session: Session, novel_id: int, _args: dict) -> str:
|
||||
|
||||
result = []
|
||||
for o in top_items:
|
||||
node = {"id": o.id, "sort_order": o.sort_order, "summary": o.summary, "detail": o.detail}
|
||||
node: dict = {"id": o.id, "sort_order": o.sort_order, "summary": o.summary}
|
||||
kids = children_map.get(o.id, [])
|
||||
if kids:
|
||||
node["children"] = [
|
||||
{"id": c.id, "sort_order": c.sort_order, "summary": c.summary, "detail": c.detail, "parent_id": c.parent_id}
|
||||
{"id": c.id, "sort_order": c.sort_order, "summary": c.summary, "parent_id": c.parent_id}
|
||||
for c in kids
|
||||
]
|
||||
result.append(node)
|
||||
return json.dumps({"outlines": result, "total": len(result)}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _get_outline_detail(session: Session, novel_id: int, args: dict) -> str:
|
||||
outline = session.get(Outline, args["outline_id"])
|
||||
if not outline or outline.novel_id != novel_id:
|
||||
return json.dumps({"error": "大纲不存在"}, ensure_ascii=False)
|
||||
return json.dumps({
|
||||
"id": outline.id, "sort_order": outline.sort_order,
|
||||
"summary": outline.summary, "detail": outline.detail,
|
||||
"parent_id": outline.parent_id,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _create_outline(session: Session, novel_id: int, args: dict) -> str:
|
||||
parent_id = args.get("parent_id")
|
||||
|
||||
@@ -498,18 +559,25 @@ def _save_world_setting(session: Session, novel_id: int, args: dict) -> str:
|
||||
|
||||
|
||||
def _list_characters(session: Session, novel_id: int, _args: dict) -> str:
|
||||
"""列表仅返回 id + 名称(不含 description),节省上下文。"""
|
||||
items = session.exec(
|
||||
select(Character)
|
||||
.where(Character.novel_id == novel_id)
|
||||
.order_by(Character.created_at)
|
||||
).all()
|
||||
result = [
|
||||
{"id": c.id, "name": c.name, "description": c.description}
|
||||
for c in items
|
||||
]
|
||||
result = [{"id": c.id, "name": c.name} for c in items]
|
||||
return json.dumps({"characters": result, "total": len(result)}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _get_character_detail(session: Session, novel_id: int, args: dict) -> str:
|
||||
char = session.get(Character, args["character_id"])
|
||||
if not char or char.novel_id != novel_id:
|
||||
return json.dumps({"error": "角色不存在"}, ensure_ascii=False)
|
||||
return json.dumps({
|
||||
"id": char.id, "name": char.name, "description": char.description,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _create_character(session: Session, novel_id: int, args: dict) -> str:
|
||||
name = args["name"].strip()
|
||||
if not name or len(name) > 100:
|
||||
@@ -564,23 +632,26 @@ def _delete_character(session: Session, novel_id: int, args: dict) -> str:
|
||||
|
||||
|
||||
def _list_chapters(session: Session, novel_id: int, _args: dict) -> str:
|
||||
"""列表仅返回 id + 排序 + 标题(不含正文),节省上下文。"""
|
||||
items = session.exec(
|
||||
select(Chapter)
|
||||
.where(Chapter.novel_id == novel_id)
|
||||
.order_by(Chapter.sort_order)
|
||||
).all()
|
||||
result = [
|
||||
{
|
||||
"id": c.id,
|
||||
"sort_order": c.sort_order,
|
||||
"title": c.title,
|
||||
"content_preview": c.content[:100] + "…" if len(c.content) > 100 else c.content,
|
||||
}
|
||||
for c in items
|
||||
]
|
||||
result = [{"id": c.id, "sort_order": c.sort_order, "title": c.title} for c in items]
|
||||
return json.dumps({"chapters": result, "total": len(result)}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _get_chapter_detail(session: Session, novel_id: int, args: dict) -> str:
|
||||
chapter = session.get(Chapter, args["chapter_id"])
|
||||
if not chapter or chapter.novel_id != novel_id:
|
||||
return json.dumps({"error": "章节不存在"}, ensure_ascii=False)
|
||||
return json.dumps({
|
||||
"id": chapter.id, "sort_order": chapter.sort_order,
|
||||
"title": chapter.title, "content": chapter.content,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _create_chapter(session: Session, novel_id: int, args: dict) -> str:
|
||||
title = args["title"].strip()
|
||||
if not title or len(title) > 200:
|
||||
|
||||
Reference in New Issue
Block a user