Spaces:
Sleeping
Sleeping
File size: 7,766 Bytes
210ef30 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | """The developer-assistant tools. Master prompt 6: a capability inside BOTH modes.
"The tone follows the mode; the substance is identical." So each tool runs a specialist,
mode-free sub-call that returns JSON, and Prova's own turn presents that result in whichever
voice the mode calls for. Nothing here knows or cares which mode it is in.
Each result is also returned as an *artifact* — the web app renders a review as a diff view and
an API design as a table (master prompt 6's UI list), rather than as prose.
"""
import json
import logging
from typing import Any
import httpx
from .. import config
from ..llm.base import user_content
from ..tools.registry import Tool, ToolContext, ToolOutcome, failed, ok, schema, string
from . import github, prompts
from .attachments import resolve
log = logging.getLogger(__name__)
SUBCALL_TEMPERATURE = 0.4
MAX_CODE_CHARS = 120_000
async def _specialist(ctx: ToolContext, instruction: str, material: str) -> dict[str, Any] | None:
turn = await ctx.llm.step(
instruction.strip(),
[user_content(material[:MAX_CODE_CHARS])],
temperature=SUBCALL_TEMPERATURE,
max_output_tokens=config.MAX_OUTPUT_TOKENS_TECHNICAL,
response_mime_type="application/json",
)
return parse_json(turn.text)
def parse_json(text: str) -> dict[str, Any] | None:
"""JSON mode is requested, but a fenced or prefixed reply must not lose the whole result."""
raw = text.strip()
if raw.startswith("```"):
raw = raw.strip("`")
raw = raw.split("\n", 1)[1] if "\n" in raw else raw
start, end = raw.find("{"), raw.rfind("}")
if start == -1 or end == -1:
return None
try:
value = json.loads(raw[start : end + 1])
except json.JSONDecodeError:
return None
return value if isinstance(value, dict) else None
async def _review_code(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
label, code = resolve(args.get("target"), ctx.attachments, ctx.user_message)
if not code.strip():
return ToolOutcome(failed("there is no code to review — ask him to paste or attach it"))
material = f"FILE: {label}\n\n{code}"
if args.get("focus"):
material = f"FOCUS: {args['focus']}\n\n{material}"
result = await _specialist(ctx, prompts.REVIEW, material)
if result is None:
return ToolOutcome(failed("the review came back unreadable"))
findings = [f for f in result.get("findings", []) if isinstance(f, dict)][:12]
artifact = {
"type": "review",
"target": label,
"summary": result.get("summary", ""),
"findings": findings,
}
return ToolOutcome(ok("reviewed", **artifact), artifacts=[artifact])
async def _explain_code(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
label, code = resolve(args.get("target"), ctx.attachments, ctx.user_message)
if not code.strip():
return ToolOutcome(failed("there is no code to explain"))
material = f"FILE: {label}\n\n{code}"
if args.get("focus"):
material = f"QUESTION: {args['focus']}\n\n{material}"
result = await _specialist(ctx, prompts.EXPLAIN, material)
if result is None:
return ToolOutcome(failed("the explanation came back unreadable"))
return ToolOutcome(ok("explained", target=label, **result))
async def _debug_error(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
error = str(args["error"]).strip()
label, code = resolve(args.get("target"), ctx.attachments, ctx.user_message)
material = f"ERROR:\n{error}"
if code.strip() and code.strip() != error:
material += f"\n\nCODE ({label}):\n{code}"
result = await _specialist(ctx, prompts.DEBUG, material)
if result is None:
return ToolOutcome(failed("the diagnosis came back unreadable"))
artifact = {"type": "debug", "target": label, **result}
return ToolOutcome(ok("diagnosed", **result), artifacts=[artifact])
async def _analyze_project(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
if not ctx.tree and not ctx.attachments:
return ToolOutcome(failed("no project is open — ask him to open a folder or attach files"))
parts = []
if ctx.tree:
parts.append("TREE:\n" + "\n".join(ctx.tree[:1500]))
for name, content in list(ctx.attachments.items())[:20]:
parts.append(f"FILE: {name}\n{content[:20_000]}")
if args.get("focus"):
parts.insert(0, f"FOCUS: {args['focus']}")
result = await _specialist(ctx, prompts.ANALYZE_PROJECT, "\n\n".join(parts))
if result is None:
return ToolOutcome(failed("the analysis came back unreadable"))
artifact = {"type": "analysis", **result}
return ToolOutcome(ok("analysed", **result), artifacts=[artifact])
async def _design_api(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
result = await _specialist(ctx, prompts.DESIGN_API, f"REQUIREMENTS:\n{args['requirements']}")
if result is None:
return ToolOutcome(failed("the design came back unreadable"))
artifact = {"type": "api", **result}
return ToolOutcome(ok("designed", **result), artifacts=[artifact])
async def _fetch_github(args: dict[str, Any], ctx: ToolContext) -> ToolOutcome:
client = ctx.http or httpx.AsyncClient(timeout=20.0, follow_redirects=False)
try:
fetched = await github.fetch(client, str(args["url"]))
except (github.GithubError, httpx.HTTPError) as exc:
return ToolOutcome(failed(str(exc) or "could not fetch that link"))
finally:
if ctx.http is None:
await client.aclose()
if fetched["kind"] == "file":
# Available to the other dev tools for the rest of this turn.
ctx.attachments[fetched["name"]] = fetched["content"]
return ToolOutcome(
ok("fetched", file=fetched["name"], content=fetched["content"][:MAX_CODE_CHARS])
)
ctx.tree = fetched["paths"]
return ToolOutcome(
ok(
"fetched tree",
repo=fetched["repo"],
files=len(fetched["paths"]),
paths=fetched["paths"][:300],
)
)
_TARGET = string("An attached file's name, or omit to use the code pasted in his message")
DEV_TOOLS: list[Tool] = [
Tool(
"review_code",
"Review code for bugs, security, performance and WordPress/WooCommerce/Laravel/NestJS "
"best practice. Returns findings with suggested edits he can see as a diff.",
schema({"target": _TARGET, "focus": string("Anything specific he wants checked")}),
_review_code,
),
Tool(
"explain_code",
"Explain what a piece of code does, step by step.",
schema({"target": _TARGET, "focus": string("His specific question about it")}),
_explain_code,
),
Tool(
"debug_error",
"Diagnose an error message or stack trace, with the code if he gave it.",
schema(
{"error": string("The error or stack trace, verbatim"), "target": _TARGET}, ["error"]
),
_debug_error,
),
Tool(
"analyze_project",
"Get oriented in a whole project from its file tree and attached files.",
schema({"focus": string("What he wants to understand about it")}),
_analyze_project,
),
Tool(
"design_api",
"Design a REST API — resources, endpoints, data model — from requirements.",
schema({"requirements": string("What the API has to do")}, ["requirements"]),
_design_api,
),
Tool(
"fetch_github",
"Fetch a public GitHub file or repository tree from a github.com link he shared.",
schema({"url": string("The github.com or raw.githubusercontent.com link")}, ["url"]),
_fetch_github,
),
]
|