From e1482da0e4d49420ff5083b848217fd143292e73 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 12 Jun 2026 10:21:28 +0000 Subject: [PATCH 1/3] feat(wiki-compose): autonomous research loop without user iteration setting Remove the Brief-phase research depth slider and let the evaluator LLM decide when sources are sufficient. Wiki Compose now uses an internal safety cap instead of a user-configured 1..5 iteration limit; ingest planner keeps its explicit 1..5 cap when provided. --- .../nodes/humanReviewBrief.test.ts | 9 ---- .../subgraphs/research/constants.test.ts | 22 ++++++++++ .../research/nodes/planQueries.test.ts | 10 ++++- .../wikiCompose/nodes/humanReviewBrief.ts | 13 +----- .../graphs/wikiCompose/resumeSchemas.ts | 5 +-- .../src/agents/graphs/wikiCompose/state.ts | 5 ++- .../src/agents/graphs/wikiCompose/types.ts | 2 - .../agents/subgraphs/research/constants.ts | 44 +++++++++++++++++++ .../subgraphs/research/nodes/compileBatch.ts | 5 ++- .../research/nodes/evaluateSufficiency.ts | 12 +++-- .../subgraphs/research/nodes/planQueries.ts | 15 +++---- .../subgraphs/research/nodes/refineQueries.ts | 2 +- .../subgraphs/research/researchGraph.ts | 10 +++-- .../agents/subgraphs/research/shouldRefine.ts | 9 ++-- .../src/agents/subgraphs/research/state.ts | 5 ++- server/api/src/routes/composeSessions.ts | 4 +- src/components/wikiCompose/ComposePanel.tsx | 6 +-- .../wikiCompose/DialogueSection.tsx | 35 +-------------- src/hooks/wiki/useWikiComposeSession.ts | 6 +-- src/i18n/locales/en/wikiCompose.json | 5 --- src/i18n/locales/ja/wikiCompose.json | 5 --- src/lib/wikiCompose/composeService.ts | 2 +- 22 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 server/api/src/__tests__/agents/subgraphs/research/constants.test.ts create mode 100644 server/api/src/agents/subgraphs/research/constants.ts diff --git a/server/api/src/__tests__/agents/graphs/wikiCompose/nodes/humanReviewBrief.test.ts b/server/api/src/__tests__/agents/graphs/wikiCompose/nodes/humanReviewBrief.test.ts index 9c0c8eda..70464f2e 100644 --- a/server/api/src/__tests__/agents/graphs/wikiCompose/nodes/humanReviewBrief.test.ts +++ b/server/api/src/__tests__/agents/graphs/wikiCompose/nodes/humanReviewBrief.test.ts @@ -74,7 +74,6 @@ describe("humanReviewBrief", () => { }, ], appendToExisting: true, - researchMaxIterations: 4, }); const update = await humanReviewBrief(state({}), { configurable: {} } as never); @@ -97,7 +96,6 @@ describe("humanReviewBrief", () => { ]); expect(brief.summary).toContain("Target audience?"); expect(brief.summary).toContain("selected=opt-a"); - expect(update.maxIterations).toBe(4); }); it("accepts an empty answers array (explicit Brief skip)", async () => { @@ -111,13 +109,6 @@ describe("humanReviewBrief", () => { expect(brief.answers).toEqual([]); expect(brief.summary).toBe("(no brief provided)"); expect(brief.appendToExisting).toBe(false); - expect(update.maxIterations).toBeUndefined(); - }); - - it("rejects researchMaxIterations outside 1..5", async () => { - interrupt.mockReturnValueOnce({ answers: [], researchMaxIterations: 9 }); - - await expect(humanReviewBrief(state({}), { configurable: {} } as never)).rejects.toThrow(); }); it("rejects answers missing questionId", async () => { diff --git a/server/api/src/__tests__/agents/subgraphs/research/constants.test.ts b/server/api/src/__tests__/agents/subgraphs/research/constants.test.ts new file mode 100644 index 00000000..54072e83 --- /dev/null +++ b/server/api/src/__tests__/agents/subgraphs/research/constants.test.ts @@ -0,0 +1,22 @@ +/** + * Research loop iteration cap resolution tests. + */ +import { describe, expect, it } from "vitest"; +import { + RESEARCH_SAFETY_MAX_ITERATIONS, + resolveResearchMaxIterations, +} from "../../../../agents/subgraphs/research/constants.js"; + +describe("resolveResearchMaxIterations", () => { + it("honours explicit ingest caps in 1..5", () => { + expect(resolveResearchMaxIterations(3)).toBe(3); + expect(resolveResearchMaxIterations(1)).toBe(1); + expect(resolveResearchMaxIterations(5)).toBe(5); + }); + + it("uses the autonomous safety cap for wiki compose defaults", () => { + expect(resolveResearchMaxIterations(undefined)).toBe(RESEARCH_SAFETY_MAX_ITERATIONS); + expect(resolveResearchMaxIterations(10)).toBe(RESEARCH_SAFETY_MAX_ITERATIONS); + expect(resolveResearchMaxIterations(99)).toBe(RESEARCH_SAFETY_MAX_ITERATIONS); + }); +}); diff --git a/server/api/src/__tests__/agents/subgraphs/research/nodes/planQueries.test.ts b/server/api/src/__tests__/agents/subgraphs/research/nodes/planQueries.test.ts index afaa639a..ac3953a2 100644 --- a/server/api/src/__tests__/agents/subgraphs/research/nodes/planQueries.test.ts +++ b/server/api/src/__tests__/agents/subgraphs/research/nodes/planQueries.test.ts @@ -32,6 +32,7 @@ vi.mock("../../../../../agents/subgraphs/research/nodes/shared/dispatchSseCustom })); import { planQueries } from "../../../../../agents/subgraphs/research/nodes/planQueries.js"; +import { RESEARCH_SAFETY_MAX_ITERATIONS } from "../../../../../agents/subgraphs/research/constants.js"; import { GRAPH_CONTEXT_CONFIG_KEY } from "../../../../../agents/core/types/graphContext.js"; import type { GraphContext } from "../../../../../agents/core/types/graphContext.js"; import type { Database } from "../../../../../types/index.js"; @@ -93,9 +94,14 @@ afterEach(() => { describe("planQueries — additional research detection", () => { const config = { configurable: { [GRAPH_CONTEXT_CONFIG_KEY]: fakeContext() } }; - it("clamps maxIterations to 1..5 (default 3)", async () => { + it("uses the autonomous safety cap when no explicit ingest cap is set", async () => { const update = await planQueries(state({ maxIterations: 99 }), config as never); - expect(update.maxIterations).toBe(5); + expect(update.maxIterations).toBe(RESEARCH_SAFETY_MAX_ITERATIONS); + }); + + it("honours explicit ingest caps in 1..5", async () => { + const update = await planQueries(state({ maxIterations: 4 }), config as never); + expect(update.maxIterations).toBe(4); }); it("consumes state.additionalRequest and seeds carried-over sources", async () => { diff --git a/server/api/src/agents/graphs/wikiCompose/nodes/humanReviewBrief.ts b/server/api/src/agents/graphs/wikiCompose/nodes/humanReviewBrief.ts index 831ea038..941726c3 100644 --- a/server/api/src/agents/graphs/wikiCompose/nodes/humanReviewBrief.ts +++ b/server/api/src/agents/graphs/wikiCompose/nodes/humanReviewBrief.ts @@ -4,13 +4,10 @@ * Brief 質問群を `interrupt(value)` でユーザーに渡し、`PATCH .../resume` の * 結果を `briefResumeSchema` で検証して `brief` を state に確定する。 * 既存本文ありで「追記」を選んだ場合は `appendToExisting=true` が立ち、Draft - * フェーズがそれを読んで挙動を切り替える。`researchMaxIterations` (1..5) が - * 指定されていれば、後段の Research subgraph に渡るようミラーする。 + * フェーズがそれを読んで挙動を切り替える。 * * Halts the graph at the Brief interrupt and projects the user's answers into - * `state.brief`. The resume payload's `researchMaxIterations` (when present) - * is mirrored to `state.researchMaxIterations` so the research subgraph node - * picks it up via its own state slot when invoked. + * `state.brief`. */ import type { LangGraphRunnableConfig } from "@langchain/langgraph"; import { interrupt } from "@langchain/langgraph"; @@ -127,11 +124,5 @@ export async function humanReviewBrief( brief, phase: "brief:completed", }; - if (parsed.researchMaxIterations !== undefined) { - // Mirror onto the canonical research subgraph channel name so the - // composed research node picks it up via shared state. - // research subgraph と共有する `maxIterations` チャネルに反映する。 - update.maxIterations = parsed.researchMaxIterations; - } return update; } diff --git a/server/api/src/agents/graphs/wikiCompose/resumeSchemas.ts b/server/api/src/agents/graphs/wikiCompose/resumeSchemas.ts index dec5f9d4..8fa16fa0 100644 --- a/server/api/src/agents/graphs/wikiCompose/resumeSchemas.ts +++ b/server/api/src/agents/graphs/wikiCompose/resumeSchemas.ts @@ -16,13 +16,11 @@ import { z } from "zod"; * * - `answers` — 必須。空配列でも可(Brief をスキップしたケース)。 * - `appendToExisting` — 本文ありページで「追記」を選んだ場合 true。 - * - `researchMaxIterations` — Brief 内で 1..5 にユーザーが調整した場合のみ。 * * Validates the resume payload at the Brief interrupt. `answers` is required * even when empty (the user may explicitly skip Brief by submitting an empty * array). Default for `appendToExisting` is `false` (replace-mode is the - * historical Wiki Compose behaviour); `researchMaxIterations` is clamped to - * 1..5 by the schema so the graph never sees an out-of-range value. + * historical Wiki Compose behaviour). */ export const briefResumeSchema = z.object({ answers: z @@ -35,7 +33,6 @@ export const briefResumeSchema = z.object({ ) .default([]), appendToExisting: z.boolean().optional().default(false), - researchMaxIterations: z.number().int().min(1).max(5).optional(), }); export type BriefResumeParsed = z.infer; diff --git a/server/api/src/agents/graphs/wikiCompose/state.ts b/server/api/src/agents/graphs/wikiCompose/state.ts index 8960d4cd..1d84fea6 100644 --- a/server/api/src/agents/graphs/wikiCompose/state.ts +++ b/server/api/src/agents/graphs/wikiCompose/state.ts @@ -19,6 +19,7 @@ */ import { Annotation } from "@langchain/langgraph"; import { BaseState } from "../../core/state/baseState.js"; +import { RESEARCH_SAFETY_MAX_ITERATIONS } from "../../subgraphs/research/constants.js"; import type { AdditionalResearchRequest, Evaluation, @@ -155,10 +156,10 @@ export const WikiComposeState = Annotation.Root({ reducer: (_prev, next) => next, default: () => 0, }), - /** ループ上限(Brief で 1..5 にユーザー設定可、デフォルト 3)。 */ + /** ループ上限(自律調査の安全 cap。ingest 連携時のみ 1..5 の明示 cap あり)。 */ maxIterations: Annotation({ reducer: (prev, next) => next ?? prev, - default: () => 3, + default: () => RESEARCH_SAFETY_MAX_ITERATIONS, }), /** Research subgraph 内の直近クエリ。 */ queries: Annotation({ diff --git a/server/api/src/agents/graphs/wikiCompose/types.ts b/server/api/src/agents/graphs/wikiCompose/types.ts index a80c0114..cf39622b 100644 --- a/server/api/src/agents/graphs/wikiCompose/types.ts +++ b/server/api/src/agents/graphs/wikiCompose/types.ts @@ -272,8 +272,6 @@ export interface BriefResumeInput { answers: BriefAnswer[]; /** True when the user chose "append to existing body" (U2). */ appendToExisting?: boolean; - /** Optional override for the research loop's max iterations (1..5). */ - researchMaxIterations?: number; } /** Resume payload for the outline interrupt. */ diff --git a/server/api/src/agents/subgraphs/research/constants.ts b/server/api/src/agents/subgraphs/research/constants.ts new file mode 100644 index 00000000..73599e81 --- /dev/null +++ b/server/api/src/agents/subgraphs/research/constants.ts @@ -0,0 +1,44 @@ +/** + * Research loop limits for Wiki Compose autonomous exploration. + * Wiki Compose 自律調査ループの上限定数。 + */ + +/** + * Hard safety cap for autonomous research loops. Wiki Compose no longer exposes + * a user-facing iteration slider; the evaluator LLM decides when sources are + * sufficient (`score >= 0.75`). This constant prevents runaway loops only. + * + * ユーザー向けの調査回数設定は廃止。充足度評価 LLM が十分と判断するまで + * ループし、無限ループ防止のためだけにこの上限を使う。 + */ +export const RESEARCH_SAFETY_MAX_ITERATIONS = 10; + +/** + * Explicit iteration cap range accepted from ingest / legacy API callers (1..5). + * Values outside this range fall back to {@link RESEARCH_SAFETY_MAX_ITERATIONS}. + * + * ingest 等が明示的に渡す回数上限(1..5)。範囲外は自律モードの安全上限へ。 + */ +export const INGEST_EXPLICIT_MAX_ITERATIONS_MIN = 1; +export const INGEST_EXPLICIT_MAX_ITERATIONS_MAX = 5; + +/** + * Resolve the iteration cap for the research loop. + * + * - `1..5` → honour explicit caller cap (ingest planner). + * - otherwise → {@link RESEARCH_SAFETY_MAX_ITERATIONS} (autonomous Wiki Compose). + * + * @param raw Value from graph state before `plan_queries` runs. + */ +export function resolveResearchMaxIterations(raw: unknown): number { + if (typeof raw === "number" && Number.isFinite(raw)) { + const truncated = Math.trunc(raw); + if ( + truncated >= INGEST_EXPLICIT_MAX_ITERATIONS_MIN && + truncated <= INGEST_EXPLICIT_MAX_ITERATIONS_MAX + ) { + return truncated; + } + } + return RESEARCH_SAFETY_MAX_ITERATIONS; +} diff --git a/server/api/src/agents/subgraphs/research/nodes/compileBatch.ts b/server/api/src/agents/subgraphs/research/nodes/compileBatch.ts index b9561dfc..9f0a8122 100644 --- a/server/api/src/agents/subgraphs/research/nodes/compileBatch.ts +++ b/server/api/src/agents/subgraphs/research/nodes/compileBatch.ts @@ -9,6 +9,7 @@ import type { LangGraphRunnableConfig } from "@langchain/langgraph"; import { randomUUID } from "node:crypto"; import { dispatchResearchBatch } from "./shared/dispatchSseCustom.js"; +import { RESEARCH_SUFFICIENCY_SCORE_THRESHOLD } from "../shouldRefine.js"; import type { ResearchLoopStateType, ResearchLoopStateUpdate } from "../state.js"; import type { ExitReason, ResearchBatch } from "../types.js"; @@ -30,7 +31,9 @@ export async function compileBatch( ): Promise { const score = state.lastEvaluation?.score ?? null; const exitReason: ExitReason = - score !== null && score >= 0.75 ? "score_threshold" : "max_iterations"; + score !== null && score >= RESEARCH_SUFFICIENCY_SCORE_THRESHOLD + ? "score_threshold" + : "max_iterations"; const batch: ResearchBatch = { id: randomUUID(), iteration: state.iteration, diff --git a/server/api/src/agents/subgraphs/research/nodes/evaluateSufficiency.ts b/server/api/src/agents/subgraphs/research/nodes/evaluateSufficiency.ts index aefb2cae..92c7a881 100644 --- a/server/api/src/agents/subgraphs/research/nodes/evaluateSufficiency.ts +++ b/server/api/src/agents/subgraphs/research/nodes/evaluateSufficiency.ts @@ -6,7 +6,7 @@ * 現在の `pendingSources` が brief を満たしているかを LLM で評価し、 * `score` (0..1) と `missingAspects` を返す。post-increment した `iteration` * を返すことで、後段の `shouldRefine` がループ終了条件 - * (`score >= 0.75 || iteration >= maxIterations`) を正しく判定できる。 + * (`score >= threshold || iteration >= cap`) を正しく判定できる。 */ import type { LangGraphRunnableConfig } from "@langchain/langgraph"; import { z } from "zod"; @@ -15,6 +15,7 @@ import { createZediChatModel } from "../../../core/llm/modelFactory.js"; import { resolveWikiComposeModelId } from "../../../core/llm/wikiComposeModelId.js"; import { getGraphContext } from "./shared/getGraphContext.js"; import { dispatchResearchEvaluation } from "./shared/dispatchSseCustom.js"; +import { RESEARCH_SUFFICIENCY_SCORE_THRESHOLD } from "../shouldRefine.js"; import type { ResearchLoopStateType, ResearchLoopStateUpdate } from "../state.js"; import type { Evaluation } from "../types.js"; @@ -26,8 +27,10 @@ export const evaluationSchema = z.object({ const SYSTEM_PROMPT = "You are evaluating whether the research sources collected so far are sufficient " + - "to write the requested wiki article. Score 0..1 (≥0.75 means 'good enough'), " + - "give a short rationale, and list up to 5 missing aspects. Output JSON only."; + "to write the requested wiki article. Autonomously decide when coverage is good " + + `enough to proceed — score >= ${RESEARCH_SUFFICIENCY_SCORE_THRESHOLD} means sufficient. ` + + "Give a short rationale and list up to 5 missing aspects that would still matter " + + "for the article. Output JSON only."; function buildUserPrompt(state: ResearchLoopStateType): string { const brief = state.messages @@ -49,7 +52,8 @@ function buildUserPrompt(state: ResearchLoopStateType): string { `[Sources collected: ${state.pendingSources.length}]`, ...sourceLines, "", - `Iteration so far: ${state.iteration} / ${state.maxIterations}`, + `Research iteration completed so far: ${state.iteration}`, + "Continue refining only if important gaps remain for the brief.", ].join("\n"); } diff --git a/server/api/src/agents/subgraphs/research/nodes/planQueries.ts b/server/api/src/agents/subgraphs/research/nodes/planQueries.ts index 1e730584..672d0c45 100644 --- a/server/api/src/agents/subgraphs/research/nodes/planQueries.ts +++ b/server/api/src/agents/subgraphs/research/nodes/planQueries.ts @@ -2,7 +2,7 @@ * `plan_queries` — generates the initial query set for the research loop. * * 調査ループの最初のノード。Brief / 指示メッセージから 1〜8 件の調査クエリを - * 生成し、`maxIterations` を 1..5 にクランプする。"additional_research" 入力で + * 生成する。"additional_research" 入力で * 既存セッションの追加調査として呼ばれた場合、`iteration / lastEvaluation / * exitReason` をリセットし、`carryOverApprovedIds` で `pendingSources` を初期化 * する(issue #949 の追加調査 API パス)。 @@ -52,12 +52,7 @@ const SYSTEM_PROMPT = "and 'web' for queries needing fresh public information. Output JSON only."; import type { AdditionalResearchRequest } from "../types.js"; - -function clampMaxIterations(raw: unknown): number { - if (typeof raw !== "number" || !Number.isFinite(raw)) return 3; - const truncated = Math.trunc(raw); - return Math.min(Math.max(truncated, 1), 5); -} +import { resolveResearchMaxIterations } from "../constants.js"; function briefFromState( state: ResearchLoopStateType, @@ -97,9 +92,9 @@ export async function planQueries( const additional = state.additionalRequest ?? null; const brief = briefFromState(state, additional); - // Resolve maxIterations: input override > existing state > default(3); clamp 1..5. - // maxIterations は既存 state を優先しつつ 1..5 にクランプ。 - const maxIterations = clampMaxIterations(state.maxIterations ?? 3); + // Resolve maxIterations: explicit ingest cap (1..5) or autonomous safety cap. + // maxIterations は ingest の明示指定 (1..5) か、自律調査の安全上限。 + const maxIterations = resolveResearchMaxIterations(state.maxIterations); const modelId = await resolveWikiComposeModelId("orchestrator", ctx.tier, ctx.db); const model = await createZediChatModel({ diff --git a/server/api/src/agents/subgraphs/research/nodes/refineQueries.ts b/server/api/src/agents/subgraphs/research/nodes/refineQueries.ts index 95738e13..f3a5ee7d 100644 --- a/server/api/src/agents/subgraphs/research/nodes/refineQueries.ts +++ b/server/api/src/agents/subgraphs/research/nodes/refineQueries.ts @@ -30,7 +30,7 @@ function buildUserPrompt(state: ResearchLoopStateType): string { const prior = state.queries.map((q) => `- ${q.query} (${q.channels.join("/")})`); const sourceTitles = state.pendingSources.map((s) => `- [${s.kind}] ${s.title}`); return [ - `[Iteration ${state.iteration} / ${state.maxIterations}]`, + `[Research iteration ${state.iteration}]`, `Previous evaluation score: ${evaluation?.score ?? "n/a"}`, "", "[Missing aspects to address]", diff --git a/server/api/src/agents/subgraphs/research/researchGraph.ts b/server/api/src/agents/subgraphs/research/researchGraph.ts index bfb91d73..2dfe3f2d 100644 --- a/server/api/src/agents/subgraphs/research/researchGraph.ts +++ b/server/api/src/agents/subgraphs/research/researchGraph.ts @@ -5,7 +5,8 @@ * `fetch_articles` → `evaluate_sufficiency` を 1 イテレーションとし、 * `shouldRefine` の判定で `refine_queries` (= 次ループ) か `compile_batch` → * `human_review_research` (= HITL 中断) のいずれかに分岐する。終了条件: - * `score >= 0.75` OR `iteration >= maxIterations` (default 3, clamp 1..5)。 + * 評価 LLM が `score >= 0.75` と判断したとき、または安全上限 + * {@link RESEARCH_SAFETY_MAX_ITERATIONS}(ingest が 1..5 を明示した場合はその cap)。 * * Cyclic LangGraph with a parallel fan-out (`web_search ∥ wiki_search`) and a * conditional edge after `evaluate_sufficiency`. The HITL stop is implemented @@ -78,9 +79,10 @@ export function registerResearchLoopGraph(): void { phase: "research", description: "Wiki Compose P1: autonomous research loop. Plans queries, runs web + wiki search, " + - "fetches articles, evaluates sufficiency, optionally refines and re-loops up to " + - "maxIterations (1..5, default 3), then interrupts at human_review_research for " + - "HITL source approval. Resume payload: { approvedSourceIds, rejectedSourceIds?, note? }.", + "fetches articles, evaluates sufficiency, optionally refines and re-loops until the " + + "evaluator LLM deems sources sufficient (score >= 0.75) or a safety cap is reached, " + + "then interrupts at human_review_research for HITL source approval. " + + "Resume payload: { approvedSourceIds, rejectedSourceIds?, note? }.", factory, }); } diff --git a/server/api/src/agents/subgraphs/research/shouldRefine.ts b/server/api/src/agents/subgraphs/research/shouldRefine.ts index 1368b1ee..ed755e57 100644 --- a/server/api/src/agents/subgraphs/research/shouldRefine.ts +++ b/server/api/src/agents/subgraphs/research/shouldRefine.ts @@ -3,16 +3,19 @@ */ import type { ResearchLoopStateType } from "./state.js"; +/** Score at or above which the evaluator considers research sufficient. */ +export const RESEARCH_SUFFICIENCY_SCORE_THRESHOLD = 0.75; + /** * 終了条件判定。`evaluate_sufficiency` の直後に呼ばれる。 * - * - `score >= 0.75` → `"compile"` - * - `iteration >= maxIterations` → `"compile"` + * - `score >= 0.75` → `"compile"` (agent decided sources are sufficient) + * - `iteration >= maxIterations` → `"compile"` (explicit ingest cap or safety cap) * - otherwise → `"refine"` */ export function shouldRefine(state: ResearchLoopStateType): "refine" | "compile" { const score = state.lastEvaluation?.score; - if (typeof score === "number" && score >= 0.75) return "compile"; + if (typeof score === "number" && score >= RESEARCH_SUFFICIENCY_SCORE_THRESHOLD) return "compile"; if (state.iteration >= state.maxIterations) return "compile"; return "refine"; } diff --git a/server/api/src/agents/subgraphs/research/state.ts b/server/api/src/agents/subgraphs/research/state.ts index 425459bf..174ef929 100644 --- a/server/api/src/agents/subgraphs/research/state.ts +++ b/server/api/src/agents/subgraphs/research/state.ts @@ -15,6 +15,7 @@ */ import { Annotation } from "@langchain/langgraph"; import { BaseState } from "../../core/state/baseState.js"; +import { RESEARCH_SAFETY_MAX_ITERATIONS } from "./constants.js"; import type { AdditionalResearchRequest, Evaluation, @@ -61,10 +62,10 @@ export const ResearchLoopState = Annotation.Root({ reducer: (_prev, next) => next, default: () => 0, }), - /** ループ回数上限(1..5、デフォルト 3)。`plan_queries` で clamp 確定。 */ + /** ループ回数上限。ingest が 1..5 を明示した場合はその cap、それ以外は安全上限。 */ maxIterations: Annotation({ reducer: (prev, next) => next ?? prev, - default: () => 3, + default: () => RESEARCH_SAFETY_MAX_ITERATIONS, }), /** 直近のクエリリスト。`plan_queries` / `refine_queries` が全置換する。 */ queries: Annotation({ diff --git a/server/api/src/routes/composeSessions.ts b/server/api/src/routes/composeSessions.ts index 3763a8cf..3be84a3a 100644 --- a/server/api/src/routes/composeSessions.ts +++ b/server/api/src/routes/composeSessions.ts @@ -16,9 +16,9 @@ * * `wiki-compose-research` (#949 / P1): * - `POST /run` body.input shapes: - * - Initial run: `{ messages?: [...], maxIterations?: number }` (or any + * - Initial run: `{ messages?: [...] }` (or any * object; the graph reads `state.messages` set by LangGraph from - * `body.input`). + * `body.input`). Research depth is decided autonomously by the evaluator LLM. * - Additional research (re-run on a *new* session of the same graph id): * `{ kind: "additional_research", instruction: string, brief?: string, * carryOverApprovedIds?: string[] }` diff --git a/src/components/wikiCompose/ComposePanel.tsx b/src/components/wikiCompose/ComposePanel.tsx index 481e1a3e..65e0fa28 100644 --- a/src/components/wikiCompose/ComposePanel.tsx +++ b/src/components/wikiCompose/ComposePanel.tsx @@ -51,11 +51,7 @@ export interface ComposePanelProps { activity: ComposeActivity[]; - onSubmitBrief: (input: { - answers: BriefAnswer[]; - appendToExisting?: boolean; - researchMaxIterations?: number; - }) => Promise; + onSubmitBrief: (input: { answers: BriefAnswer[]; appendToExisting?: boolean }) => Promise; onSubmitResearchApproval: (input: { approvedSourceIds: string[]; rejectedSourceIds?: string[]; diff --git a/src/components/wikiCompose/DialogueSection.tsx b/src/components/wikiCompose/DialogueSection.tsx index 5d9a0f20..cbe8c2f5 100644 --- a/src/components/wikiCompose/DialogueSection.tsx +++ b/src/components/wikiCompose/DialogueSection.tsx @@ -11,7 +11,7 @@ */ import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Card, CardContent, CardHeader, CardTitle, Slider } from "@zedi/ui"; +import { Button, Card, CardContent, CardHeader, CardTitle } from "@zedi/ui"; import { Sparkles, RefreshCw, ArrowRight } from "lucide-react"; import type { BriefAnswer, @@ -30,11 +30,7 @@ export interface DialogueSectionProps { outlineProposal: OutlineSection[]; isStreaming: boolean; /** Brief submission. */ - onSubmitBrief: (input: { - answers: BriefAnswer[]; - appendToExisting?: boolean; - researchMaxIterations?: number; - }) => Promise; + onSubmitBrief: (input: { answers: BriefAnswer[]; appendToExisting?: boolean }) => Promise; /** Structure submission. */ onSubmitOutline: (input: { sections: OutlineSection[] }) => Promise; } @@ -70,7 +66,6 @@ export const DialogueSection: React.FC = ({ const [appendToExisting, setAppendToExisting] = useState( Boolean(pageSnapshot?.hasContent), ); - const [maxIterations, setMaxIterations] = useState(3); const [submitting, setSubmitting] = useState(false); const canSubmitBrief = useMemo( @@ -139,31 +134,6 @@ export const DialogueSection: React.FC = ({ ) : null} - - - - {t("wikiCompose.brief.researchDepthTitle")} - - - -
- {t("wikiCompose.brief.researchQuick")} - - {t("wikiCompose.brief.iterationCount", { count: maxIterations })} - - {t("wikiCompose.brief.researchDeep")} -
- setMaxIterations(v[0] ?? 3)} - /> -
-
-