Files
ainovel-clients/tools/check_consistency.go
voocel 27bd85ef90 init
2026-03-07 21:25:55 +08:00

126 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/voocel/agentcore/schema"
"github.com/voocel/ainovel-cli/domain"
"github.com/voocel/ainovel-cli/state"
)
// CheckConsistencyTool 对照状态文件检查章节一致性。
// 返回上下文数据和已知约束供 LLM 判断,不做 AI 推理。
type CheckConsistencyTool struct {
store *state.Store
}
func NewCheckConsistencyTool(store *state.Store) *CheckConsistencyTool {
return &CheckConsistencyTool{store: store}
}
func (t *CheckConsistencyTool) Name() string { return "check_consistency" }
func (t *CheckConsistencyTool) Description() string {
return "检查章节一致性。返回章节内容、全部状态数据和具体检查清单,你需要逐项对照并以 JSON 格式返回冲突项"
}
func (t *CheckConsistencyTool) Label() string { return "一致性检查" }
func (t *CheckConsistencyTool) Schema() map[string]any {
return schema.Object(
schema.Property("chapter", schema.Int("要检查的章节号")).Required(),
)
}
func (t *CheckConsistencyTool) Execute(_ context.Context, args json.RawMessage) (json.RawMessage, error) {
var a struct {
Chapter int `json:"chapter"`
}
if err := json.Unmarshal(args, &a); err != nil {
return nil, fmt.Errorf("invalid args: %w", err)
}
if a.Chapter <= 0 {
return nil, fmt.Errorf("chapter must be > 0")
}
result := map[string]any{"chapter": a.Chapter}
// 加载章节内容polished 优先)
content, wordCount, err := t.store.LoadChapterContent(a.Chapter)
if err != nil {
return nil, fmt.Errorf("load chapter content: %w", err)
}
if content == "" {
return nil, fmt.Errorf("no content found for chapter %d", a.Chapter)
}
result["content"] = content
result["word_count"] = wordCount
// 加载全部状态数据供 LLM 对照
if timeline, _ := t.store.LoadTimeline(); len(timeline) > 0 {
result["timeline"] = timeline
}
if foreshadow, _ := t.store.LoadForeshadowLedger(); len(foreshadow) > 0 {
result["foreshadow_ledger"] = foreshadow
if active := filterActive(foreshadow); len(active) > 0 {
result["unresolved_foreshadow"] = active
}
}
if relationships, _ := t.store.LoadRelationships(); len(relationships) > 0 {
result["relationships"] = relationships
}
if chars, _ := t.store.LoadCharacters(); len(chars) > 0 {
result["characters"] = chars
}
if rules, _ := t.store.LoadWorldRules(); len(rules) > 0 {
result["world_rules"] = rules
// 提取边界清单,方便 LLM 逐条对照
var boundaries []string
for _, r := range rules {
if r.Boundary != "" {
boundaries = append(boundaries, fmt.Sprintf("[%s] %s", r.Category, r.Boundary))
}
}
if len(boundaries) > 0 {
result["world_rules_boundaries"] = boundaries
}
}
// 加载前两章摘要
if summaries, _ := t.store.LoadRecentSummaries(a.Chapter, 2); len(summaries) > 0 {
result["recent_summaries"] = summaries
}
result["instruction"] = `请逐项对照以上状态数据检查本章内容,返回 JSON 数组格式的冲突项:
[
{
"type": "timeline|foreshadow|relationship|character|world_rules",
"severity": "error|warning",
"description": "具体冲突描述",
"suggestion": "建议修正范围和方式"
}
]
检查清单:
1. 时间线:本章事件时间是否与已有 timeline 矛盾
2. 伏笔unresolved_foreshadow 中是否有本章应推进但遗漏的
3. 人物关系:角色互动是否与 relationships 当前状态矛盾
4. 角色一致性:行为是否符合 characters 中的性格和弧线
5. 世界规则:逐条检查 world_rules_boundaries 中的边界约束,本章内容是否违反任何一条
如果没有发现冲突,返回空数组 []。不要返回其他格式。`
return json.Marshal(result)
}
func filterActive(entries []domain.ForeshadowEntry) []domain.ForeshadowEntry {
var active []domain.ForeshadowEntry
for _, e := range entries {
if e.Status != "resolved" {
active = append(active, e)
}
}
return active
}