| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import { describe, expect, it, vi } from "vitest"
- import type { NovelAgent, SimulationState, StoryNode, ExtractionResult } from "@/lib/novel/story-simulation/types"
- import {
- createSimulationBlackboard,
- } from "@/lib/novel/story-simulation/multi-agent-orchestrator"
- import { agentDecideAndActWithReact } from "@/lib/novel/story-simulation/simulation-engine"
- import { ModelDoesNotSupportToolsError } from "@/lib/agent/runner"
- let mockRun: any
- vi.mock("@/lib/agent/runner", () => ({
- AgentRunner: class {
- run(...args: any[]) {
- return mockRun(...args)
- }
- },
- ModelDoesNotSupportToolsError: class extends Error {
- constructor() {
- super("当前模型不支持工具调用")
- this.name = "ModelDoesNotSupportToolsError"
- }
- },
- }))
- function makeAgent(id: string, name: string): NovelAgent {
- return {
- characterId: id,
- name,
- profile: `${name}的档案`,
- aura: null,
- cognition: null,
- soul: "",
- currentGoal: "完成当前目标",
- emotionalState: "neutral",
- knownFacts: new Set(),
- relationships: new Map(),
- powerLevel: "normal",
- memory: {
- observedEvents: [],
- knownSecrets: new Set(),
- sentiments: new Map(),
- recentDecisions: [],
- rumorCredibility: 0.5,
- },
- knowledgeScope: [],
- personality: [],
- speakingStyle: "",
- }
- }
- function makeNode(): StoryNode {
- return {
- index: 0,
- phase: "起",
- title: "开端",
- coreConflict: "冲突",
- involvedCharacters: ["甲", "乙"],
- goal: "推进剧情",
- causeFromPrev: "无",
- expectedOutcome: "完成开端",
- }
- }
- function makeExtraction(): ExtractionResult {
- return {
- characters: [],
- chapterContents: [],
- memoryData: {
- characterStates: "",
- characterCognition: null,
- foreshadowingTracker: null,
- timeline: [],
- canonFacts: "",
- conflicts: "",
- },
- worldRules: "",
- powerSystem: "",
- foreshadowing: null,
- timeline: [],
- outlineContent: "",
- soulDoc: "",
- }
- }
- function makeState(agents: NovelAgent[]): SimulationState {
- return {
- currentRound: 0,
- timelineEvents: [],
- activeAgents: new Map(agents.map((a) => [a.characterId, a])),
- worldState: {},
- directorEnabled: false,
- nextNodeInjectionMap: new Map(),
- }
- }
- describe("agentDecideAndActWithReact", () => {
- it("parses action from AgentRunner finalText and creates timeline event", async () => {
- const agentA = makeAgent("a", "甲")
- const agentB = makeAgent("b", "乙")
- const agents = [agentA, agentB]
- const node = makeNode()
- const state = makeState(agents)
- const extraction = makeExtraction()
- const blackboard = createSimulationBlackboard({ agents })
- mockRun = vi.fn().mockResolvedValue({
- toolCalls: [],
- roundsUsed: 1,
- finalText: JSON.stringify({
- type: "speak",
- content: "你好乙",
- target: "乙",
- visibility: "target_only",
- motivation: "打招呼",
- plot_push: "建立联系",
- }),
- })
- const result = await agentDecideAndActWithReact(
- agentA,
- node,
- state,
- {} as any,
- extraction,
- [],
- undefined,
- blackboard,
- )
- expect(result).not.toBeNull()
- expect(result!.parsed.action.type).toBe("speak")
- expect(result!.parsed.action.content).toBe("你好乙")
- expect(result!.parsed.action.target).toBe("乙")
- expect(result!.tlEvent.actorId).toBe("a")
- expect(result!.tlEvent.content).toBe("你好乙")
- expect(mockRun).toHaveBeenCalledTimes(1)
- const callArgs = mockRun.mock.calls[0]
- expect(callArgs[0].maxRounds).toBe(3)
- expect(callArgs[0].tools.length).toBe(6)
- })
- it("throws ModelDoesNotSupportToolsError when model does not support tools", async () => {
- const agentA = makeAgent("a", "甲")
- const agents = [agentA]
- const node = makeNode()
- const state = makeState(agents)
- const extraction = makeExtraction()
- const blackboard = createSimulationBlackboard({ agents })
- mockRun = vi.fn().mockImplementation(async (_config, _registry, _messages, callbacks) => {
- callbacks.onError(new ModelDoesNotSupportToolsError())
- return { toolCalls: [], roundsUsed: 0, finalText: "" }
- })
- await expect(
- agentDecideAndActWithReact(
- agentA,
- node,
- state,
- {} as any,
- extraction,
- [],
- undefined,
- blackboard,
- ),
- ).rejects.toThrow(ModelDoesNotSupportToolsError)
- })
- it("returns null when signal is aborted", async () => {
- const agentA = makeAgent("a", "甲")
- const agents = [agentA]
- const node = makeNode()
- const state = makeState(agents)
- const extraction = makeExtraction()
- const blackboard = createSimulationBlackboard({ agents })
- mockRun = vi.fn().mockResolvedValue({
- toolCalls: [],
- roundsUsed: 0,
- finalText: "",
- })
- const controller = new AbortController()
- controller.abort()
- const result = await agentDecideAndActWithReact(
- agentA,
- node,
- state,
- {} as any,
- extraction,
- [],
- undefined,
- blackboard,
- controller.signal,
- )
- expect(result).toBeNull()
- })
- it("handles malformed JSON gracefully with fallback", async () => {
- const agentA = makeAgent("a", "甲")
- const agents = [agentA]
- const node = makeNode()
- const state = makeState(agents)
- const extraction = makeExtraction()
- const blackboard = createSimulationBlackboard({ agents })
- mockRun = vi.fn().mockResolvedValue({
- toolCalls: [],
- roundsUsed: 1,
- finalText: "这不是有效的JSON格式",
- })
- const result = await agentDecideAndActWithReact(
- agentA,
- node,
- state,
- {} as any,
- extraction,
- [],
- undefined,
- blackboard,
- )
- expect(result).not.toBeNull()
- expect(result!.parsed.action.type).toBe("observe")
- })
- it("passes injectionEvent and modeHint to user message", async () => {
- const agentA = makeAgent("a", "甲")
- const agents = [agentA]
- const node = makeNode()
- const state = makeState(agents)
- const extraction = makeExtraction()
- const blackboard = createSimulationBlackboard({ agents })
- mockRun = vi.fn().mockResolvedValue({
- toolCalls: [],
- roundsUsed: 1,
- finalText: JSON.stringify({
- type: "observe",
- content: "观察周围",
- visibility: "self",
- }),
- })
- await agentDecideAndActWithReact(
- agentA,
- node,
- state,
- {} as any,
- extraction,
- [],
- "突发地震",
- blackboard,
- undefined,
- "主动出击",
- )
- expect(mockRun).toHaveBeenCalledTimes(1)
- const callArgs = mockRun.mock.calls[0]
- const messages = callArgs[2]
- const userMessage = messages.find((m: any) => m.role === "user")
- expect(userMessage.content).toContain("突发地震")
- expect(userMessage.content).toContain("主动出击")
- })
- })
|