change
This commit is contained in:
@@ -33,7 +33,17 @@ from app.cad_agent.domain.feature_plan import FeaturePlan, plan_hash
|
||||
from app.cad_agent.domain.errors import ErrorCode, WorkflowError
|
||||
from app.cad_agent.domain.operation_contract import fragment_schema
|
||||
from app.cad_agent.domain.state import TaskPhase, TaskState, retry_resume_event, transition
|
||||
from app.cad_agent.ports import AdapterUnavailable, ArtifactStore, CadRuntime, ModelGateway, ReviewGateway, TaskRepository
|
||||
from app.cad_agent.ports import (
|
||||
AdapterUnavailable,
|
||||
ArtifactStore,
|
||||
AuthorGuidance,
|
||||
AuthorGuidanceSelection,
|
||||
CadRuntime,
|
||||
ModelGateway,
|
||||
NullAuthorGuidance,
|
||||
ReviewGateway,
|
||||
TaskRepository,
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
@@ -116,6 +126,7 @@ class WorkflowCoordinator:
|
||||
review_gateway: ReviewGateway,
|
||||
requirements: RequirementsCommandHandler,
|
||||
actions: ActionCommandHandler,
|
||||
author_guidance: AuthorGuidance | None = None,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.repository = repository
|
||||
@@ -125,6 +136,7 @@ class WorkflowCoordinator:
|
||||
self.review_gateway = review_gateway
|
||||
self.requirements = requirements
|
||||
self.actions = actions
|
||||
self.author_guidance = author_guidance or NullAuthorGuidance()
|
||||
|
||||
def create_task(
|
||||
self,
|
||||
@@ -1078,14 +1090,14 @@ class WorkflowCoordinator:
|
||||
"message": message[:1000],
|
||||
}
|
||||
|
||||
async def _author_turn(self, task_id: str, author: ModelIdentity, tools: list[dict[str, Any]], feedback: list[dict[str, Any]]) -> tuple[str, str, dict[str, int]] | WorkflowError:
|
||||
async def _author_turn(self, task_id: str, author: ModelIdentity, tools: list[dict[str, Any]], feedback: list[dict[str, Any]]) -> tuple[str, str, dict[str, Any]] | WorkflowError:
|
||||
if len(tools) != 1:
|
||||
raise RuntimeError("Workflow state must expose exactly one author tool.")
|
||||
tool = tools[0]
|
||||
name = str((tool.get("function") or {}).get("name") or "")
|
||||
if not name:
|
||||
raise RuntimeError("Workflow exposed an unnamed author tool.")
|
||||
messages = self._author_context(task_id, feedback)
|
||||
messages, guidance = self._author_context(task_id, feedback)
|
||||
try:
|
||||
response = await self.model_gateway.call_tool(
|
||||
messages=messages, tool=tool, provider_id=author.provider_id,
|
||||
@@ -1103,10 +1115,26 @@ class WorkflowCoordinator:
|
||||
"model_id": author.model_id,
|
||||
"retry_reason": "provider_unavailable",
|
||||
"cache_hit": False,
|
||||
**guidance.usage_metadata(),
|
||||
})
|
||||
return WorkflowError(ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE, str(error)[:1000], retryable=True)
|
||||
call = validate_one_tool_call(response["tool_calls"], name)
|
||||
if isinstance(call, WorkflowError):
|
||||
# A provider response is still a billable author attempt even when
|
||||
# it violates the one-tool-call protocol. Keep guidance audit
|
||||
# metadata on that record so A/B reports do not silently omit the
|
||||
# failures this corpus is intended to reduce.
|
||||
self.repository.record_usage(task_id, {
|
||||
**response["usage"],
|
||||
"context_chars": len(json.dumps(messages, ensure_ascii=False)),
|
||||
"tool": name,
|
||||
"provider_id": author.provider_id,
|
||||
"model_id": author.model_id,
|
||||
"raw_arguments_hash": "",
|
||||
"retry_reason": "invalid_tool_call",
|
||||
"cache_hit": False,
|
||||
**guidance.usage_metadata(),
|
||||
})
|
||||
self._record_rejected_tool_calls(
|
||||
task_id,
|
||||
actor="author",
|
||||
@@ -1132,6 +1160,7 @@ class WorkflowCoordinator:
|
||||
"raw_arguments_hash": raw_arguments_hash(raw),
|
||||
"retry_reason": "",
|
||||
"cache_hit": False,
|
||||
**guidance.usage_metadata(),
|
||||
}
|
||||
self.repository.record_usage(task_id, usage)
|
||||
return name, raw, usage
|
||||
@@ -1400,10 +1429,10 @@ class WorkflowCoordinator:
|
||||
return []
|
||||
return [self._tool("record_geometry_conclusion", StatelessGeometryConclusion)]
|
||||
|
||||
def _author_context(self, task_id: str, feedback: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
def _author_context(self, task_id: str, feedback: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], AuthorGuidanceSelection]:
|
||||
state = self.repository.get_state(task_id)
|
||||
if state is None:
|
||||
return []
|
||||
return [], AuthorGuidanceSelection(fallback_reason="task_state_unavailable")
|
||||
if state.phase == TaskPhase.DRAFTING_REQUIREMENTS_DOCUMENT:
|
||||
content = {
|
||||
"protocol": "cad.v3.2.feature-dag",
|
||||
@@ -1557,8 +1586,17 @@ class WorkflowCoordinator:
|
||||
"feature_plan_hash": state.feature_plan_hash,
|
||||
"feature_node_statuses": self._feature_node_statuses(task_id, None),
|
||||
})
|
||||
messages: list[dict[str, Any]] = [{"role": "system", "content": "You are the autonomous CAD author. Use exactly one offered structured tool call. Never emit Markdown plans or free-form JSON."}, {"role": "user", "content": json.dumps(content, ensure_ascii=False)}]
|
||||
return [*messages, *feedback[-2:]]
|
||||
guidance = self.author_guidance.select(
|
||||
phase=state.phase,
|
||||
atomic_id=state.pending_feature.atomic_id if state.pending_feature is not None else "",
|
||||
repair_required=state.repair_required,
|
||||
supported_atomic_ids=self.runtime.supported_atomic_ids(),
|
||||
)
|
||||
system = "You are the autonomous CAD author. Use exactly one offered structured tool call. Never emit Markdown plans or free-form JSON."
|
||||
if guidance.content:
|
||||
system += "\n\nThe following is non-authoritative CDSL author guidance. The current tool schema, operation contract, and server facts take precedence.\n\n" + guidance.content
|
||||
messages: list[dict[str, Any]] = [{"role": "system", "content": system}, {"role": "user", "content": json.dumps(content, ensure_ascii=False)}]
|
||||
return [*messages, *feedback[-2:]], guidance
|
||||
|
||||
def _user_clarifications(self, task_id: str) -> list[dict[str, str]]:
|
||||
clarifications: list[dict[str, str]] = []
|
||||
|
||||
Reference in New Issue
Block a user