feat(agent): list_facts 改为分页+关键词过滤,避免上下文膨胀
fact 多时旧的 list_facts 一次返回每任务至多 500 条且 summary 不截断,
一次调用就可能撑爆上下文。改为分页(默认 20,最新在前)+ q 关键词过滤,
返回 {facts, total, has_more, next_before},summary 超长按 rune 截断。
- db: listByKindPageFiltered/countByKindFiltered(payload->>'summary' ILIKE)
+ ListByKindPageWithSources 跨本任务/继承任务合并分页
- agent: list_facts 加 limit/before/q 参数,compactFact 截断 summary;
worker/planner 提示词同步说明分页语义
- server: 旧库一次性迁移把 list_facts 新 schema 刷进 tools 目录表
(SeedTool 首插入only,否则工具管理页显示「无参数」);flag bump 到 v7
This commit is contained in:
1 parent
6b6b56e0a7
commit
10f6a4fbc7
7 files changed
+139
-11
No files matched your search
@@ -672,6 +672,40 @@ ORDER BY id DESC LIMIT $4`, s.expID, kind, before, limit+1)
|
||||
return nodes, hasMore, nil
|
||||
}
|
||||
|
||||
// listByKindPageFiltered is ListByKindPage with an optional summary keyword
|
||||
// filter (payload->>'summary' ILIKE %q%). Fetches one extra row so the caller can
|
||||
// probe hasMore. Empty q = no filter. Newest-first by id.
|
||||
func (s *ExplorationStore) listByKindPageFiltered(kind string, before int64, limit int, q string) ([]*Node, error) {
|
||||
where := `exploration_id=$1 AND kind=$2 AND ($3 <= 0 OR id < $3)`
|
||||
args := []any{s.expID, kind, before}
|
||||
if q != "" {
|
||||
args = append(args, "%"+q+"%")
|
||||
where += fmt.Sprintf(` AND payload->>'summary' ILIKE $%d`, len(args))
|
||||
}
|
||||
args = append(args, limit+1)
|
||||
rows, err := s.db.Query(`SELECT `+nodeCols+` FROM exploration_nodes
|
||||
WHERE `+where+` ORDER BY id DESC LIMIT $`+fmt.Sprintf("%d", len(args)), args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanNodes(rows)
|
||||
}
|
||||
|
||||
// countByKindFiltered counts nodes of a kind under the same summary filter, for a
|
||||
// page's total. Empty q = count all of that kind.
|
||||
func (s *ExplorationStore) countByKindFiltered(kind, q string) (int, error) {
|
||||
where := `exploration_id=$1 AND kind=$2`
|
||||
args := []any{s.expID, kind}
|
||||
if q != "" {
|
||||
args = append(args, "%"+q+"%")
|
||||
where += fmt.Sprintf(` AND payload->>'summary' ILIKE $%d`, len(args))
|
||||
}
|
||||
var n int
|
||||
err := s.db.QueryRow(`SELECT COUNT(*) FROM exploration_nodes WHERE `+where, args...).Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// GetNode returns one node of this exploration by id (nil, nil if not found).
|
||||
func (s *ExplorationStore) GetNode(id int64) (*Node, error) {
|
||||
n, err := scanNode(s.db.QueryRow(`SELECT `+nodeCols+` FROM exploration_nodes WHERE id=$1 AND exploration_id=$2`, id, s.expID))
|
||||
|
||||
@@ -108,6 +108,53 @@ func (s *ExplorationStore) ListByKindWithSources(kind string, limit int) ([]*Nod
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListByKindPageWithSources is the paginated, keyword-filterable sibling of
|
||||
// ListByKindWithSources: it returns one newest-first page (id < before, before<=0
|
||||
// = newest) spanning this exploration and its direct sources, plus hasMore and the
|
||||
// filtered total across all of them. Node ids are globally unique, so merging each
|
||||
// store's own page and re-sorting by id DESC yields the true global page; fetching
|
||||
// limit+1 per store guarantees the merged top-`limit` is complete.
|
||||
func (s *ExplorationStore) ListByKindPageWithSources(kind string, before int64, limit int, q string) (nodes []*Node, hasMore bool, total int, err error) {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
own, err := s.listByKindPageFiltered(kind, before, limit, q)
|
||||
if err != nil {
|
||||
return nil, false, 0, err
|
||||
}
|
||||
merged := own
|
||||
total, err = s.countByKindFiltered(kind, q)
|
||||
if err != nil {
|
||||
return nil, false, 0, err
|
||||
}
|
||||
|
||||
sources, err := s.DirectSourceStores()
|
||||
if err != nil {
|
||||
return nil, false, 0, err
|
||||
}
|
||||
for _, source := range sources {
|
||||
page, err := source.Store.listByKindPageFiltered(kind, before, limit, q)
|
||||
if err != nil {
|
||||
return nil, false, 0, err
|
||||
}
|
||||
for _, n := range page {
|
||||
merged = append(merged, markInheritedNode(n, source.Task.TaskID))
|
||||
}
|
||||
cnt, err := source.Store.countByKindFiltered(kind, q)
|
||||
if err != nil {
|
||||
return nil, false, 0, err
|
||||
}
|
||||
total += cnt
|
||||
}
|
||||
|
||||
sort.Slice(merged, func(i, j int) bool { return merged[i].ID > merged[j].ID })
|
||||
hasMore = len(merged) > limit
|
||||
if hasMore {
|
||||
merged = merged[:limit]
|
||||
}
|
||||
return merged, hasMore, total, nil
|
||||
}
|
||||
|
||||
// GetNodeWithSources reads a node only when it belongs to this exploration or
|
||||
// one of its direct sources. Inherited nodes are tagged so tool callers can keep
|
||||
// them read-only and show their provenance.
|
||||
|
||||
Reference in new issue
Block a user