feat: Add AI-powered commit message generation with jujutsu integration
- Added `json_extract` module to convert structured text to JSON using LOCAL_LLM_API - Implemented robust error handling for API failures and malformed responses - Added markdown backtick stripping to ensure clean JSON parsing - Integrated AI-driven commit message generation via `jj_commit_message()` and `jj_auto_describe()` - Provided comprehensive unit tests for all core functions - Added support for custom/local LLM endpoints with fallback to default URL
This commit is contained in:
76
src/json_extract/__init__.py
Normal file
76
src/json_extract/__init__.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
def structure_to_json(text: str, schema: Dict) -> Dict:
|
||||
"""
|
||||
Convert structured text to JSON using LOCAL_LLM_API.
|
||||
|
||||
Args:
|
||||
text (str): The structured text to convert
|
||||
schema (dict): The JSON schema to use for conversion
|
||||
|
||||
Returns:
|
||||
dict: The parsed JSON object
|
||||
"""
|
||||
# Get the API URL from environment variable
|
||||
local_llm_api = os.environ.get("LOCAL_LLM_API", "http://host.docker.internal:1248/v1")
|
||||
|
||||
# Format the schema as JSON string
|
||||
schema_json = json.dumps(schema, indent=2)
|
||||
|
||||
# Create the prompt following the pattern from jj-ai-commit.sh
|
||||
prompt_text = f"""Analyse this text and respond ONLY with a valid JSON object matching the schema.
|
||||
|
||||
Text:
|
||||
{text}
|
||||
|
||||
Schema:
|
||||
{schema_json}"""
|
||||
|
||||
# Build the request body
|
||||
request_body = {
|
||||
"model": "qwen3-coder",
|
||||
"messages": [
|
||||
{"role": "user", "content": prompt_text}
|
||||
]
|
||||
}
|
||||
|
||||
# Build the URL
|
||||
url = f"{local_llm_api}/chat/completions"
|
||||
|
||||
# Create the request
|
||||
data = json.dumps(request_body).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST"
|
||||
)
|
||||
|
||||
try:
|
||||
# Execute the request
|
||||
with urllib.request.urlopen(req) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.URLError as e:
|
||||
return {"error": f"API request failed: {e}", "raw_response": ""}
|
||||
except json.JSONDecodeError as e:
|
||||
return {"error": f"Failed to parse response: {e}", "raw_response": ""}
|
||||
|
||||
try:
|
||||
content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||
|
||||
# Clean markdown backticks from response
|
||||
clean_content = content.strip()
|
||||
while clean_content.startswith("`") or clean_content.startswith("```"):
|
||||
clean_content = clean_content[1:].strip()
|
||||
while clean_content.endswith("`") or clean_content.endswith("```"):
|
||||
clean_content = clean_content[:-1].strip()
|
||||
|
||||
# Parse the JSON content
|
||||
return json.loads(clean_content)
|
||||
except (json.JSONDecodeError, KeyError, IndexError) as e:
|
||||
return {"error": f"Failed to parse JSON: {e}", "raw_response": json.dumps(result)}
|
||||
Reference in New Issue
Block a user