feat: trust explicit user goals
This commit is contained in:
@@ -15,6 +15,8 @@ Agent 使用 DeepSeek 的思考模式,结合最近的连续原文与精简想
|
||||
- 不要求用户建立会话;系统在后台保留滚动的连续语境。
|
||||
- 后台的“想法”是广义的演化脉络,也包括项目、工作、学习、问题和现实承诺。
|
||||
- 后续输入让一个方向成形时,系统可以把先前暂存的原文补回完整证据链。
|
||||
- 用户明确说这是自己的目标或方向时,系统默认相信并跟踪;语气、野心和暂时没有路径
|
||||
不构成静默丢弃的理由。
|
||||
- 成熟度是可回退的连续位势,不是阶段、成绩或任务完成百分比。
|
||||
- 运动、张力和可能动作由模型结合上下文动态生成,不使用固定关卡。
|
||||
- 用户手动校准的位势优先展示,AI 估计仍独立保留。
|
||||
|
||||
+15
-1
@@ -29,7 +29,7 @@ from .db import Database
|
||||
from .schemas import CuratorDecision
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
PROMPT_VERSION = "maturity-2026-07-29.v4"
|
||||
PROMPT_VERSION = "maturity-2026-07-29.v5"
|
||||
RECENT_CONTEXT_LIMIT = 12
|
||||
IDEA_CATALOG_LIMIT = 40
|
||||
|
||||
@@ -51,6 +51,8 @@ CURATOR_INSTRUCTIONS = """
|
||||
|
||||
0—100 只是连续的“位势坐标”,不是阶段、成绩或完成百分比。成熟度可以后退;新材料也可能
|
||||
让一个看似成熟的想法重新打开。禁止用笔记数量、时间、链接数、是否列清单来机械打分。
|
||||
位势也绝不是对用户能力、品格或成功概率的评分。目标规模大、困难、像玩笑、暂时没有方案,
|
||||
都不能成为降低对用户信任或拒绝跟踪的理由;只描述这个想法本身已经长出了什么。
|
||||
|
||||
motion 是你对当下运动的自然语言压缩,例如“向现实探去”“重构中”“暂时沉淀”,不使用
|
||||
预设流水线。position 说明此刻所处的位置。trajectory 只描述最近发生的变化。possible_moves
|
||||
@@ -65,6 +67,17 @@ motion 是你对当下运动的自然语言压缩,例如“向现实探去”
|
||||
不值得跟踪的杂讯。只有纯粹随手事实、情绪或转瞬观察,既无可辨认对象也无未来张力,或信息
|
||||
少到无法诚实命名时,才 standalone。
|
||||
|
||||
你的默认姿态是“有根据的信任”:相信用户主动写下的方向值得被认真对待,同时不做空洞吹捧。
|
||||
用户拥有自己话语的严肃程度;不要根据口语、幽默、夸张、野心或你对可行性的猜测,擅自把它
|
||||
解释成“随口说说”。当用户明确把某件事称为自己的目标、想法、项目、工作、计划、问题、
|
||||
研究、决定或承诺时,direction_signal 必须是 explicit,最终不能 standalone;除非用户自己
|
||||
明确否定它。即使没有期限、方法或下一步,也应建立低位势脉络:缺少的部分进入 position 和
|
||||
tension,不能成为拒绝创建的门槛。
|
||||
|
||||
保持诚实,不做空洞吹捧。你可以直说一个目标缺乏路径、远离现实或存在巨大矛盾,也可以给出
|
||||
很低的位势;但这些判断只能描述想法当前的状态,不能反过来否定用户已经宣告的方向、推断用户
|
||||
只是开玩笑,或把它静默丢弃。possible_moves 应提供现实接触点,而不是用任务惩罚用户。
|
||||
|
||||
谨慎合并。仅因主题词相似不能归到同一想法;关注它们是否共享同一个问题、张力或生成方向。
|
||||
一个片段最多关联两个想法。新建必须克制,但克制意味着不凭关键词重复建,不意味着压制真实
|
||||
的新方向。新建想法时 idea_id 必须为 null;更新时必须使用工具返回的准确 id。
|
||||
@@ -717,6 +730,7 @@ class Curator:
|
||||
user_id,
|
||||
"decision_committed",
|
||||
{
|
||||
"direction_signal": decision.direction_signal,
|
||||
"standalone": decision.standalone,
|
||||
"reasoning_note": decision.reasoning_note,
|
||||
"assessments": assessments,
|
||||
|
||||
+22
-2
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
@@ -36,7 +36,14 @@ class IdeaAssessment(BaseModel):
|
||||
)
|
||||
title: str = Field(min_length=1, max_length=80)
|
||||
summary: str = Field(min_length=1, max_length=280)
|
||||
maturity: float = Field(ge=0, le=100)
|
||||
maturity: float = Field(
|
||||
ge=0,
|
||||
le=100,
|
||||
description=(
|
||||
"Position of the evolving idea itself, never a score of the user or "
|
||||
"a probability of success."
|
||||
),
|
||||
)
|
||||
confidence: float = Field(ge=0, le=1)
|
||||
motion: str = Field(min_length=1, max_length=24)
|
||||
position: str = Field(min_length=1, max_length=120)
|
||||
@@ -73,6 +80,15 @@ class IdeaAssessment(BaseModel):
|
||||
|
||||
|
||||
class CuratorDecision(BaseModel):
|
||||
direction_signal: Literal["explicit", "implicit", "none"] = Field(
|
||||
description=(
|
||||
"explicit when the user names this as their goal, idea, project, "
|
||||
"work, plan, question, inquiry, decision, or commitment; implicit "
|
||||
"when a future-bearing direction is inferred; none when no such "
|
||||
"direction is present. Tone, ambition, feasibility, and missing "
|
||||
"steps must not downgrade an explicit signal."
|
||||
)
|
||||
)
|
||||
standalone: bool = Field(
|
||||
description="True when the fragment should remain only in the raw stream for now."
|
||||
)
|
||||
@@ -84,6 +100,10 @@ class CuratorDecision(BaseModel):
|
||||
|
||||
@model_validator(mode="after")
|
||||
def decision_is_coherent(self) -> "CuratorDecision":
|
||||
if self.direction_signal == "explicit" and self.standalone:
|
||||
raise ValueError(
|
||||
"an explicit user-declared direction cannot remain standalone"
|
||||
)
|
||||
if self.standalone and self.assessments:
|
||||
raise ValueError("standalone decisions cannot contain assessments")
|
||||
if not self.standalone and not self.assessments:
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_tool_loop_uses_no_tool_convergence_repair(
|
||||
return SimpleNamespace(
|
||||
final_output=(
|
||||
'{"standalone":true,"reasoning_note":"暂时独立保留",'
|
||||
'"assessments":[]}'
|
||||
'"direction_signal":"none","assessments":[]}'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -100,6 +100,7 @@ def test_curator_recovers_visible_prior_fragment(
|
||||
return SimpleNamespace(
|
||||
final_output=(
|
||||
"{"
|
||||
'"direction_signal":"explicit",'
|
||||
'"standalone":false,'
|
||||
'"reasoning_note":"明确的新工作,应形成可跟踪脉络",'
|
||||
'"assessments":[{'
|
||||
|
||||
+29
-1
@@ -1,8 +1,16 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.config import UserSeed
|
||||
from app.curator import _rank_ideas, build_curator_prompt
|
||||
from app.curator import (
|
||||
CURATOR_INSTRUCTIONS,
|
||||
_rank_ideas,
|
||||
build_curator_prompt,
|
||||
)
|
||||
from app.db import Database
|
||||
from app.schemas import CuratorDecision
|
||||
|
||||
|
||||
ADMIN = UserSeed(
|
||||
@@ -128,6 +136,26 @@ def test_curator_prompt_contains_recent_continuity_and_idea_catalog(
|
||||
assert "是否形成新的未完成方向" in prompt
|
||||
|
||||
|
||||
def test_explicit_user_direction_cannot_be_silently_discarded():
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match="explicit user-declared direction cannot remain standalone",
|
||||
):
|
||||
CuratorDecision.model_validate(
|
||||
{
|
||||
"direction_signal": "explicit",
|
||||
"standalone": True,
|
||||
"reasoning_note": "目标太大,暂时不值得跟踪",
|
||||
"assessments": [],
|
||||
}
|
||||
)
|
||||
|
||||
assert "有根据的信任" in CURATOR_INSTRUCTIONS
|
||||
assert "用户拥有自己话语的严肃程度" in CURATOR_INSTRUCTIONS
|
||||
assert "不能成为拒绝创建的门槛" in CURATOR_INSTRUCTIONS
|
||||
assert "保持诚实,不做空洞吹捧" in CURATOR_INSTRUCTIONS
|
||||
|
||||
|
||||
def test_later_fragment_can_recover_earlier_standalone_evidence(
|
||||
tmp_path: Path,
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user