Fix web search date handling

This commit is contained in:
caoqianming 2026-07-27 12:50:07 +08:00
parent c3a6f7deee
commit ade3a847f6
6 changed files with 53 additions and 6 deletions

View File

@ -5,6 +5,10 @@
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.60.1 — 2026-07-27
- 联网搜索现在会以当前北京时间判断时效:未指定时间时不再擅自给搜索词添加旧年份,查询“最新、当前、近期”内容时会采用合适的近期结果范围;用户明确指定的年份或时间窗保持优先。
## 0.60.0 — 2026-07-27
- 登录后的空白页现在可以直接选择已有工作目录或新建工作目录,然后开始对话;点击左栏「+ 新对话」也进入同一个快速入口,发送第一条消息时才真正创建对话。

View File

@ -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 个离线单测覆盖单图、多图、文生兼容、越界与超量拒绝。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
__version__ = "0.60.0"
__version__ = "0.60.1"

View File

@ -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 = (

View File

@ -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()

View File

@ -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"],