4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
249 lines
8.3 KiB
Python
249 lines
8.3 KiB
Python
"""Terminal assistant prompt assembly for the interactive shell."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any, Protocol
|
|
|
|
from config.constants.prompts import SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST
|
|
from core.agent_harness.prompts.assistant_agent_prompt import (
|
|
_build_observation_block,
|
|
_build_system_prompt,
|
|
build_handoff_guidance_block,
|
|
)
|
|
from core.agent_harness.prompts.conversation_memory import (
|
|
format_prior_action_facts,
|
|
format_recent_conversation,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from core.agent_harness.turns.turn_snapshot import TurnSnapshot
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
_MAX_SYNTHETIC_OBSERVATION_PROMPT_CHARS = 120_000
|
|
|
|
|
|
class AssistantPromptContextProvider(Protocol):
|
|
"""Grounding provider used by the surface-agnostic assistant turn."""
|
|
|
|
def cli_reference(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def agents_md(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def investigation_flow(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def environment_block(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def suggested_synthetic_prompt(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def log_diagnostics(self, reason: str) -> None:
|
|
raise NotImplementedError
|
|
|
|
|
|
def build_assistant_system_prompt(
|
|
reference: str,
|
|
history: str,
|
|
agents_md: str = "",
|
|
investigation_flow: str = "",
|
|
prior_investigation: str = "",
|
|
prior_action_facts: str = "",
|
|
environment: str = "",
|
|
) -> str:
|
|
"""Build the system prompt for one assistant turn."""
|
|
return _build_system_prompt(
|
|
reference,
|
|
history,
|
|
agents_md=agents_md,
|
|
investigation_flow=investigation_flow,
|
|
prior_investigation=prior_investigation,
|
|
prior_action_facts=prior_action_facts,
|
|
environment=environment,
|
|
)
|
|
|
|
|
|
def build_observation_block(tool_observation: str | None, *, on_screen: bool = True) -> str:
|
|
"""Wrap freshly gathered tool output for the assistant."""
|
|
return _build_observation_block(tool_observation, on_screen=on_screen)
|
|
|
|
|
|
def _summarize_evidence(evidence: Any) -> list[str]:
|
|
if isinstance(evidence, dict):
|
|
sample_keys = list(evidence)[:3]
|
|
sample = {key: evidence[key] for key in sample_keys}
|
|
return [
|
|
f"Evidence items: {len(evidence)}",
|
|
"Evidence keys: " + ", ".join(map(str, sample_keys)),
|
|
"Sample evidence:\n" + json.dumps(sample, indent=2, default=str)[:1500],
|
|
]
|
|
if isinstance(evidence, list):
|
|
return [
|
|
f"Evidence items: {len(evidence)}",
|
|
"Sample evidence:\n" + json.dumps(evidence[:3], indent=2, default=str)[:1500],
|
|
]
|
|
return [
|
|
f"Evidence type: {type(evidence).__name__}",
|
|
f"Evidence summary:\n{str(evidence)[:1500]}",
|
|
]
|
|
|
|
|
|
def _summarize_last_state(state: dict[str, Any]) -> str:
|
|
"""Produce a compact text summary of the previous investigation."""
|
|
parts: list[str] = []
|
|
alert_name = state.get("alert_name")
|
|
if alert_name:
|
|
parts.append(f"Alert: {alert_name}")
|
|
root_cause = state.get("root_cause")
|
|
if root_cause:
|
|
parts.append(f"Root cause: {root_cause}")
|
|
problem_md = state.get("problem_md") or ""
|
|
if problem_md:
|
|
parts.append(f"Problem summary:\n{problem_md[:2000]}")
|
|
slack_message = state.get("slack_message") or ""
|
|
if slack_message:
|
|
parts.append(f"Report:\n{slack_message[:2000]}")
|
|
evidence = state.get("evidence")
|
|
if evidence:
|
|
try:
|
|
parts.extend(_summarize_evidence(evidence))
|
|
except (TypeError, ValueError) as exc:
|
|
_logger.warning("could not serialize evidence for grounding: %s", exc)
|
|
parts.append("(evidence present but could not be serialized for grounding)")
|
|
return "\n\n".join(parts) or "(no prior investigation details available)"
|
|
|
|
|
|
def _user_message_requests_synthetic_failure_explanation(
|
|
message: str,
|
|
suggested_prompt: str = SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST,
|
|
) -> bool:
|
|
"""True when the user is likely asking about a failed synthetic benchmark."""
|
|
m = message.strip().lower()
|
|
if not m:
|
|
return False
|
|
suggested = suggested_prompt.lower().rstrip("?")
|
|
if m.rstrip("?") == suggested:
|
|
return True
|
|
if "why" in m and "fail" in m:
|
|
return True
|
|
return "what went wrong" in m
|
|
|
|
|
|
def _load_synthetic_observation_text(
|
|
path_str: str, *, max_chars: int = _MAX_SYNTHETIC_OBSERVATION_PROMPT_CHARS
|
|
) -> str:
|
|
try:
|
|
raw = Path(path_str).read_text(encoding="utf-8")
|
|
except OSError:
|
|
return ""
|
|
if len(raw) > max_chars:
|
|
return (
|
|
raw[:max_chars]
|
|
+ f"\n… [truncated for prompt size; observation is {len(raw)} characters total]"
|
|
)
|
|
return raw
|
|
|
|
|
|
def _assistant_context_blocks(
|
|
*,
|
|
turn_snapshot: TurnSnapshot,
|
|
handoff_contents: tuple[str, ...],
|
|
tool_observation: str | None,
|
|
tool_observation_on_screen: bool,
|
|
suggested_prompt: str = SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST,
|
|
) -> str:
|
|
return (
|
|
f"{_build_integration_guard(turn_snapshot)}"
|
|
f"{build_handoff_guidance_block(handoff_contents)}"
|
|
f"{build_observation_block(tool_observation, on_screen=tool_observation_on_screen)}"
|
|
f"{_build_synthetic_failure_block(turn_snapshot, suggested_prompt=suggested_prompt)}"
|
|
)
|
|
|
|
|
|
def _build_integration_guard(ctx: TurnSnapshot) -> str:
|
|
"""Render the no-integrations guidance block from the turn snapshot."""
|
|
if not (ctx.configured_integrations_known and not ctx.configured_integrations):
|
|
return ""
|
|
|
|
return (
|
|
"No integrations are configured in this session. You may still help the user "
|
|
"configure one: explain `/integrations setup <service>` for integrations or "
|
|
"`/mcp connect <server>` for MCP servers. Do not claim any integration is "
|
|
"already connected, and for show/verify/remove requests against unconfigured "
|
|
"integrations, answer with guidance only.\n\n"
|
|
)
|
|
|
|
|
|
def _build_synthetic_failure_block(
|
|
ctx: TurnSnapshot,
|
|
*,
|
|
suggested_prompt: str = SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST,
|
|
) -> str:
|
|
obs_path = ctx.last_synthetic_observation_path
|
|
if not obs_path:
|
|
return ""
|
|
|
|
if not _user_message_requests_synthetic_failure_explanation(
|
|
ctx.text,
|
|
suggested_prompt=suggested_prompt,
|
|
):
|
|
return ""
|
|
|
|
obs_text = _load_synthetic_observation_text(obs_path)
|
|
if not obs_text:
|
|
return ""
|
|
|
|
return (
|
|
"The user is asking about a failed `opensre tests synthetic` run "
|
|
"in this checkout. The JSON below is the saved observation "
|
|
f"(scores, gates, stderr summary). Path: {obs_path}\n"
|
|
"Use it to explain validation failures. Do not say nothing ran or "
|
|
"that you lack context — the run completed and this file was written.\n\n"
|
|
f"--- observation_json ---\n{obs_text}\n\n"
|
|
)
|
|
|
|
|
|
def build_cli_agent_prompt_from_provider(
|
|
*,
|
|
message: str,
|
|
prompts: AssistantPromptContextProvider,
|
|
tool_observation: str | None,
|
|
tool_observation_on_screen: bool,
|
|
handoff_contents: tuple[str, ...] = (),
|
|
turn_snapshot: TurnSnapshot,
|
|
) -> str:
|
|
"""Render an assistant prompt from the core prompt-provider port."""
|
|
prompts.log_diagnostics("cli_agent_grounding")
|
|
system = build_assistant_system_prompt(
|
|
prompts.cli_reference(),
|
|
format_recent_conversation(list(turn_snapshot.conversation_messages)),
|
|
agents_md=prompts.agents_md(),
|
|
investigation_flow=prompts.investigation_flow(),
|
|
prior_investigation=(
|
|
_summarize_last_state(turn_snapshot.last_state)
|
|
if turn_snapshot.last_state is not None
|
|
else ""
|
|
),
|
|
prior_action_facts=format_prior_action_facts(list(turn_snapshot.conversation_messages)),
|
|
environment=prompts.environment_block(),
|
|
)
|
|
return (
|
|
f"{system}\n"
|
|
f"{_assistant_context_blocks(turn_snapshot=turn_snapshot, handoff_contents=handoff_contents, tool_observation=tool_observation, tool_observation_on_screen=tool_observation_on_screen, suggested_prompt=prompts.suggested_synthetic_prompt())}"
|
|
f"--- User message ---\n{message}"
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"AssistantPromptContextProvider",
|
|
"build_assistant_system_prompt",
|
|
"build_cli_agent_prompt_from_provider",
|
|
"build_observation_block",
|
|
]
|