- 角色管理:CRUD API + 卡片网格UI,每个角色约200字描述 - 世界观设定:单篇长文编辑器,建议500字以上 - 章节管理:CRUD + 排序 API,左右分栏编辑器,单章5000字上限 - AI工具调用:支持工具审批流程和结构化工具调用 - 小说详情页新增章节、角色、世界观入口按钮 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
"""工具注册表和执行器 —— 大纲 & 世界观 CRUD"""
|
||
|
||
import json
|
||
from datetime import datetime, timezone
|
||
|
||
from sqlmodel import Session, select, func
|
||
|
||
from ..database import engine
|
||
from ..models import Novel, Outline, WorldSetting
|
||
|
||
|
||
# ── 工具定义(provider 无关格式)──
|
||
|
||
TOOL_DEFINITIONS: list[dict] = [
|
||
{
|
||
"name": "list_outlines",
|
||
"description": "列出当前小说的所有大纲节点,返回每个节点的 id、排序、摘要和详情",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {},
|
||
},
|
||
},
|
||
{
|
||
"name": "create_outline",
|
||
"description": "为当前小说创建一个新的大纲节点",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"summary": {"type": "string", "description": "大纲摘要(简短标题,200字以内)"},
|
||
"detail": {"type": "string", "description": "大纲详情(可选,1000字以内)"},
|
||
},
|
||
"required": ["summary"],
|
||
},
|
||
},
|
||
{
|
||
"name": "update_outline",
|
||
"description": "更新指定大纲节点的摘要或详情",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"outline_id": {"type": "integer", "description": "要更新的大纲节点 ID"},
|
||
"summary": {"type": "string", "description": "新的摘要(可选)"},
|
||
"detail": {"type": "string", "description": "新的详情(可选)"},
|
||
},
|
||
"required": ["outline_id"],
|
||
},
|
||
},
|
||
{
|
||
"name": "delete_outline",
|
||
"description": "删除指定的大纲节点",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"outline_id": {"type": "integer", "description": "要删除的大纲节点 ID"},
|
||
},
|
||
"required": ["outline_id"],
|
||
},
|
||
},
|
||
{
|
||
"name": "get_world_setting",
|
||
"description": "获取当前小说的世界观设定内容",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {},
|
||
},
|
||
},
|
||
{
|
||
"name": "save_world_setting",
|
||
"description": "保存/更新当前小说的世界观设定",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"content": {"type": "string", "description": "世界观设定内容(5000字以内)"},
|
||
},
|
||
"required": ["content"],
|
||
},
|
||
},
|
||
]
|
||
|
||
|
||
def get_openai_tools() -> list[dict]:
|
||
"""转换为 OpenAI function calling 格式"""
|
||
return [
|
||
{"type": "function", "function": t}
|
||
for t in TOOL_DEFINITIONS
|
||
]
|
||
|
||
|
||
def get_anthropic_tools() -> list[dict]:
|
||
"""转换为 Anthropic tool_use 格式"""
|
||
return [
|
||
{"name": t["name"], "description": t["description"], "input_schema": t["parameters"]}
|
||
for t in TOOL_DEFINITIONS
|
||
]
|
||
|
||
|
||
# ── 工具执行 ──
|
||
|
||
|
||
def execute_tool(name: str, arguments: dict, novel_id: int) -> str:
|
||
"""执行工具,返回结果字符串(JSON)"""
|
||
with Session(engine) as session:
|
||
handlers = {
|
||
"list_outlines": _list_outlines,
|
||
"create_outline": _create_outline,
|
||
"update_outline": _update_outline,
|
||
"delete_outline": _delete_outline,
|
||
"get_world_setting": _get_world_setting,
|
||
"save_world_setting": _save_world_setting,
|
||
}
|
||
handler = handlers.get(name)
|
||
if not handler:
|
||
return json.dumps({"error": f"未知工具: {name}"}, ensure_ascii=False)
|
||
try:
|
||
return handler(session, novel_id, arguments)
|
||
except Exception as e:
|
||
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
||
|
||
|
||
def _list_outlines(session: Session, novel_id: int, _args: dict) -> str:
|
||
items = session.exec(
|
||
select(Outline)
|
||
.where(Outline.novel_id == novel_id)
|
||
.order_by(Outline.sort_order)
|
||
).all()
|
||
result = [
|
||
{"id": o.id, "sort_order": o.sort_order, "summary": o.summary, "detail": o.detail}
|
||
for o in items
|
||
]
|
||
return json.dumps({"outlines": result, "total": len(result)}, ensure_ascii=False)
|
||
|
||
|
||
def _create_outline(session: Session, novel_id: int, args: dict) -> str:
|
||
max_order = session.exec(
|
||
select(func.max(Outline.sort_order)).where(Outline.novel_id == novel_id)
|
||
).one()
|
||
next_order = (max_order or 0) + 1
|
||
|
||
outline = Outline(
|
||
novel_id=novel_id,
|
||
sort_order=next_order,
|
||
summary=args["summary"],
|
||
detail=args.get("detail", ""),
|
||
)
|
||
session.add(outline)
|
||
session.commit()
|
||
session.refresh(outline)
|
||
return json.dumps(
|
||
{"id": outline.id, "sort_order": outline.sort_order, "summary": outline.summary, "detail": outline.detail},
|
||
ensure_ascii=False,
|
||
)
|
||
|
||
|
||
def _update_outline(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)
|
||
|
||
if "summary" in args:
|
||
outline.summary = args["summary"]
|
||
if "detail" in args:
|
||
outline.detail = args["detail"]
|
||
outline.updated_at = datetime.now(timezone.utc)
|
||
session.add(outline)
|
||
session.commit()
|
||
session.refresh(outline)
|
||
return json.dumps(
|
||
{"id": outline.id, "summary": outline.summary, "detail": outline.detail},
|
||
ensure_ascii=False,
|
||
)
|
||
|
||
|
||
def _delete_outline(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)
|
||
|
||
session.delete(outline)
|
||
session.commit()
|
||
return json.dumps({"deleted": True, "id": args["outline_id"]}, ensure_ascii=False)
|
||
|
||
|
||
def _get_world_setting(session: Session, novel_id: int, _args: dict) -> str:
|
||
ws = session.exec(
|
||
select(WorldSetting).where(WorldSetting.novel_id == novel_id)
|
||
).first()
|
||
if not ws:
|
||
return json.dumps({"content": "", "exists": False}, ensure_ascii=False)
|
||
return json.dumps({"content": ws.content, "exists": True}, ensure_ascii=False)
|
||
|
||
|
||
def _save_world_setting(session: Session, novel_id: int, args: dict) -> str:
|
||
ws = session.exec(
|
||
select(WorldSetting).where(WorldSetting.novel_id == novel_id)
|
||
).first()
|
||
now = datetime.now(timezone.utc)
|
||
if ws:
|
||
ws.content = args["content"]
|
||
ws.updated_at = now
|
||
else:
|
||
ws = WorldSetting(novel_id=novel_id, content=args["content"])
|
||
session.add(ws)
|
||
session.commit()
|
||
session.refresh(ws)
|
||
return json.dumps({"saved": True, "content_length": len(ws.content)}, ensure_ascii=False)
|