Initial upload of auto-changelog-generator
This commit is contained in:
144
src/changeloggen/llm_client.py
Normal file
144
src/changeloggen/llm_client.py
Normal file
@@ -0,0 +1,144 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class CategorizedChange:
|
||||
"""Represents a single categorized change."""
|
||||
type: str
|
||||
scope: Optional[str]
|
||||
description: str
|
||||
file_path: str
|
||||
breaking_change: bool = False
|
||||
breaking_description: Optional[str] = None
|
||||
confidence: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMResponse:
|
||||
"""Response from LLM for change categorization."""
|
||||
changes: list[CategorizedChange]
|
||||
summary: str
|
||||
version: str = "1.0"
|
||||
breaking_changes: list[str] = field(default_factory=list)
|
||||
contributors: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""Configuration for LLM client."""
|
||||
base_url: str = "http://localhost:11434"
|
||||
model: str = "llama3.2"
|
||||
temperature: float = 0.3
|
||||
max_tokens: int = 2000
|
||||
timeout: int = 120
|
||||
|
||||
|
||||
class OllamaAPIClient:
|
||||
"""Client for interacting with Ollama or LM Studio local API."""
|
||||
|
||||
def __init__(self, config: Optional[Config] = None):
|
||||
self.config = config or Config()
|
||||
self.base_url = self.config.base_url
|
||||
self.model = self.config.model
|
||||
|
||||
def _make_request(self, endpoint: str, payload: dict) -> dict:
|
||||
"""Make HTTP request to LLM API."""
|
||||
import requests
|
||||
url = f"{self.base_url}/{endpoint}"
|
||||
response = requests.post(
|
||||
url,
|
||||
json=payload,
|
||||
timeout=self.config.timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def generate(self, prompt: str) -> str:
|
||||
"""Generate response from LLM."""
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": self.config.temperature,
|
||||
"num_predict": self.config.max_tokens,
|
||||
}
|
||||
}
|
||||
result = self._make_request("api/generate", payload)
|
||||
return result.get("response", "")
|
||||
|
||||
def chat(self, messages: list[dict]) -> str:
|
||||
"""Send chat messages to LLM."""
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": self.config.temperature,
|
||||
"num_predict": self.config.max_tokens,
|
||||
}
|
||||
}
|
||||
result = self._make_request("api/chat", payload)
|
||||
return result.get("message", {}).get("content", "")
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if LLM API is available."""
|
||||
try:
|
||||
import requests
|
||||
response = requests.get(
|
||||
f"{self.base_url}/api/tags",
|
||||
timeout=5
|
||||
)
|
||||
return response.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def build_categorization_prompt(changes_text: str) -> str:
|
||||
"""Build prompt for categorizing changes."""
|
||||
return f"""You are a helpful assistant that categorizes git changes according to the Conventional Commits specification.
|
||||
|
||||
Please analyze the following git diff changes and categorize each change into one of these types:
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- docs: Documentation changes
|
||||
- breaking: Breaking changes (could be part of any type)
|
||||
- refactor: Code refactoring without feature/fix
|
||||
- style: Formatting, missing semi-colons, etc.
|
||||
- test: Adding or modifying tests
|
||||
- chore: Maintenance tasks, dependency updates, etc.
|
||||
|
||||
For each change, provide:
|
||||
1. The type (one of the above)
|
||||
2. An optional scope (e.g., 'auth', 'api', 'ui')
|
||||
3. A concise description (imperative mood, max 100 chars)
|
||||
4. Whether it's a breaking change
|
||||
5. Breaking change description if applicable
|
||||
|
||||
Also provide a brief summary of all changes combined (2-3 sentences).
|
||||
|
||||
Output your response in the following JSON format:
|
||||
{{
|
||||
"changes": [
|
||||
{{
|
||||
"type": "feat",
|
||||
"scope": "auth",
|
||||
"description": "add user login functionality",
|
||||
"file_path": "src/auth/login.py",
|
||||
"breaking_change": false,
|
||||
"breaking_description": null,
|
||||
"confidence": 0.9
|
||||
}}
|
||||
],
|
||||
"summary": "This release adds user authentication, fixes a memory leak, and updates documentation.",
|
||||
"version": "1.0.0",
|
||||
"breaking_changes": [],
|
||||
"contributors": []
|
||||
}}
|
||||
|
||||
Here are the changes to categorize:
|
||||
|
||||
{changes_text}
|
||||
|
||||
Remember to output ONLY valid JSON without any additional text or formatting."""
|
||||
Reference in New Issue
Block a user