Modernize provider support
This commit is contained in:
+64
-37
@@ -161,32 +161,6 @@ No text before or after the JSON. No markdown fences.{' ' + field_guidance if fi
|
||||
{prompt}"""
|
||||
|
||||
|
||||
# Legacy function kept for rollback if needed
|
||||
def _append_schema_instructions_legacy(prompt: str, schema: dict) -> str:
|
||||
"""
|
||||
DEPRECATED: Append schema instructions to a prompt.
|
||||
|
||||
This approach causes small models to echo the schema. Use
|
||||
prepend_schema_instructions() instead.
|
||||
"""
|
||||
schema_json = json.dumps(schema, indent=2)
|
||||
|
||||
return f"""{prompt}
|
||||
|
||||
---
|
||||
RESPONSE FORMAT:
|
||||
You must respond with ONLY valid JSON matching this exact schema:
|
||||
{schema_json}
|
||||
|
||||
CRITICAL RULES:
|
||||
- Output ONLY the JSON object, no other text before or after
|
||||
- Put any thinking, reasoning, or analysis in the "reasoning" field
|
||||
- Put your actual answer/response in the "output" field
|
||||
- Do not include markdown code fences
|
||||
- Do not include any preamble like "Here is my response:"
|
||||
"""
|
||||
|
||||
|
||||
def check_system_dependencies(tool: Tool) -> list:
|
||||
"""
|
||||
Check if system dependencies are satisfied.
|
||||
@@ -506,11 +480,30 @@ def _extract_json(text: str) -> dict | list | None:
|
||||
return None
|
||||
|
||||
|
||||
def _read_step_file(filename: str, base_dir: Optional[Path], step_type: str) -> str:
|
||||
"""Read a prompt/code file relative to the tool directory."""
|
||||
if not base_dir:
|
||||
raise ValueError(f"{step_type}_file requires the tool to have a filesystem path")
|
||||
|
||||
relative_path = Path(filename)
|
||||
if relative_path.is_absolute():
|
||||
raise ValueError(f"{step_type}_file must be relative to the tool directory")
|
||||
|
||||
base = base_dir.resolve()
|
||||
path = (base / relative_path).resolve()
|
||||
if path == base or base not in path.parents:
|
||||
raise ValueError(f"{step_type}_file cannot reference files outside the tool directory")
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"{step_type}_file not found: {filename}")
|
||||
return path.read_text()
|
||||
|
||||
|
||||
def execute_prompt_step(
|
||||
step: PromptStep,
|
||||
variables: dict,
|
||||
provider_override: str = None,
|
||||
verbose: bool = False
|
||||
verbose: bool = False,
|
||||
base_dir: Optional[Path] = None
|
||||
) -> tuple[str, bool]:
|
||||
"""
|
||||
Execute a prompt step.
|
||||
@@ -529,7 +522,13 @@ def execute_prompt_step(
|
||||
import re
|
||||
|
||||
# Build prompt with variable substitution
|
||||
prompt = substitute_variables(step.prompt, variables, warn_non_scalar=verbose)
|
||||
try:
|
||||
prompt_template = _read_step_file(step.prompt_file, base_dir, "prompt") if step.prompt_file else step.prompt
|
||||
except (OSError, ValueError) as e:
|
||||
print(f"Error in prompt step: {e}", file=sys.stderr)
|
||||
return "", False
|
||||
|
||||
prompt = substitute_variables(prompt_template, variables, warn_non_scalar=verbose)
|
||||
|
||||
# Inject profile system prompt if specified
|
||||
if step.profile:
|
||||
@@ -633,7 +632,8 @@ def execute_code_step(
|
||||
step: CodeStep,
|
||||
variables: dict,
|
||||
step_num: int = 0,
|
||||
verbose: bool = False
|
||||
verbose: bool = False,
|
||||
base_dir: Optional[Path] = None
|
||||
) -> tuple[dict, bool]:
|
||||
"""
|
||||
Execute a code step.
|
||||
@@ -647,7 +647,14 @@ def execute_code_step(
|
||||
Tuple of (output_vars_dict, success)
|
||||
"""
|
||||
# Substitute variables in code (like {outputfile} -> actual value)
|
||||
code = substitute_variables(step.code, variables, warn_non_scalar=verbose)
|
||||
try:
|
||||
code_template = _read_step_file(step.code_file, base_dir, "code") if step.code_file else step.code
|
||||
except (OSError, ValueError) as e:
|
||||
print(f"Error in code step (step {step_num}):", file=sys.stderr)
|
||||
print(f" {e}", file=sys.stderr)
|
||||
return {}, False
|
||||
|
||||
code = substitute_variables(code_template, variables, warn_non_scalar=verbose)
|
||||
|
||||
# Create execution environment with variables
|
||||
# IMPORTANT: Use the same dict for both globals and locals.
|
||||
@@ -887,6 +894,8 @@ def run_tool(
|
||||
print(f"[verbose] Variables: {list(variables.keys())}", file=sys.stderr)
|
||||
print(f"[verbose] Steps: {len(tool.steps)}", file=sys.stderr)
|
||||
|
||||
tool_base_dir = tool.path.parent if tool.path else None
|
||||
|
||||
# If no steps, just substitute output template
|
||||
if not tool.steps:
|
||||
output = substitute_variables(tool.output, variables, warn_non_scalar=verbose)
|
||||
@@ -916,7 +925,13 @@ def run_tool(
|
||||
if dry_run:
|
||||
variables[step.output_var] = f"[DRY RUN - would call {step.provider}]"
|
||||
else:
|
||||
output, success = execute_prompt_step(step, variables, provider_override, verbose=verbose)
|
||||
output, success = execute_prompt_step(
|
||||
step,
|
||||
variables,
|
||||
provider_override,
|
||||
verbose=verbose,
|
||||
base_dir=tool_base_dir
|
||||
)
|
||||
if not success:
|
||||
return "", 2
|
||||
variables[step.output_var] = output
|
||||
@@ -932,7 +947,13 @@ def run_tool(
|
||||
for var in [v.strip() for v in step.output_var.split(',')]:
|
||||
variables[var] = "[DRY RUN - would execute code]"
|
||||
else:
|
||||
outputs, success = execute_code_step(step, variables, step_num=i+1, verbose=verbose)
|
||||
outputs, success = execute_code_step(
|
||||
step,
|
||||
variables,
|
||||
step_num=i+1,
|
||||
verbose=verbose,
|
||||
base_dir=tool_base_dir
|
||||
)
|
||||
if not success:
|
||||
return "", 1
|
||||
# Merge all output vars into variables
|
||||
@@ -1011,6 +1032,16 @@ def create_argument_parser(tool: Tool) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def collect_custom_args(tool: Tool, args: argparse.Namespace) -> dict:
|
||||
"""Collect tool-specific arguments from a parsed argparse namespace."""
|
||||
custom_args = {}
|
||||
for arg in tool.arguments:
|
||||
value = getattr(args, arg.variable, None)
|
||||
if value is not None:
|
||||
custom_args[arg.variable] = value
|
||||
return custom_args
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for tool execution via wrapper script."""
|
||||
if len(sys.argv) < 2:
|
||||
@@ -1060,11 +1091,7 @@ def main():
|
||||
input_text = ""
|
||||
|
||||
# Collect custom args
|
||||
custom_args = {}
|
||||
for arg in tool.arguments:
|
||||
value = getattr(args, arg.variable, None)
|
||||
if value is not None:
|
||||
custom_args[arg.variable] = value
|
||||
custom_args = collect_custom_args(tool, args)
|
||||
|
||||
# Determine provider override (CLI flag takes precedence over manifest)
|
||||
effective_provider = args.provider or provider_override_from_manifest
|
||||
|
||||
Reference in New Issue
Block a user