Fix web search date handling
This commit is contained in:
parent
c3a6f7deee
commit
ade3a847f6
|
|
@ -5,6 +5,10 @@
|
|||
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
||||
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
||||
|
||||
## 0.60.1 — 2026-07-27
|
||||
|
||||
- 联网搜索现在会以当前北京时间判断时效:未指定时间时不再擅自给搜索词添加旧年份,查询“最新、当前、近期”内容时会采用合适的近期结果范围;用户明确指定的年份或时间窗保持优先。
|
||||
|
||||
## 0.60.0 — 2026-07-27
|
||||
|
||||
- 登录后的空白页现在可以直接选择已有工作目录或新建工作目录,然后开始对话;点击左栏「+ 新对话」也进入同一个快速入口,发送第一条消息时才真正创建对话。
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-07-27(目录优先的新对话草稿页 + 首条消息自动命名,bump 0.60.0)
|
||||
最后更新:2026-07-27(联网搜索动态时效约束,bump 0.60.1)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
### 2026-07
|
||||
|
||||
- **07-27 / 0.60.1 / 联网搜索动态时效约束**:真实 task `24d6b609` 在 2026-07-27、用户未指定年份时,`deepseek_v4.flash` 两次自行给 `web_search` query 加 `2025`;工具原样转发且默认 `freshness=noLimit`,根因是 system prompt 虽注入 today 却仅标作宪法文件命名用途,工具 schema 也无年份策略。修为 system prompt 首段按 `Asia/Shanghai` 动态注入当前时间/日期/年份,并给唯一规则:未指定时间则 query 无年份,“最新/当前/近期”按当前日期配合 freshness,明确时间窗则服从用户;`web_search` 的 query/freshness schema 再局部强化。5 项定向测试通过。
|
||||
- **07-27 / 0.60.0 / 目录优先的新对话草稿页 + 首条消息自动命名**:登录未选任务与左栏「+ 新对话」统一进入不落 DB 的草稿页,选择/新建 working_dir 后首发才创建 task,避免空任务;原完整表单保留为「自定义」。新增 0023 `tasks.auto_title_pending` 一次性闸与 `auto_title` 可选创建字段,首条消息并行生成短标题(`usage_events.kind=task_title`),只改 task 显示名不动目录,人工 PATCH name 清闸且条件 UPDATE 防在途覆盖;失败保留「新对话」不阻塞主 run。
|
||||
- **07-27 / 0.59.6 / 对话内安全重命名 working_dir**:新增 `rename_working_dir` 受控工具,agent run 内只登记目标 leaf 名,正常回复结束并把当前 task 退出 running 后再落地,避免 executor/system prompt/宿主工具仍握旧 cwd;取消或失败不执行。文件面板与对话路径共用 `core/working_dirs.py` DB-aware 原语:锁定并同步更新共享目录的全部 task、活跃邻居与 no-subtask 冲突安全拒绝、DB UPDATE 后再做 FS rename。system prompt 明确当前 task_dir 禁走 shell/run_python 直接改名,定时 run 不挂该工具;无新增 schema/migration。全量 357 测试通过(17 项按环境跳过),DB 路由测试未设 `ZCBOT_TEST_DB_URL` 时按安全门控跳过。
|
||||
- **07-27 / 0.59.5 / Seedance 图生视频(单图首帧 + 最多 9 张多图参考)**:`seedance` 在原文生视频契约上向后兼容增加 `image`(唯一 `first_frame`)与 `reference_images`(0-9 张 `reference_image`),两者合计最多 9 张;复用 image_ref 的 task/user_root 三形态路径解析、越界防护与 base64 内联,新增单图 10MB / 合计 30MB 请求体闸。请求按有无图片切图生/文生单价,banner/meta/usage snapshot 记录 mode、首帧及参考图;videogen skill、工具系统提示、RUN 与能力清单同步。5 个离线单测覆盖单图、多图、文生兼容、越界与超量拒绝。
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.60.0"
|
||||
__version__ = "0.60.1"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from datetime import datetime
|
|||
from pathlib import Path
|
||||
from typing import Any, Callable, Optional, Tuple
|
||||
from uuid import UUID, uuid4
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import yaml
|
||||
from rich.console import Console
|
||||
|
|
@ -320,7 +321,21 @@ def _build_system_prompt(
|
|||
不强求同步(由 short_id 兜底定位)。
|
||||
today 当场算,落 prompt 给 LLM 直接拼路径(避免 LLM 不知道当前日期)。
|
||||
"""
|
||||
prompt = (ROOT / cfg["system_prompt"]).read_text(encoding="utf-8")
|
||||
now = datetime.now(ZoneInfo("Asia/Shanghai"))
|
||||
today = now.strftime("%Y-%m-%d")
|
||||
current_year = now.year
|
||||
time_block = (
|
||||
"## 当前时间与时效\n"
|
||||
f"- **当前时间**: `{now.strftime('%Y-%m-%d %H:%M:%S')} Asia/Shanghai`\n"
|
||||
f"- **当前日期**: `{today}`;**当前年份**: `{current_year}`\n"
|
||||
"- 当前时间事实以本段为唯一依据,不使用模型训练时间推断年份。\n"
|
||||
"- 用户未指定时间范围时,搜索词保持无年份;用户要求“最新 / 当前 / 近期”时,"
|
||||
"以本段日期为准,并按需要使用当前年份及搜索工具的 freshness;"
|
||||
"用户明确指定年份或时间窗时,以用户要求为准。\n"
|
||||
)
|
||||
prompt = time_block + "\n" + (
|
||||
ROOT / cfg["system_prompt"]
|
||||
).read_text(encoding="utf-8")
|
||||
# docker backend 下 shell/run_python/fs 工具全在容器里跑,容器把
|
||||
# `<workspace>/users/<uid>` bind 到 `/workspace`、`--workdir /workspace/<wd>`
|
||||
# (executor_docker.py:99-100)。此时 prompt 给 agent 的所有可写/可读绝对路径
|
||||
|
|
@ -362,7 +377,6 @@ def _build_system_prompt(
|
|||
render_cmd = "python /sandbox/rendering/render.py"
|
||||
else:
|
||||
render_cmd = f'"{sys.executable}" "{ROOT / "rendering" / "render.py"}"'
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
tname = task_name or "<未指定>"
|
||||
short_id = task_id.hex[:8]
|
||||
skill_line = (
|
||||
|
|
|
|||
|
|
@ -42,6 +42,13 @@ def _build(backend: str, *, allow_rename: bool = True) -> tuple[str, Path, Path]
|
|||
|
||||
|
||||
class TestSystemPromptPaths(unittest.TestCase):
|
||||
def test_current_time_rules_are_at_prompt_start(self):
|
||||
prompt, _, _ = _build("docker")
|
||||
self.assertTrue(prompt.startswith("## 当前时间与时效\n"))
|
||||
self.assertIn("Asia/Shanghai", prompt)
|
||||
self.assertIn("用户未指定时间范围时,搜索词保持无年份", prompt)
|
||||
self.assertIn("搜索工具的 freshness", prompt)
|
||||
|
||||
def test_docker_prompt_uses_container_path_no_host_leak(self):
|
||||
prompt, ws, wd = _build("docker")
|
||||
# 容器路径在
|
||||
|
|
@ -66,6 +73,15 @@ class TestSystemPromptPaths(unittest.TestCase):
|
|||
self.assertIn("必须调用 `rename_working_dir`", interactive)
|
||||
self.assertNotIn("必须调用 `rename_working_dir`", scheduled)
|
||||
|
||||
def test_web_search_schema_reinforces_time_rules(self):
|
||||
from tools.web_search import WebSearchTool
|
||||
|
||||
query_desc = WebSearchTool.parameters["properties"]["query"]["description"]
|
||||
freshness_desc = WebSearchTool.parameters["properties"]["freshness"]["description"]
|
||||
self.assertIn("omit years", query_desc)
|
||||
self.assertIn("current date/year", query_desc)
|
||||
self.assertIn("narrowest window", freshness_desc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,15 @@ class WebSearchTool(Tool):
|
|||
parameters = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query string"},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Search query string. Follow the current date in the system prompt: "
|
||||
"omit years when the user gives no time range; for latest/current/recent "
|
||||
"requests, use the current date/year only when semantically useful; "
|
||||
"honor any user-specified year or time window."
|
||||
),
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
|
|
@ -25,7 +33,11 @@ class WebSearchTool(Tool):
|
|||
"type": "string",
|
||||
"enum": ["noLimit", "oneDay", "oneWeek", "oneMonth", "oneYear"],
|
||||
"default": "noLimit",
|
||||
"description": "Filter results by recency",
|
||||
"description": (
|
||||
"Result recency window: noLimit, past day, week, month, or year. "
|
||||
"For latest/current/recent requests, choose the narrowest window that "
|
||||
"still fits the user's intent; otherwise keep noLimit."
|
||||
),
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue