From 01f223aaa1c4323aacbf5ab4c1f0fc8ed8d5f76e Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:15:48 -0400 Subject: [PATCH] =?UTF-8?q?feat(planner):=20done=20=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E5=9D=97=E5=88=97=E5=87=BA=E6=9C=AC=E6=84=8F=E5=9B=BE=E6=96=B0?= =?UTF-8?q?=E4=BA=A7=E7=94=9F=E7=9A=84=20fact=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit worker 结束触发规划时,除输出结论外,追加该意图经 yields 边产生的 fact id 列表,让规划者聚焦本轮增量、可直接 node_detail 查详情。 新增 ExplorationStore.FactsYielded 查询。 --- agent/planner.go | 20 +++++++++++++++++++- db/exploration.go | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/agent/planner.go b/agent/planner.go index ccfcc2e..91b6d1c 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -178,12 +178,30 @@ func renderTriggers(ts *db.ExplorationStore, evs []TriggerEvent) string { b.WriteString(fmt.Sprintf("\n- 意图 #%d 由用户删除,意图内容是:%s、删除原因是:%s。该意图已停止(不再执行),其原因已作为事实挂在该意图上;请据此重新规划。", ev.IntentID, intentSummary(ts, ev.IntentID), ev.Detail)) default: // "done" b.WriteString(fmt.Sprintf("\n- 意图 #%d(%s)的 worker 结束,输出结论:%s", ev.IntentID, intentSummary(ts, ev.IntentID), workerOutput(ts, ev.IntentID))) + if fids := factIDsYielded(ts, ev.IntentID); fids != "" { + b.WriteString(fmt.Sprintf(";本意图新产生的事实 id:%s ", fids)) + } } } b.WriteString("\n(完整细节可 node_detail / get_worker_output / list_findings 再查。)") return b.String() } +// factIDsYielded lists the fact ids an intent produced this run as "#12、#15", so the +// planner can jump straight to the round's incremental facts. Empty (best-effort) when +// the intent yielded no facts or the lookup fails. +func factIDsYielded(ts *db.ExplorationStore, id int64) string { + ids, err := ts.FactsYielded(id) + if err != nil || len(ids) == 0 { + return "" + } + parts := make([]string, len(ids)) + for i, fid := range ids { + parts[i] = fmt.Sprintf("#%d", fid) + } + return strings.Join(parts, "、") +} + // intentSummary reads an intent node's one-line summary (best-effort, "?" on miss). func intentSummary(ts *db.ExplorationStore, id int64) string { n, err := ts.GetNode(id) @@ -381,7 +399,7 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts // clamped(被任务 deadline 夹逼)时改用 PromptByReason(见 wrapupSettlementForTask)。 Settlement: settle, NonStreaming: p.nonStreaming(), // 该 profile 选非流式时走 Provider.Complete - MaxTokens: p.maxTokens(), // 0 = 不发上限,由服务端默认值决定 + MaxTokens: p.maxTokens(), // 0 = 不发上限,由服务端默认值决定 } if p.tx != nil { // persist raw LLM conversation; one accumulating file per task's planner opts.Transcript = p.tx diff --git a/db/exploration.go b/db/exploration.go index a4260c2..c90a290 100644 --- a/db/exploration.go +++ b/db/exploration.go @@ -768,6 +768,31 @@ func (s *ExplorationStore) Edges(limit int) ([]Edge, error) { return out, rows.Err() } +// FactsYielded returns the ids of fact nodes an intent produced (intent --yields--> +// fact), oldest first. Used to spell out the round's incremental facts to the planner +// alongside the finished worker's output. Best-effort: returns nil for an intent with +// no facts (or a missing one). +func (s *ExplorationStore) FactsYielded(intentID int64) ([]int64, error) { + rows, err := s.db.Query(`SELECT n.id + FROM exploration_edges e + JOIN exploration_nodes n ON n.id=e.dst_id AND n.exploration_id=e.exploration_id + WHERE e.exploration_id=$1 AND e.src_id=$2 AND e.rel=$3 AND n.kind=$4 + ORDER BY n.id`, s.expID, intentID, RelYields, KindFact) + if err != nil { + return nil, err + } + defer rows.Close() + var ids []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + ids = append(ids, id) + } + return ids, rows.Err() +} + // FindingLineage returns the sub-DAG that leads FROM the exploration root TO the // given node: the node itself plus all its ancestors (nodes reverse-reachable by // following edges backward), and every edge whose both endpoints are in that set.