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