diff --git a/local_llm_chat.py b/local_llm_chat.py new file mode 100644 index 0000000..4aee7de --- /dev/null +++ b/local_llm_chat.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import argparse +from pathlib import Path + +import ollama + + +DEFAULT_MODEL = "qwen2.5:3b" +DEFAULT_FILES = [ + "README.md", + "Auto_Seat.ino", + "upload.ps1", +] + + +def read_context(paths: list[str]) -> str: + chunks: list[str] = [] + for item in paths: + path = Path(item) + if not path.exists() or not path.is_file(): + continue + + text = path.read_text(encoding="utf-8", errors="replace") + chunks.append(f"## {path.as_posix()}\n{text}") + + return "\n\n".join(chunks) + + +def ask(model: str, context: str, question: str) -> str: + system_prompt = ( + "You are a local assistant for this Arduino Auto_Seat project. " + "Answer in Korean by default. Use the provided project context when it is relevant. " + "If the context does not contain the answer, say that clearly and reason from general knowledge." + ) + + if context: + user_prompt = f"Project context:\n{context}\n\nQuestion:\n{question}" + else: + user_prompt = question + + response = ollama.chat( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + ) + return response["message"]["content"] + + +def main() -> None: + parser = argparse.ArgumentParser(description="Local LLM chat for the Auto_Seat project.") + parser.add_argument("--model", default=DEFAULT_MODEL, help=f"Ollama model name. Default: {DEFAULT_MODEL}") + parser.add_argument( + "--no-context", + action="store_true", + help="Chat without loading project files into the prompt.", + ) + args = parser.parse_args() + + context = "" if args.no_context else read_context(DEFAULT_FILES) + + print(f"Local LLM ready: {args.model}") + print("Ask anything. Type /exit to quit.") + + while True: + try: + question = input("\nYou> ").strip() + except (EOFError, KeyboardInterrupt): + print() + break + + if question.lower() in {"/exit", "/quit", "exit", "quit"}: + break + if not question: + continue + + try: + answer = ask(args.model, context, question) + except Exception as exc: + print(f"\nError: {exc}") + continue + + print(f"\nLLM> {answer}") + + +if __name__ == "__main__": + main() diff --git a/run-local-llm.ps1 b/run-local-llm.ps1 new file mode 100644 index 0000000..88067a6 --- /dev/null +++ b/run-local-llm.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = "Stop" + +$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") + +if (-not (Test-Path ".\.venv\Scripts\python.exe")) { + python -m venv .venv +} + +.\.venv\Scripts\python.exe -m pip install -r requirements.txt +.\.venv\Scripts\python.exe .\local_llm_chat.py