rndubs commited on
Commit
6a55750
·
verified ·
1 Parent(s): 824acee

Upload inference.py

Browse files
Files changed (1) hide show
  1. inference.py +178 -0
inference.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Inference Script: RAG-Augmented Code Generation with Tool Calling
3
+
4
+ Combines:
5
+ - Fine-tuned Qwen2.5-Coder-7B (code gen + tool-calling)
6
+ - RAG pipeline (AST-aware code search)
7
+ - ReAct-style reasoning loop for multi-step tasks
8
+
9
+ Usage:
10
+ python inference.py --model your-username/qwen25-coder-7b-code-toolcall --repo /path/to/codebase
11
+ python inference.py --model your-username/qwen25-coder-7b-code-toolcall --repo /path/to/codebase \
12
+ --query "Implement a caching layer for the user service"
13
+ """
14
+
15
+ import os, json, re, subprocess
16
+ from typing import Optional
17
+ from rag_pipeline import CodebaseIndexer, CodeRetriever, ContextBuilder
18
+
19
+ TOOLS = [
20
+ {"type": "function", "function": {"name": "search_codebase",
21
+ "description": "Search the internal codebase using semantic search.",
22
+ "parameters": {"type": "object", "properties": {
23
+ "query": {"type": "string"}, "file_pattern": {"type": "string"},
24
+ "max_results": {"type": "integer", "default": 5}}, "required": ["query"]}}},
25
+ {"type": "function", "function": {"name": "execute_python",
26
+ "description": "Execute Python code in a sandboxed environment.",
27
+ "parameters": {"type": "object", "properties": {
28
+ "code": {"type": "string"}, "timeout": {"type": "integer", "default": 30}},
29
+ "required": ["code"]}}},
30
+ {"type": "function", "function": {"name": "read_file",
31
+ "description": "Read contents of a file from the codebase.",
32
+ "parameters": {"type": "object", "properties": {
33
+ "path": {"type": "string"}, "start_line": {"type": "integer"},
34
+ "end_line": {"type": "integer"}}, "required": ["path"]}}},
35
+ {"type": "function", "function": {"name": "run_tests",
36
+ "description": "Run unit tests for a module or file.",
37
+ "parameters": {"type": "object", "properties": {
38
+ "test_path": {"type": "string"}, "verbose": {"type": "boolean", "default": True}},
39
+ "required": ["test_path"]}}}
40
+ ]
41
+
42
+ SYSTEM_PROMPT = '''You are an expert Python programmer with access to our internal codebase via tools.
43
+ When helping with code tasks:
44
+ 1. ALWAYS search the codebase first to understand existing patterns
45
+ 2. Reference and integrate with existing code
46
+ 3. Follow the codebase's conventions
47
+ 4. Write comprehensive docstrings and type hints
48
+ 5. Consider edge cases and error handling
49
+ 6. For complex tasks, break them into steps and use tools iteratively
50
+ Think step by step.'''
51
+
52
+
53
+ class ToolExecutor:
54
+ def __init__(self, retriever: CodeRetriever, repo_path: str):
55
+ self.retriever = retriever
56
+ self.repo_path = repo_path
57
+
58
+ def execute(self, tool_name: str, arguments: dict) -> str:
59
+ handlers = {"search_codebase": self._search, "execute_python": self._execute,
60
+ "read_file": self._read, "run_tests": self._test}
61
+ return handlers.get(tool_name, lambda **kw: f"Unknown tool: {tool_name}")(**arguments)
62
+
63
+ def _search(self, query, file_pattern=None, max_results=5):
64
+ import fnmatch
65
+ results = self.retriever.search(query, top_k=max_results)
66
+ output = [{"file": c.file_path, "name": c.name, "type": c.chunk_type,
67
+ "score": round(s, 3), "signature": c.signature,
68
+ "content": c.content[:500]} for c, s in results
69
+ if not file_pattern or fnmatch.fnmatch(c.file_path, file_pattern)]
70
+ return json.dumps({"results": output}, indent=2)
71
+
72
+ def _execute(self, code, timeout=30):
73
+ try:
74
+ r = subprocess.run(["python", "-c", code], capture_output=True, text=True,
75
+ timeout=timeout, cwd=self.repo_path)
76
+ return (r.stdout or "") + (r.stderr or "") or "Success (no output)"
77
+ except subprocess.TimeoutExpired:
78
+ return f"Timed out after {timeout}s"
79
+
80
+ def _read(self, path, start_line=None, end_line=None):
81
+ fp = os.path.join(self.repo_path, path)
82
+ if not os.path.exists(fp): return f"File not found: {path}"
83
+ with open(fp) as f: lines = f.readlines()
84
+ if start_line: lines = lines[start_line-1:end_line]
85
+ return "".join(lines)
86
+
87
+ def _test(self, test_path, verbose=True):
88
+ cmd = ["python", "-m", "pytest", test_path] + (["-v"] if verbose else [])
89
+ try:
90
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=120, cwd=self.repo_path)
91
+ return r.stdout + r.stderr
92
+ except subprocess.TimeoutExpired:
93
+ return "Tests timed out"
94
+
95
+
96
+ class CodeAgent:
97
+ """ReAct-style agent combining fine-tuned LLM with tool execution."""
98
+
99
+ def __init__(self, model_id, retriever, repo_path, max_turns=10, device="auto"):
100
+ self.tool_executor = ToolExecutor(retriever, repo_path)
101
+ self.context_builder = ContextBuilder(retriever)
102
+ self.max_turns = max_turns
103
+ from transformers import AutoModelForCausalLM, AutoTokenizer
104
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
105
+ self.model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype="auto",
106
+ device_map=device, trust_remote_code=True)
107
+
108
+ def run(self, user_query, current_file=None):
109
+ rag_context = self.context_builder.build_context(user_query)
110
+ user_content = user_query
111
+ if rag_context:
112
+ user_content += f"\\n\\n--- Code context ---\\n{rag_context}\\n--- End ---"
113
+ messages = [{"role": "system", "content": SYSTEM_PROMPT},
114
+ {"role": "user", "content": user_content}]
115
+
116
+ for turn in range(self.max_turns):
117
+ response = self._generate(messages)
118
+ tool_calls = self._extract_tool_calls(response)
119
+ if not tool_calls:
120
+ return response
121
+ messages.append({"role": "assistant", "content": response})
122
+ for tc in tool_calls:
123
+ result = self.tool_executor.execute(tc["name"], tc["arguments"])
124
+ messages.append({"role": "tool", "name": tc["name"], "content": result[:3000]})
125
+ return response
126
+
127
+ def _generate(self, messages):
128
+ import torch
129
+ text = self.tokenizer.apply_chat_template(messages, tools=TOOLS, tokenize=False,
130
+ add_generation_prompt=True)
131
+ inputs = self.tokenizer(text, return_tensors="pt").to(self.model.device)
132
+ with torch.no_grad():
133
+ outputs = self.model.generate(**inputs, max_new_tokens=4096, temperature=0.7,
134
+ top_p=0.9, do_sample=True)
135
+ return self.tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:],
136
+ skip_special_tokens=True).strip()
137
+
138
+ def _extract_tool_calls(self, response):
139
+ tool_calls = []
140
+ for pattern in [r'\\{"name":\\s*"(\\w+)",\\s*"arguments":\\s*(\\{[^}]+\\})\\}',
141
+ r'<tool_call>\\n?(.*?)\\n?</tool_call>']:
142
+ for m in re.finditer(pattern, response, re.DOTALL):
143
+ try:
144
+ if m.lastindex == 2:
145
+ tool_calls.append({"name": m.group(1), "arguments": json.loads(m.group(2))})
146
+ else:
147
+ data = json.loads(m.group(1))
148
+ if "name" in data: tool_calls.append(data)
149
+ except: pass
150
+ return tool_calls
151
+
152
+
153
+ if __name__ == "__main__":
154
+ import argparse
155
+ parser = argparse.ArgumentParser()
156
+ parser.add_argument("--model", required=True)
157
+ parser.add_argument("--repo", required=True)
158
+ parser.add_argument("--query", "-q", default=None)
159
+ parser.add_argument("--index-dir", default=None)
160
+ parser.add_argument("--embedding-model", default="jinaai/jina-embeddings-v2-base-code")
161
+ args = parser.parse_args()
162
+
163
+ if args.index_dir and os.path.exists(os.path.join(args.index_dir, "chunks.json")):
164
+ retriever = CodeRetriever(args.embedding_model)
165
+ retriever.load_index(args.index_dir)
166
+ else:
167
+ retriever = CodebaseIndexer(args.repo, embedding_model=args.embedding_model).index()
168
+ if args.index_dir: retriever.save_index(args.index_dir)
169
+
170
+ agent = CodeAgent(args.model, retriever, args.repo)
171
+ if args.query:
172
+ print(agent.run(args.query))
173
+ else:
174
+ print("Code Assistant (type 'quit' to exit)")
175
+ while True:
176
+ q = input("> ").strip()
177
+ if q.lower() in ("quit", "exit", "q"): break
178
+ if q: print(agent.run(q))