Version 1.0

This commit is contained in:
Alexander R.
2026-07-21 22:24:25 +00:00
parent f3645fbfbc
commit 9edbfd3f77
4134 changed files with 1448752 additions and 1 deletions
+120
View File
@@ -0,0 +1,120 @@
"""Mentor LLM client communicating with llama.cpp server (OpenAI API compatible)."""
import httpx
from typing import Dict, Any, Optional
from src.core.config import config
from src.core.prompts import get_mentor_prompt, get_socratic_prompt
def get_chat_completions_url(base_url: str) -> str:
url = base_url.strip().rstrip("/")
if url.endswith("/chat/completions"):
return url
if url.endswith("/v1"):
return f"{url}/chat/completions"
return f"{url}/v1/chat/completions"
class MentorClient:
"""LLM client for software engineering tutoring via OpenAI-compatible endpoints."""
def __init__(self) -> None:
self.config = config
async def get_guidance_async(
self, challenge: object, user_code: str, user_question: str = ""
) -> Dict[str, Any]:
"""Fetch mentor guidance asynchronously from the configured LLM endpoint."""
prompt = get_mentor_prompt(challenge, user_code, user_question=user_question)
endpoint = get_chat_completions_url(self.config.llm_base_url)
user_content = (
f"Question for Mentor: {user_question}\n\nPlease evaluate my code or answer my question for this challenge."
if user_question.strip()
else "Please review my code structure and provide guidance for my current step."
)
payload = {
"model": self.config.llm_model,
"messages": [
{"role": "system", "content": prompt},
{"role": "user", "content": user_content},
],
"temperature": self.config.llm_temperature,
"max_tokens": self.config.llm_max_tokens,
}
headers = {"Content-Type": "application/json"}
if self.config.llm_api_key and self.config.llm_api_key != "not-needed":
headers["Authorization"] = f"Bearer {self.config.llm_api_key}"
try:
async with httpx.AsyncClient(timeout=self.config.llm_timeout, follow_redirects=True) as client:
response = await client.post(endpoint, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
content = (
data.get("choices", [{}])[0]
.get("message", {})
.get("content", "")
.strip()
)
if content:
return {
"status": "success",
"mentor_response": content,
"offline": False,
}
else:
return {
"status": "error",
"mentor_response": "Received empty response from LLM server.",
"offline": False,
}
except (httpx.ConnectError, httpx.TimeoutException, httpx.TransportError):
return {
"status": "offline",
"mentor_response": (
f"⚠️ LLM Server offline or unreachable at {self.config.llm_base_url}.\n"
"Start your llama.cpp server with:\n"
" ./llama-server -m <your-model.gguf> --port 8080\n\n"
"💡 Offline Hint:\n"
"How is your code structuring the initial state before entering your main logic loop?"
),
"offline": True,
}
except Exception as e:
return {
"status": "error",
"mentor_response": f"⚠️ Error consulting LLM mentor: {e}",
"offline": False,
}
def get_guidance(
self, challenge: object, user_code: str, user_question: str = ""
) -> Dict[str, Any]:
"""Synchronous wrapper for fetching mentor guidance."""
import asyncio
try:
loop = asyncio.get_event_loop()
if loop.is_running():
import nest_asyncio
nest_asyncio.apply()
return loop.run_until_complete(
self.get_guidance_async(challenge, user_code, user_question=user_question)
)
else:
return asyncio.run(
self.get_guidance_async(challenge, user_code, user_question=user_question)
)
except Exception:
return asyncio.run(
self.get_guidance_async(challenge, user_code, user_question=user_question)
)
SocraticMentor = MentorClient
mentor = MentorClient()