mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-15 23:31:05 +00:00
* refractor(proactive): upgrade proactive feature with disentangled job and steps * refactor(proactive): apply audit fixes - rename read-side job 'proactive' -> 'proactive_read' (less confusing vs the refresh pipeline) - drop dedicated agent_wrapper.proactive; extraction reuses the default wrapper - simplify schema: remove unused ProactiveExtractOutput/TopicUpdate, drop resource_paths - extract no longer scans resource/ directly (daily notes already carry resource content) - update tests and docs accordingly * feat(proactive): strict extract-output gate and prompt total budget - parse_extract_reply now requires a contract section (follow_ups/extends/updates as a list); non-empty replies with misspelled section names trigger the existing one-shot retry instead of silently checkpointing changed files - pack_paths gains max_total_chars; extract packs newest daily material first, keeps the first file on overflow, and records omitted files in a trailer (default budget 300000 chars, configurable via max_total_chars) - tests: schema gate unit, schema-error retry e2e, budget unit + e2e * feat(proactive): add scenario-card plan step and generative agenda step * feat(proactive): digest-personal profile personalization and leaner LLM contract - extract/plan/agenda now draw a user profile block from <digest_dir>/personal/*.md (frontmatter description + body excerpt, per-file budget, profile.md fallback) - all daily access honours the configured daily_dir (prompt paths parameterized, config-driven fallbacks) so workspaces using e.g. memory/ work unchanged - schema trim: drop dead fields errors/material_paths, carry_forward_all -> count - shrink LLM output contract: new topics emit title/reason/confidence/paths only; keywords removed end-to-end, evidence derived from paths[0] (updates keep it) * fix(proactive): skip checkpoint when extract reply stays unusable after retry Two consecutive unparseable replies now short-circuit the round without checkpointing, so the same material is retried next round instead of being silently consumed (closes the residual audit #1 gap: the structural gate detected schema-wrong output but a double failure still checkpointed). * fix(proactive): replace running bool with reference-counted job activity tracker for the idle gate * refactor(proactive): remove job activity tracking and idle gate, restore job tree to upstream * fix(proactive): address second audit round (readonly reader, mtime checkpoint, wider fallbacks, profile containment, horizon content, expiry boundary) * refactor(dream): strip interests.yaml ownership from dream, proactive is now the sole writer * refactor(dream): separate proactive topic generation * ci: update renamed auto dream smoke test * fix(proactive): complete refresh migration and docs --------- Co-authored-by: jinli.yl <jinli.yl@alibaba-inc.com>
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""Shared helpers for evolve steps."""
|
|
|
|
import datetime
|
|
import zoneinfo
|
|
|
|
from agentscope.message import Msg
|
|
|
|
from ...schema import Response
|
|
|
|
|
|
def now(timezone: str | None = None) -> datetime.datetime:
|
|
"""Return current datetime in the given IANA timezone, falling back to local."""
|
|
if not timezone:
|
|
return datetime.datetime.now()
|
|
try:
|
|
return datetime.datetime.now(zoneinfo.ZoneInfo(timezone))
|
|
except Exception:
|
|
return datetime.datetime.now()
|
|
|
|
|
|
def format_history(messages: list[Msg], include_timestamp: bool = True) -> str:
|
|
"""Render a conversation slice as a human-readable transcript."""
|
|
lines: list[str] = []
|
|
for msg in messages:
|
|
text = (msg.get_text_content() or "").strip()
|
|
if not text:
|
|
continue
|
|
speaker = msg.name or msg.role or "?"
|
|
header = f"[{speaker} @ {msg.created_at}]" if include_timestamp else f"[{speaker}]"
|
|
lines.append(f"{header}\n{text}")
|
|
return "\n\n".join(lines) or "(empty)"
|
|
|
|
|
|
def agent_reply_result_text(reply_result: dict) -> str:
|
|
"""Return the final user-visible text block from an agent reply result."""
|
|
last_message = reply_result.get("last_message") or {}
|
|
content = last_message.get("content") if isinstance(last_message, dict) else None
|
|
if isinstance(content, list):
|
|
for block in reversed(content):
|
|
if isinstance(block, dict) and block.get("type") == "text":
|
|
text = str(block.get("text") or "").strip()
|
|
if text:
|
|
return text
|
|
return str(reply_result.get("result") or "").strip()
|
|
|
|
|
|
def passthrough_response(step, skip_key: str) -> Response:
|
|
"""Return a success response when a short-circuit flag is set (INV-7).
|
|
|
|
Short-circuited rounds never write interests.yaml or checkpoint catalogs;
|
|
the job still reports success so the skipped round is not counted as a failure.
|
|
"""
|
|
assert step.context is not None
|
|
response = step.context.response
|
|
response.success = True
|
|
flag = step.context.get(skip_key)
|
|
if isinstance(flag, dict):
|
|
reason = str(flag.get("reason") or "skipped")
|
|
else:
|
|
reason = str(flag or "skipped")
|
|
response.answer = f"Skipped: {reason}"
|
|
step.logger.info(f"[{step.name}] short-circuit via {skip_key!r} reason={reason}")
|
|
return response
|