fix: preserve exploration tool history and cap context budget
This commit is contained in:
1 parent
ee256ad7ce
commit
621f30e437
17 files changed
+211
-23
No files matched your search
@@ -17,7 +17,7 @@ from collections.abc import Awaitable
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from langchain_core.messages import SystemMessage, ToolMessage
|
||||
from langchain_core.messages import BaseMessage, SystemMessage, ToolMessage
|
||||
from langchain_core.tools import BaseTool
|
||||
|
||||
from artemis.agents.validator.tool_declarations import normalize_coordinate_target
|
||||
@@ -146,9 +146,9 @@ class OperatorNode:
|
||||
from artemis.config.agent import MemoryTranscriptConfig
|
||||
|
||||
self._transcript_cfg = MemoryTranscriptConfig()
|
||||
# Index into the rendered message list where this turn's tail begins;
|
||||
# set by the transcript build and consumed after the tool loop.
|
||||
self._transcript_turn_base: int | None = None
|
||||
# Keep the actual observation, not an index: plugin/exploration context
|
||||
# may be prepended after render, shifting every message's position.
|
||||
self._transcript_turn_tail: BaseMessage | None = None
|
||||
self._turn_plan_baseline: str | None = None
|
||||
self._turn_ledger_streak: int = 0
|
||||
# Whether the previous turn's replies carried no visible reasoning text;
|
||||
@@ -225,7 +225,7 @@ class OperatorNode:
|
||||
active_background_tasks: list[dict] = None,
|
||||
newly_finished_tasks: list[dict] = None,
|
||||
) -> list:
|
||||
self._transcript_turn_base = None
|
||||
self._transcript_turn_tail = None
|
||||
if getattr(self._transcript_cfg, "enabled", False):
|
||||
try:
|
||||
return await self._build_prompt_transcript(
|
||||
@@ -243,7 +243,7 @@ class OperatorNode:
|
||||
f"Transcript prompt path failed; falling back to the legacy"
|
||||
f" 2-message build for this turn: {e}"
|
||||
)
|
||||
self._transcript_turn_base = None
|
||||
self._transcript_turn_tail = None
|
||||
|
||||
plan_and_history = self._build_plan_and_history(steps, task_plan)
|
||||
|
||||
@@ -468,18 +468,19 @@ class OperatorNode:
|
||||
mark_ephemeral(tail, builder.ephemeral_indices)
|
||||
|
||||
messages = ledger.render([tail])
|
||||
self._transcript_turn_base = len(messages) - 1
|
||||
self._transcript_turn_tail = tail
|
||||
return messages
|
||||
|
||||
def _stage_transcript_turn(self, messages: list) -> None:
|
||||
"""Hand this turn's tail + tool-loop products to the ledger."""
|
||||
base = self._transcript_turn_base
|
||||
self._transcript_turn_base = None
|
||||
if base is None:
|
||||
tail = self._transcript_turn_tail
|
||||
self._transcript_turn_tail = None
|
||||
if tail is None:
|
||||
return
|
||||
try:
|
||||
ledger = getattr(self.ctx, "transcript_ledger", None)
|
||||
if ledger is not None:
|
||||
base = next(i for i, message in enumerate(messages) if message is tail)
|
||||
ledger.stage_turn(messages[base:])
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to stage transcript turn: {e}")
|
||||
|
||||
@@ -23,6 +23,7 @@ from pydantic import BaseModel, Field, model_validator
|
||||
from artemis.config.constants import (
|
||||
AGENT_CONFIG_FILENAME,
|
||||
DEFAULT_EXPLORER_VERSION,
|
||||
MAX_CONTEXT_WINDOW_TOKENS,
|
||||
ExplorerVersion,
|
||||
)
|
||||
from artemis.config.paths import ROOT_DIR, get_config_path
|
||||
@@ -483,6 +484,7 @@ class MemoryTranscriptConfig(BaseModel):
|
||||
context_budget_tokens: int = Field(
|
||||
default=80000,
|
||||
ge=1024,
|
||||
le=MAX_CONTEXT_WINDOW_TOKENS,
|
||||
description=(
|
||||
"Measured-token budget for the transcript context (provisional"
|
||||
" default pending the on-device baseline; §3.4). The soft/hard"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
from typing import Literal
|
||||
|
||||
# Application context ceiling; independent from output and session token totals.
|
||||
MAX_CONTEXT_WINDOW_TOKENS = 300_000
|
||||
|
||||
# ==============================================================================
|
||||
# Environment Variable Keys
|
||||
# ==============================================================================
|
||||
|
||||
@@ -81,6 +81,11 @@ _AUTH_MARKERS = ("unauthorized", "forbidden", "api key", "credential")
|
||||
# "deadline exceeded"/"deadline_exceeded" cover google-genai SDK timeouts that
|
||||
# surface without an HTTP status code attached.
|
||||
_TIMEOUT_MARKERS = ("timed out", "timeout", "deadline exceeded", "deadline_exceeded")
|
||||
_TOOL_HISTORY_ERRORS = (
|
||||
"no tool call found for function call output",
|
||||
"no tool output found for function call",
|
||||
"tool_call_id not found",
|
||||
)
|
||||
|
||||
|
||||
def classify_failure(error: BaseException) -> Failure:
|
||||
@@ -91,6 +96,12 @@ def classify_failure(error: BaseException) -> Failure:
|
||||
code = extract_status_code(error)
|
||||
message = str(error).lower()
|
||||
|
||||
# Compatible Responses endpoints can report input validation inside an
|
||||
# HTTP 200 stream as APIError without a status code. Retrying the same
|
||||
# broken call/output history cannot repair it, even on a fallback model.
|
||||
if any(marker in message for marker in _TOOL_HISTORY_ERRORS):
|
||||
return Failure(FailureCategory.BAD_REQUEST, False, False)
|
||||
|
||||
if code == 429 or any(marker in message for marker in _RATE_LIMIT_MARKERS):
|
||||
return Failure(FailureCategory.RATE_LIMIT, True, True)
|
||||
if code in {500, 502, 503, 504} or any(marker in message for marker in _UNAVAILABLE_MARKERS):
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
},
|
||||
"transcript": {
|
||||
"enabled": true,
|
||||
// 历史压缩预算,允许范围 1024–300000;上下文参照上限固定为 300k。
|
||||
"context_budget_tokens": 80000,
|
||||
"start_ratio": 0.35,
|
||||
"soft_ratio": 0.7,
|
||||
|
||||
@@ -40,6 +40,7 @@ import threading
|
||||
from typing import Any
|
||||
|
||||
from artemis.data_engine.context_vars import CURRENT_NODE_NAME
|
||||
from artemis.config.constants import MAX_CONTEXT_WINDOW_TOKENS
|
||||
from artemis.llm.usage import usage_from_message
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
@@ -52,7 +53,7 @@ _METERS: dict[str, "SessionTokenMeter"] = {}
|
||||
# measured against when the UI reports "context used". The prompt size of the
|
||||
# last executor call (``context_base_tokens`` on its ``llm_usage`` trace) is
|
||||
# the numerator; this is the denominator.
|
||||
OPERATOR_CONTEXT_WINDOW_TOKENS = 1_000_000
|
||||
OPERATOR_CONTEXT_WINDOW_TOKENS = MAX_CONTEXT_WINDOW_TOKENS
|
||||
|
||||
# Traced node names whose LLM calls carry the live executor context. Both the
|
||||
# Pro Operator and the Flash runner are "the operator" from the user's view.
|
||||
|
||||
Reference in new issue
Block a user