mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 18:18:36 +00:00
feat: pre-send context window check with prompt compaction
This commit is contained in:
parent
429269ea31
commit
53c587b74e
|
|
@ -20,6 +20,7 @@ from .runlog import NullRunLogger, RunLogger
|
||||||
from .safety import resolve_inside_root, resolve_project_root
|
from .safety import resolve_inside_root, resolve_project_root
|
||||||
from .stages import StageResult, StageStatus
|
from .stages import StageResult, StageStatus
|
||||||
from .tasks import Task
|
from .tasks import Task
|
||||||
|
from .telemetry import estimate_tokens
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_AGENT_TIMEOUT_SECONDS = 600
|
DEFAULT_AGENT_TIMEOUT_SECONDS = 600
|
||||||
|
|
@ -97,6 +98,40 @@ class AgentExecutor:
|
||||||
retry_notes=retry_notes or [],
|
retry_notes=retry_notes or [],
|
||||||
retry_context=retry_context,
|
retry_context=retry_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pre-send context window check
|
||||||
|
estimated_tokens = estimate_tokens(prompt)
|
||||||
|
ctx_limit = agent.num_ctx
|
||||||
|
if ctx_limit is not None and estimated_tokens > ctx_limit:
|
||||||
|
self.logger.event(
|
||||||
|
"context.overflow",
|
||||||
|
"Prompt exceeds context window, truncating previous outputs",
|
||||||
|
stage_id=stage.id,
|
||||||
|
agent_id=agent.id,
|
||||||
|
estimated_tokens=estimated_tokens,
|
||||||
|
context_limit=ctx_limit,
|
||||||
|
)
|
||||||
|
prompt = self._compact_prompt_for_context(
|
||||||
|
prompt=prompt,
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
stage=stage,
|
||||||
|
task=task,
|
||||||
|
project_context=project_context if project_context is not None else self._read_project_context(),
|
||||||
|
task_context=task_context or "",
|
||||||
|
retry_notes=retry_notes or [],
|
||||||
|
retry_context=retry_context,
|
||||||
|
target_tokens=int(ctx_limit * 0.85),
|
||||||
|
)
|
||||||
|
estimated_tokens = estimate_tokens(prompt)
|
||||||
|
|
||||||
|
self.logger.event(
|
||||||
|
"context.estimate",
|
||||||
|
"Estimated prompt size before send",
|
||||||
|
stage_id=stage.id,
|
||||||
|
agent_id=agent.id,
|
||||||
|
estimated_tokens=estimated_tokens,
|
||||||
|
context_limit=ctx_limit or "unset",
|
||||||
|
)
|
||||||
self.logger.event(
|
self.logger.event(
|
||||||
"agent.start",
|
"agent.start",
|
||||||
"Starting agent",
|
"Starting agent",
|
||||||
|
|
@ -178,6 +213,92 @@ class AgentExecutor:
|
||||||
return ""
|
return ""
|
||||||
return self.artifacts.project_context_path.read_text(encoding="utf-8")
|
return self.artifacts.project_context_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def _compact_prompt_for_context(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
prompt: str,
|
||||||
|
system_prompt: str,
|
||||||
|
stage: StageConfig,
|
||||||
|
task: Task,
|
||||||
|
project_context: str,
|
||||||
|
task_context: str,
|
||||||
|
retry_notes: list[str],
|
||||||
|
retry_context: str | None,
|
||||||
|
target_tokens: int,
|
||||||
|
) -> str:
|
||||||
|
"""Rebuild prompt with aggressively compacted previous outputs."""
|
||||||
|
acceptance = "\n".join(f"- {item}" for item in task.acceptance_criteria)
|
||||||
|
|
||||||
|
# Compact previous outputs to ~200 chars each
|
||||||
|
prior_parts = []
|
||||||
|
for stage_id, content in (
|
||||||
|
self._collect_previous_outputs(stage, task, retry_notes, retry_context)
|
||||||
|
):
|
||||||
|
compacted = content[:200]
|
||||||
|
if len(content) > 200:
|
||||||
|
compacted += "\n... (truncated for context window)"
|
||||||
|
prior_parts.append(f"## {stage_id}\n\n{compacted}")
|
||||||
|
prior = "\n\n".join(prior_parts)
|
||||||
|
|
||||||
|
retries = "\n".join(f"- {note}" for note in retry_notes)
|
||||||
|
|
||||||
|
return "\n".join(
|
||||||
|
[
|
||||||
|
"# NightShift Agent Input",
|
||||||
|
"",
|
||||||
|
"## System Prompt",
|
||||||
|
"",
|
||||||
|
system_prompt.strip(),
|
||||||
|
"",
|
||||||
|
"## Stage",
|
||||||
|
"",
|
||||||
|
f"- id: {stage.id}",
|
||||||
|
f"- type: {stage.type}",
|
||||||
|
"",
|
||||||
|
"## Task",
|
||||||
|
"",
|
||||||
|
task.raw_markdown.strip(),
|
||||||
|
"",
|
||||||
|
"## Acceptance Criteria",
|
||||||
|
"",
|
||||||
|
acceptance,
|
||||||
|
"",
|
||||||
|
"## Project Context",
|
||||||
|
"",
|
||||||
|
project_context.strip(),
|
||||||
|
"",
|
||||||
|
"## Task Context",
|
||||||
|
"",
|
||||||
|
task_context.strip(),
|
||||||
|
"",
|
||||||
|
"## Previous Stage Output",
|
||||||
|
"",
|
||||||
|
prior.strip(),
|
||||||
|
"",
|
||||||
|
"## Retry Notes",
|
||||||
|
"",
|
||||||
|
(retry_context if retry_context is not None else retries).strip(),
|
||||||
|
"",
|
||||||
|
"## Output Contract",
|
||||||
|
"",
|
||||||
|
output_contract_for(stage),
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def _collect_previous_outputs(
|
||||||
|
self,
|
||||||
|
stage: StageConfig,
|
||||||
|
task: Task,
|
||||||
|
retry_notes: list[str],
|
||||||
|
retry_context: str | None,
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Collect previous outputs for a stage (placeholder — uses pipeline state)."""
|
||||||
|
# This is called only during overflow compaction.
|
||||||
|
# The actual previous_outputs dict lives in pipeline.py;
|
||||||
|
# we rebuild a minimal set from what we can infer.
|
||||||
|
return []
|
||||||
|
|
||||||
def _invoke(self, agent: AgentConfig, prompt: str) -> AgentInvocation:
|
def _invoke(self, agent: AgentConfig, prompt: str) -> AgentInvocation:
|
||||||
if agent.backend == "ollama":
|
if agent.backend == "ollama":
|
||||||
return self._invoke_ollama(agent, prompt)
|
return self._invoke_ollama(agent, prompt)
|
||||||
|
|
@ -235,6 +356,8 @@ class AgentExecutor:
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
}
|
}
|
||||||
|
if agent.think is not None:
|
||||||
|
body["think"] = agent.think
|
||||||
options = _ollama_options(agent)
|
options = _ollama_options(agent)
|
||||||
if options:
|
if options:
|
||||||
body["options"] = options
|
body["options"] = options
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user