(no commit message)

This commit is contained in:
2026-01-18 22:37:06 -08:00
parent 2945d17b91
commit 192a744884
12 changed files with 599 additions and 0 deletions

151
nanocode.py Normal file
View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""nanocode-dspy - minimal claude code alternative using DSPy ReAct"""
import os
from modaic import PrecompiledProgram, PrecompiledConfig
import dspy
from dspy.utils.callback import BaseCallback
from utils import (
RESET, BOLD, DIM, BLUE, CYAN, GREEN, RED, MAGENTA,
separator, render_markdown,
read_file, write_file, edit_file, glob_files, grep_files, run_bash,
AVAILABLE_MODELS, select_model,
)
# --- DSPy Signature ---
class CodingAssistant(dspy.Signature):
"""You are a concise coding assistant. Help the user with their coding task by using the available tools to read, write, edit files, search the codebase, and run commands."""
task: str = dspy.InputField(desc="The user's coding task or question")
answer: str = dspy.OutputField(desc="Your response to the user after completing the task")
affected_files: list[str] = dspy.OutputField(desc="List of files that were written or modified during the task")
# ReAct agent with tools
tools = [read_file, write_file, edit_file, glob_files, grep_files, run_bash]
class ToolLoggingCallback(BaseCallback):
"""Callback that logs tool calls as they happen."""
def on_tool_start(self, call_id, instance, inputs):
"""Log when a tool starts executing."""
tool_name = instance.name if hasattr(instance, 'name') else str(instance)
# Format args nicely
args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in inputs.items())
print(f" {MAGENTA}{tool_name}({args_str}){RESET}", flush=True)
def on_tool_end(self, call_id, outputs, exception):
"""Log when a tool finishes executing."""
if exception:
print(f" {RED}Error: {exception}{RESET}", flush=True)
def on_module_end(self, call_id, outputs, exception):
"""Log when the finish tool is called (ReAct completion)."""
# Check if this is a ReAct prediction with tool_calls
if outputs and 'tool_calls' in outputs:
for call in outputs['tool_calls']:
args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in call.args.items())
if call.name == 'finish':
print(f" {GREEN}⏺ finish{RESET}", flush=True)
else:
print(f" {MAGENTA}{call.name}({args_str}){RESET}", flush=True)
class AgentConfig(PrecompiledConfig):
max_iters: int = 15
lm: str = "openrouter/anthropic/claude-3.5-sonnet" # Default fallback
api_base: str = "https://openrouter.ai/api/v1"
max_tokens: int = 8192
class AgentProgram(PrecompiledProgram):
config: AgentConfig
def __init__(self, config: AgentConfig, **kwargs):
self.config = config
super().__init__(config, **kwargs)
# Configure logging callback globally
dspy.settings.configure(callbacks=[ToolLoggingCallback()])
agent = dspy.ReAct(CodingAssistant, tools=tools, max_iters=self.config.max_iters)
lm = dspy.LM(self.config.lm, api_base=self.config.api_base, max_tokens=self.config.max_tokens)
agent.set_lm(lm)
self.agent = agent
def forward(self, task: str) -> str:
assert task, "Task cannot be empty"
return self.agent(task=task)
# --- Main ---
def main():
"""Create AgentConfig with selected model."""
model = os.getenv("MODEL")
if model is None:
model = select_model()
# Add openrouter/ prefix if not already present
if not model.startswith("openrouter/"):
model = f"openrouter/{model}"
config = AgentConfig()
config.lm = model
agent = AgentProgram(config)
print(f"{BOLD}nanocode-dspy{RESET} | {DIM}{agent.config.lm} | {os.getcwd()}{RESET}\n")
# Conversation history for context
history = []
while True:
try:
print(separator())
user_input = input(f"{BOLD}{BLUE}{RESET} ").strip()
print(separator())
if not user_input:
continue
if user_input in ("/q", "exit"):
break
if user_input == "/c":
history = []
print(f"{GREEN}⏺ Cleared conversation{RESET}")
continue
# Build context from history
context = f"Working directory: {os.getcwd()}\n"
if history:
context += "\nPrevious conversation:\n"
for h in history[-5:]: # Keep last 5 exchanges
context += f"User: {h['user']}\nAssistant: {h['assistant']}\n\n"
task = f"{context}\nCurrent task: {user_input}"
print(f"\n{CYAN}{RESET} Thinking...", flush=True)
# Run the ReAct agent
result = agent(task=task)
# Display the answer
print(f"\n{CYAN}{RESET} {render_markdown(result.answer)}")
# Save to history
history.append({"user": user_input, "assistant": result.answer})
print()
except (KeyboardInterrupt, EOFError):
break
except Exception as err:
import traceback
traceback.print_exc()
print(f"{RED}⏺ Error: {err}{RESET}")
if __name__ == "__main__":
main()