Yazhiniyan's picture
Upload 190 files
5ef3875 verified
Raw History Blame Contribute Delete
4.3 kB
"""Local provider: YCode's own from-scratch model (``ycode-lm``), no API key.
A small model trained on a laptop can answer coding questions and write
short functions, but it has not learned the tool-calling protocol, so it
runs in answer-only mode: it never reads, edits or runs anything.
"""
from __future__ import annotations
from pathlib import Path
from ycode.errors import ConfigError
from ycode.llm.base import (
AssistantMessage,
LLMProvider,
LLMResponse,
Message,
StopReason,
StreamCallbacks,
ToolSpec,
Usage,
UserMessage,
)
_NOTE_PREFIX = "[Note:"
class LocalProvider(LLMProvider):
name = "local"
supports_tools = False
def __init__(self, model_dir: Path, *, max_new_tokens: int = 400, temperature: float = 0.7,
device: str = "auto", lm=None) -> None:
super().__init__(str(model_dir))
self.model_dir = Path(model_dir)
self.max_new_tokens = max_new_tokens
self.temperature = temperature
self.lm = lm if lm is not None else self._load(device)
from ycode.lm.assist import Assistant
# Big models are slow on a CPU, so they get fewer tries at writing a checked function.
big = getattr(self.lm, "num_params", 0) > 500e6
self.assistant = Assistant(self.lm, max_new_tokens=max_new_tokens, temperature=temperature,
candidates=2 if big else 4)
def _load(self, device: str):
try:
from ycode.lm.generate import LocalLM
except ImportError:
raise ConfigError(
"The local model needs PyTorch.",
hint='Install it with: pip install -e ".[local]"',
) from None
try:
return LocalLM(self.model_dir, device=device)
except FileNotFoundError:
from ycode.config import bundled_models
available = ", ".join(m.removeprefix("ycode-lm-") for m in bundled_models())
raise ConfigError(
f"No model found at {self.model_dir}.",
hint=(f"Bundled models available: {available} (e.g. `ycode --local v4`)." if available else
"YCode's trained models live in the repository's models/ folder: install from a clone "
"(git clone https://github.com/yazhiniyanMS/Code-agent && pip install -e .), "
"or set YCODE_MODELS_DIR to a folder containing them."),
) from None
except (ValueError, RuntimeError) as exc:
raise ConfigError(f"Could not load the local model at {self.model_dir}: {exc}") from None
@property
def display_name(self) -> str:
params = getattr(self.lm, "num_params", 0)
version = getattr(self.lm, "version", None)
size = f"{params / 1e6:.1f}M params, " if params else ""
name = f"YCode-LM v{version}" if isinstance(version, int) else "YCode-LM"
return f"{name} ({size}local, {self.model_dir})"
@staticmethod
def _turns(messages: list[Message]) -> list[tuple[str, str]]:
turns: list[tuple[str, str]] = []
for msg in messages:
if isinstance(msg, UserMessage):
text = msg.text
if text.startswith(_NOTE_PREFIX) and "\n\n" in text:
text = text.split("\n\n", 1)[1]
turns.append(("user", text))
elif isinstance(msg, AssistantMessage) and msg.text:
turns.append(("assistant", msg.text))
return turns
def complete(self, *, system: str, messages: list[Message], tools: list[ToolSpec],
callbacks: StreamCallbacks | None = None) -> LLMResponse:
callbacks = callbacks or StreamCallbacks()
turns = self._turns(messages)[-6:]
if not turns or turns[-1][0] != "user":
turns.append(("user", "Continue."))
answer = self.assistant.answer(turns, on_text=callbacks.text)
if not answer.strip():
answer = "(The local model did not produce an answer. Try rephrasing the question.)"
callbacks.text(answer)
return LLMResponse(
AssistantMessage(text=answer, provider=self.name),
StopReason.END_TURN,
Usage(output_tokens=len(answer) // 4),
)