code-llm-toolkit / inference.py
rndubs's picture
Upload inference.py
6a55750 verified
Raw
History Blame Contribute Delete
8.44 kB
"""
Inference Script: RAG-Augmented Code Generation with Tool Calling
Combines:
- Fine-tuned Qwen2.5-Coder-7B (code gen + tool-calling)
- RAG pipeline (AST-aware code search)
- ReAct-style reasoning loop for multi-step tasks
Usage:
python inference.py --model your-username/qwen25-coder-7b-code-toolcall --repo /path/to/codebase
python inference.py --model your-username/qwen25-coder-7b-code-toolcall --repo /path/to/codebase \
--query "Implement a caching layer for the user service"
"""
import os, json, re, subprocess
from typing import Optional
from rag_pipeline import CodebaseIndexer, CodeRetriever, ContextBuilder
TOOLS = [
{"type": "function", "function": {"name": "search_codebase",
"description": "Search the internal codebase using semantic search.",
"parameters": {"type": "object", "properties": {
"query": {"type": "string"}, "file_pattern": {"type": "string"},
"max_results": {"type": "integer", "default": 5}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "execute_python",
"description": "Execute Python code in a sandboxed environment.",
"parameters": {"type": "object", "properties": {
"code": {"type": "string"}, "timeout": {"type": "integer", "default": 30}},
"required": ["code"]}}},
{"type": "function", "function": {"name": "read_file",
"description": "Read contents of a file from the codebase.",
"parameters": {"type": "object", "properties": {
"path": {"type": "string"}, "start_line": {"type": "integer"},
"end_line": {"type": "integer"}}, "required": ["path"]}}},
{"type": "function", "function": {"name": "run_tests",
"description": "Run unit tests for a module or file.",
"parameters": {"type": "object", "properties": {
"test_path": {"type": "string"}, "verbose": {"type": "boolean", "default": True}},
"required": ["test_path"]}}}
]
SYSTEM_PROMPT = '''You are an expert Python programmer with access to our internal codebase via tools.
When helping with code tasks:
1. ALWAYS search the codebase first to understand existing patterns
2. Reference and integrate with existing code
3. Follow the codebase's conventions
4. Write comprehensive docstrings and type hints
5. Consider edge cases and error handling
6. For complex tasks, break them into steps and use tools iteratively
Think step by step.'''
class ToolExecutor:
def __init__(self, retriever: CodeRetriever, repo_path: str):
self.retriever = retriever
self.repo_path = repo_path
def execute(self, tool_name: str, arguments: dict) -> str:
handlers = {"search_codebase": self._search, "execute_python": self._execute,
"read_file": self._read, "run_tests": self._test}
return handlers.get(tool_name, lambda **kw: f"Unknown tool: {tool_name}")(**arguments)
def _search(self, query, file_pattern=None, max_results=5):
import fnmatch
results = self.retriever.search(query, top_k=max_results)
output = [{"file": c.file_path, "name": c.name, "type": c.chunk_type,
"score": round(s, 3), "signature": c.signature,
"content": c.content[:500]} for c, s in results
if not file_pattern or fnmatch.fnmatch(c.file_path, file_pattern)]
return json.dumps({"results": output}, indent=2)
def _execute(self, code, timeout=30):
try:
r = subprocess.run(["python", "-c", code], capture_output=True, text=True,
timeout=timeout, cwd=self.repo_path)
return (r.stdout or "") + (r.stderr or "") or "Success (no output)"
except subprocess.TimeoutExpired:
return f"Timed out after {timeout}s"
def _read(self, path, start_line=None, end_line=None):
fp = os.path.join(self.repo_path, path)
if not os.path.exists(fp): return f"File not found: {path}"
with open(fp) as f: lines = f.readlines()
if start_line: lines = lines[start_line-1:end_line]
return "".join(lines)
def _test(self, test_path, verbose=True):
cmd = ["python", "-m", "pytest", test_path] + (["-v"] if verbose else [])
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120, cwd=self.repo_path)
return r.stdout + r.stderr
except subprocess.TimeoutExpired:
return "Tests timed out"
class CodeAgent:
"""ReAct-style agent combining fine-tuned LLM with tool execution."""
def __init__(self, model_id, retriever, repo_path, max_turns=10, device="auto"):
self.tool_executor = ToolExecutor(retriever, repo_path)
self.context_builder = ContextBuilder(retriever)
self.max_turns = max_turns
from transformers import AutoModelForCausalLM, AutoTokenizer
self.tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
self.model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype="auto",
device_map=device, trust_remote_code=True)
def run(self, user_query, current_file=None):
rag_context = self.context_builder.build_context(user_query)
user_content = user_query
if rag_context:
user_content += f"\\n\\n--- Code context ---\\n{rag_context}\\n--- End ---"
messages = [{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content}]
for turn in range(self.max_turns):
response = self._generate(messages)
tool_calls = self._extract_tool_calls(response)
if not tool_calls:
return response
messages.append({"role": "assistant", "content": response})
for tc in tool_calls:
result = self.tool_executor.execute(tc["name"], tc["arguments"])
messages.append({"role": "tool", "name": tc["name"], "content": result[:3000]})
return response
def _generate(self, messages):
import torch
text = self.tokenizer.apply_chat_template(messages, tools=TOOLS, tokenize=False,
add_generation_prompt=True)
inputs = self.tokenizer(text, return_tensors="pt").to(self.model.device)
with torch.no_grad():
outputs = self.model.generate(**inputs, max_new_tokens=4096, temperature=0.7,
top_p=0.9, do_sample=True)
return self.tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:],
skip_special_tokens=True).strip()
def _extract_tool_calls(self, response):
tool_calls = []
for pattern in [r'\\{"name":\\s*"(\\w+)",\\s*"arguments":\\s*(\\{[^}]+\\})\\}',
r'<tool_call>\\n?(.*?)\\n?</tool_call>']:
for m in re.finditer(pattern, response, re.DOTALL):
try:
if m.lastindex == 2:
tool_calls.append({"name": m.group(1), "arguments": json.loads(m.group(2))})
else:
data = json.loads(m.group(1))
if "name" in data: tool_calls.append(data)
except: pass
return tool_calls
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--model", required=True)
parser.add_argument("--repo", required=True)
parser.add_argument("--query", "-q", default=None)
parser.add_argument("--index-dir", default=None)
parser.add_argument("--embedding-model", default="jinaai/jina-embeddings-v2-base-code")
args = parser.parse_args()
if args.index_dir and os.path.exists(os.path.join(args.index_dir, "chunks.json")):
retriever = CodeRetriever(args.embedding_model)
retriever.load_index(args.index_dir)
else:
retriever = CodebaseIndexer(args.repo, embedding_model=args.embedding_model).index()
if args.index_dir: retriever.save_index(args.index_dir)
agent = CodeAgent(args.model, retriever, args.repo)
if args.query:
print(agent.run(args.query))
else:
print("Code Assistant (type 'quit' to exit)")
while True:
q = input("> ").strip()
if q.lower() in ("quit", "exit", "q"): break
if q: print(agent.run(q))