修改agent流程,分步构建cdsl
This commit is contained in:
+68
-33
@@ -17,9 +17,6 @@ load_dotenv(BACKEND_ROOT / ".env")
|
||||
class ProviderModel:
|
||||
id: str
|
||||
vision: bool = False
|
||||
# Strict function schemas are provider/model capabilities, not an
|
||||
# assumption about every OpenAI-compatible endpoint.
|
||||
strict_tool_schema: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -29,6 +26,9 @@ class ProviderConfig:
|
||||
base_url: str
|
||||
api_key: str
|
||||
models: tuple[ProviderModel, ...]
|
||||
# Chat Completions uses the legacy flat ``reasoning_effort`` parameter.
|
||||
# Keep it provider-scoped because compatibility varies by endpoint/model.
|
||||
reasoning_effort: str = ""
|
||||
|
||||
@property
|
||||
def configured(self) -> bool:
|
||||
@@ -37,6 +37,12 @@ class ProviderConfig:
|
||||
def model(self, model_id: str) -> ProviderModel | None:
|
||||
return next((model for model in self.models if model.id == model_id), None)
|
||||
|
||||
@property
|
||||
def chat_completion_options(self) -> dict[str, str]:
|
||||
if self.reasoning_effort:
|
||||
return {"reasoning_effort": self.reasoning_effort}
|
||||
return {}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Settings:
|
||||
@@ -50,17 +56,21 @@ class Settings:
|
||||
llm_timeout_s: float
|
||||
default_provider_id: str
|
||||
providers: tuple[ProviderConfig, ...]
|
||||
max_repair_attempts: int = 4
|
||||
review_provider_id: str = ""
|
||||
review_model_id: str = ""
|
||||
node_authoring_attempts: int = 2
|
||||
node_repair_attempts: int = 2
|
||||
node_replan_attempts: int = 1
|
||||
incremental_generation: bool = False
|
||||
agent_tool_calls_per_cycle: int = 12
|
||||
agent_candidate_attempts_per_head: int = 3
|
||||
agent_consecutive_no_progress_limit: int = 6
|
||||
agent_format_error_repeat_limit: int = 3
|
||||
agent_max_features_per_fragment: int = 6
|
||||
agent_context_char_limit: int = 14000
|
||||
agent_render_cache: bool = True
|
||||
autonomous_generation: bool = True
|
||||
resume_running_tasks_on_startup: bool = True
|
||||
|
||||
@property
|
||||
def llm_configured(self) -> bool:
|
||||
return self.provider_for(self.default_provider_id) is not None
|
||||
return any(provider.configured for provider in self.providers)
|
||||
|
||||
def provider_for(self, provider_id: str | None) -> ProviderConfig | None:
|
||||
requested = str(provider_id or self.default_provider_id).strip().lower()
|
||||
@@ -70,7 +80,14 @@ class Settings:
|
||||
provider = self.provider_for(provider_id)
|
||||
if provider is None:
|
||||
raise ValueError("The selected model provider is not configured")
|
||||
selected = str(model_id or "").strip() or provider.models[0].id
|
||||
# A caller that supplies neither value is asking for the configured
|
||||
# application default, not the first model listed by that provider.
|
||||
# The latter made CDSL_DEFAULT_MODEL ineffective and silently routed
|
||||
# new runs to an unintended author model.
|
||||
selected = str(model_id or "").strip()
|
||||
if not selected and not str(provider_id or "").strip():
|
||||
selected = self.llm_model
|
||||
selected = selected or provider.models[0].id
|
||||
model = provider.model(selected)
|
||||
if model is None:
|
||||
raise ValueError("The selected model is not enabled for this provider")
|
||||
@@ -92,28 +109,41 @@ class Settings:
|
||||
raise ValueError("CDSL_REVIEW_MODEL must identify a configured vision-capable model")
|
||||
return provider, model
|
||||
|
||||
|
||||
def _enabled_model_ids(value: str) -> set[str]:
|
||||
return {item.strip() for item in value.split(",") if item.strip()}
|
||||
def resolve_independent_review_model(self, author_provider: ProviderConfig, author_model: ProviderModel) -> tuple[ProviderConfig, ProviderModel]:
|
||||
"""Require the candidate judge to be a separately configured model."""
|
||||
provider, model = self.resolve_review_model()
|
||||
if provider.id == author_provider.id and model.id == author_model.id:
|
||||
raise ValueError("CDSL_REVIEW_PROVIDER/CDSL_REVIEW_MODEL must differ from the autonomous author model")
|
||||
return provider, model
|
||||
|
||||
|
||||
def _as_bool(value: str) -> bool:
|
||||
def _reasoning_effort(value: str) -> str:
|
||||
effort = value.strip().lower()
|
||||
allowed = {"none", "minimal", "low", "medium", "high", "xhigh", "max"}
|
||||
if effort and effort not in allowed:
|
||||
raise ValueError(
|
||||
"CDSL_*_REASONING_EFFORT must be one of "
|
||||
f"{', '.join(sorted(allowed))}"
|
||||
)
|
||||
return effort
|
||||
|
||||
|
||||
def _env_flag(name: str, default: bool) -> bool:
|
||||
value = os.getenv(name)
|
||||
if value is None or not value.strip():
|
||||
return default
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _models(
|
||||
value: str,
|
||||
vision_value: str = "",
|
||||
strict_value: str = "",
|
||||
strict_all: bool = False,
|
||||
) -> tuple[ProviderModel, ...]:
|
||||
vision_ids = {item.strip() for item in vision_value.split(",") if item.strip()}
|
||||
strict_ids = _enabled_model_ids(strict_value)
|
||||
return tuple(
|
||||
ProviderModel(
|
||||
id=item,
|
||||
vision=item in vision_ids,
|
||||
strict_tool_schema=strict_all or item in strict_ids,
|
||||
)
|
||||
for item in (part.strip() for part in value.split(","))
|
||||
if item
|
||||
@@ -128,16 +158,13 @@ def _provider(prefix: str, provider_id: str, label: str, default_base_url: str,
|
||||
api_key = os.getenv(f"CDSL_{prefix}_API_KEY", os.getenv("CDSL_LLM_API_KEY", "") if legacy else "")
|
||||
model_list = os.getenv(f"CDSL_{prefix}_MODELS", os.getenv("CDSL_LLM_MODEL", default_model) if legacy else default_model)
|
||||
vision_models = os.getenv(f"CDSL_{prefix}_VISION_MODELS", "")
|
||||
# A provider-wide switch is convenient for a verified endpoint. The model
|
||||
# list lets mixed capability deployments opt in only selected models.
|
||||
strict_all = _as_bool(os.getenv(f"CDSL_{prefix}_STRICT_TOOL_SCHEMA", ""))
|
||||
strict_models = os.getenv(f"CDSL_{prefix}_STRICT_TOOL_MODELS", "")
|
||||
return ProviderConfig(
|
||||
provider_id,
|
||||
label,
|
||||
base_url,
|
||||
api_key,
|
||||
_models(model_list, vision_models, strict_models, strict_all),
|
||||
id=provider_id,
|
||||
label=label,
|
||||
base_url=base_url,
|
||||
api_key=api_key,
|
||||
models=_models(model_list, vision_models),
|
||||
reasoning_effort=_reasoning_effort(os.getenv(f"CDSL_{prefix}_REASONING_EFFORT", "")),
|
||||
)
|
||||
|
||||
|
||||
@@ -151,6 +178,7 @@ def get_settings() -> Settings:
|
||||
default_provider_id = os.getenv("CDSL_DEFAULT_PROVIDER", "deepseek").strip().lower() or "deepseek"
|
||||
default_provider = next((item for item in providers if item.id == default_provider_id), providers[0])
|
||||
default_model = os.getenv("CDSL_DEFAULT_MODEL", "").strip() or (default_provider.models[0].id if default_provider.models else "")
|
||||
llm_timeout_s = float(os.getenv("CDSL_LLM_TIMEOUT_S", "90"))
|
||||
return Settings(
|
||||
task_root=data_root / "tasks",
|
||||
conversation_root=data_root / "conversations",
|
||||
@@ -159,14 +187,21 @@ def get_settings() -> Settings:
|
||||
llm_base_url=default_provider.base_url,
|
||||
llm_api_key=default_provider.api_key,
|
||||
llm_model=default_model,
|
||||
llm_timeout_s=float(os.getenv("CDSL_LLM_TIMEOUT_S", "90")),
|
||||
max_repair_attempts=max(0, int(os.getenv("CDSL_MAX_REPAIR_ATTEMPTS", "4"))),
|
||||
llm_timeout_s=llm_timeout_s,
|
||||
default_provider_id=default_provider_id,
|
||||
providers=providers,
|
||||
review_provider_id=os.getenv("CDSL_REVIEW_PROVIDER", "").strip().lower(),
|
||||
review_model_id=os.getenv("CDSL_REVIEW_MODEL", "").strip(),
|
||||
node_authoring_attempts=max(1, int(os.getenv("CDSL_NODE_AUTHORING_ATTEMPTS", "2"))),
|
||||
node_repair_attempts=max(0, int(os.getenv("CDSL_NODE_REPAIR_ATTEMPTS", "2"))),
|
||||
node_replan_attempts=max(0, int(os.getenv("CDSL_NODE_REPLAN_ATTEMPTS", "1"))),
|
||||
incremental_generation=_as_bool(os.getenv("CDSL_INCREMENTAL_GENERATION", "1")),
|
||||
agent_tool_calls_per_cycle=max(1, int(os.getenv("CDSL_AGENT_TOOL_CALLS_PER_CYCLE", "12"))),
|
||||
agent_candidate_attempts_per_head=max(1, int(os.getenv("CDSL_AGENT_CANDIDATE_ATTEMPTS_PER_HEAD", "3"))),
|
||||
agent_consecutive_no_progress_limit=max(1, int(os.getenv("CDSL_AGENT_CONSECUTIVE_NO_PROGRESS_LIMIT", "6"))),
|
||||
agent_format_error_repeat_limit=max(1, int(os.getenv("CDSL_AGENT_FORMAT_ERROR_REPEAT_LIMIT", "3"))),
|
||||
agent_max_features_per_fragment=max(1, min(6, int(os.getenv("CDSL_AGENT_MAX_FEATURES_PER_FRAGMENT", "6")))),
|
||||
agent_context_char_limit=max(4000, int(os.getenv("CDSL_AGENT_CONTEXT_CHAR_LIMIT", "14000"))),
|
||||
agent_render_cache=_env_flag("CDSL_AGENT_RENDER_CACHE", True),
|
||||
autonomous_generation=True,
|
||||
# Production instances recover durable runs by default. Test workers
|
||||
# can disable this before startup to guarantee they touch only tasks
|
||||
# explicitly created by that worker.
|
||||
resume_running_tasks_on_startup=_env_flag("CDSL_RESUME_RUNNING_TASKS", True),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user