From 66f8b342f317d9657e1decfcd99fc6ee27b64161 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Wed, 4 Feb 2026 12:30:05 +0000 Subject: [PATCH] Add CLI commands (init, run, test, prompt) --- src/promptforge/cli/commands/run.py | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/promptforge/cli/commands/run.py diff --git a/src/promptforge/cli/commands/run.py b/src/promptforge/cli/commands/run.py new file mode 100644 index 0000000..024679f --- /dev/null +++ b/src/promptforge/cli/commands/run.py @@ -0,0 +1,84 @@ +import asyncio +import click +from pathlib import Path +from promptforge.core.prompt import Prompt +from promptforge.core.template import TemplateEngine +from promptforge.core.config import get_config +from promptforge.providers import ProviderFactory +from promptforge.testing.validator import Validator + + +@click.command() +@click.argument("name") +@click.option("--provider", "-p", help="Override provider") +@click.option("--var", "-v", multiple=True, help="Variables in key=value format") +@click.option("--output", "-o", type=click.Choice(["text", "json", "yaml"]), default="text") +@click.option("--stream/--no-stream", default=True, help="Stream response") +@click.pass_obj +def run(ctx, name: str, provider: str, var: tuple, output: str, stream: bool): + """Run a prompt with variables.""" + prompts_dir = ctx["prompts_dir"] + prompts = Prompt.list(prompts_dir) + + prompt = next((p for p in prompts if p.name == name), None) + if not prompt: + click.echo(f"Prompt '{name}' not found", err=True) + raise click.Abort() + + variables = {} + for v in var: + if "=" in v: + k, val = v.split("=", 1) + variables[k.strip()] = val.strip() + + template_engine = TemplateEngine() + try: + rendered = template_engine.render(prompt.content, variables, prompt.variables) + except Exception as e: + click.echo(f"Template error: {e}", err=True) + raise click.Abort() + + config = get_config() + selected_provider = provider or prompt.provider or config.defaults.provider + + try: + provider_instance = ProviderFactory.create( + selected_provider, + model=config.providers.get(selected_provider, {}).model if selected_provider in config.providers else None, + temperature=config.providers.get(selected_provider, {}).temperature if selected_provider in config.providers else 0.7, + ) + except Exception as e: + click.echo(f"Provider error: {e}", err=True) + raise click.Abort() + + async def execute(): + if stream: + full_response = [] + async for chunk in provider_instance.stream_complete(rendered): + click.echo(chunk, nl=False) + full_response.append(chunk) + response = "".join(full_response) + else: + result = await provider_instance.complete(rendered) + response = result.content + click.echo(response) + + if output == "json": + import json + click.echo("\n" + json.dumps({"response": response}, indent=2)) + + asyncio.run(execute()) + + +def validate_response(prompt: Prompt, response: str): + for rule in prompt.validation_rules: + if rule.type == "regex": + import re + if not re.search(rule.pattern or "", response): + click.echo(f"Warning: Response failed regex validation", err=True) + elif rule.type == "json": + try: + import json + json.loads(response) + except json.JSONDecodeError: + click.echo(f"Warning: Response is not valid JSON", err=True) \ No newline at end of file