Fix Issue #248: Replace hardcoded OpenAI usage with unified LLM provider service

- Convert generate_code_example_summary() to async and use LLM provider service
- Convert extract_source_summary() and generate_source_title_and_metadata() to async
- Replace direct OpenAI client instantiation with get_llm_client() context manager
- Update all function calls to use await for async functions
- This enables Ollama support for code extraction and source summary generation

Fixes: #248 - Ollama model code extraction now works properly
This commit is contained in:
John Fitzpatrick
2025-09-04 13:27:26 -07:00
parent 818b94ba7a
commit f0dc898f7b
2 changed files with 58 additions and 149 deletions

View File

@@ -33,7 +33,7 @@ def _get_model_choice() -> str:
return "gpt-4.1-nano"
def extract_source_summary(
async def extract_source_summary(
source_id: str, content: str, max_length: int = 500, provider: str = None
) -> str:
"""
@@ -72,39 +72,14 @@ The above content is from the documentation for '{source_id}'. Please provide a
"""
try:
try:
import os
# Use unified LLM provider service instead of hardcoded OpenAI
from .llm_provider_service import get_llm_client
import openai
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
# Try to get from credential service with direct fallback
from .credential_service import credential_service
if (
credential_service._cache_initialized
and "OPENAI_API_KEY" in credential_service._cache
):
cached_key = credential_service._cache["OPENAI_API_KEY"]
if isinstance(cached_key, dict) and cached_key.get("is_encrypted"):
api_key = credential_service._decrypt_value(cached_key["encrypted_value"])
else:
api_key = cached_key
else:
api_key = os.getenv("OPENAI_API_KEY", "")
if not api_key:
raise ValueError("No OpenAI API key available")
client = openai.OpenAI(api_key=api_key)
search_logger.info("Successfully created LLM client fallback for summary generation")
except Exception as e:
search_logger.error(f"Failed to create LLM client fallback: {e}")
return default_summary
# Call the OpenAI API to generate the summary
response = client.chat.completions.create(
async with get_llm_client(provider=provider) as client:
search_logger.info("Successfully created LLM client for summary generation")
# Call the LLM API to generate the summary
response = await client.chat.completions.create(
model=model_choice,
messages=[
{
@@ -140,7 +115,7 @@ The above content is from the documentation for '{source_id}'. Please provide a
return default_summary
def generate_source_title_and_metadata(
async def generate_source_title_and_metadata(
source_id: str,
content: str,
knowledge_type: str = "technical",
@@ -166,40 +141,8 @@ def generate_source_title_and_metadata(
# Try to generate a better title from content
if content and len(content.strip()) > 100:
try:
try:
import os
import openai
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
# Try to get from credential service with direct fallback
from .credential_service import credential_service
if (
credential_service._cache_initialized
and "OPENAI_API_KEY" in credential_service._cache
):
cached_key = credential_service._cache["OPENAI_API_KEY"]
if isinstance(cached_key, dict) and cached_key.get("is_encrypted"):
api_key = credential_service._decrypt_value(
cached_key["encrypted_value"]
)
else:
api_key = cached_key
else:
api_key = os.getenv("OPENAI_API_KEY", "")
if not api_key:
raise ValueError("No OpenAI API key available")
client = openai.OpenAI(api_key=api_key)
except Exception as e:
search_logger.error(
f"Failed to create LLM client fallback for title generation: {e}"
)
# Don't proceed if client creation fails
raise
# Use unified LLM provider service instead of hardcoded OpenAI
from .llm_provider_service import get_llm_client
model_choice = _get_model_choice()
@@ -215,7 +158,8 @@ def generate_source_title_and_metadata(
Provide only the title, nothing else."""
response = client.chat.completions.create(
async with get_llm_client(provider=provider) as client:
response = await client.chat.completions.create(
model=model_choice,
messages=[
{
@@ -247,7 +191,7 @@ Provide only the title, nothing else."""
return title, metadata
def update_source_info(
async def update_source_info(
client: Client,
source_id: str,
summary: str,
@@ -351,7 +295,7 @@ def update_source_info(
}
else:
# Fallback to AI generation only if no display name
title, metadata = generate_source_title_and_metadata(
title, metadata = await generate_source_title_and_metadata(
source_id, content, knowledge_type, tags, None, source_display_name
)
@@ -568,7 +512,7 @@ class SourceManagementService:
logger.error(f"Error updating source metadata: {e}")
return False, {"error": f"Error updating source metadata: {str(e)}"}
def create_source_info(
async def create_source_info(
self,
source_id: str,
content_sample: str,
@@ -596,10 +540,10 @@ class SourceManagementService:
tags = []
# Generate source summary using the utility function
source_summary = extract_source_summary(source_id, content_sample)
source_summary = await extract_source_summary(source_id, content_sample)
# Create the source info using the utility function
update_source_info(
await update_source_info(
self.supabase_client,
source_id,
source_summary,

View File

@@ -489,7 +489,7 @@ def extract_code_blocks(markdown_content: str, min_length: int = None) -> list[d
return grouped_blocks
def generate_code_example_summary(
async def generate_code_example_summary(
code: str, context_before: str, context_after: str, language: str = "", provider: str = None
) -> dict[str, str]:
"""
@@ -535,82 +535,50 @@ Format your response as JSON:
"""
try:
# Get LLM client using fallback
try:
import os
# Use unified LLM provider service instead of hardcoded OpenAI
from ..llm_provider_service import get_llm_client
import openai
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
# Try to get from credential service with direct fallback
from ..credential_service import credential_service
if (
credential_service._cache_initialized
and "OPENAI_API_KEY" in credential_service._cache
):
cached_key = credential_service._cache["OPENAI_API_KEY"]
if isinstance(cached_key, dict) and cached_key.get("is_encrypted"):
api_key = credential_service._decrypt_value(cached_key["encrypted_value"])
else:
api_key = cached_key
else:
api_key = os.getenv("OPENAI_API_KEY", "")
if not api_key:
raise ValueError("No OpenAI API key available")
client = openai.OpenAI(api_key=api_key)
except Exception as e:
search_logger.error(
f"Failed to create LLM client fallback: {e} - returning default values"
async with get_llm_client(provider=provider) as client:
search_logger.debug(
f"Calling LLM API with model: {model_choice}, language: {language}, code length: {len(code)}"
)
return {
"example_name": f"Code Example{f' ({language})' if language else ''}",
"summary": "Code example for demonstration purposes.",
response = await client.chat.completions.create(
model=model_choice,
messages=[
{
"role": "system",
"content": "You are a helpful assistant that analyzes code examples and provides JSON responses with example names and summaries.",
},
{"role": "user", "content": prompt},
],
response_format={"type": "json_object"},
)
response_content = response.choices[0].message.content.strip()
search_logger.debug(f"LLM API response: {repr(response_content[:200])}...")
result = json.loads(response_content)
# Validate the response has the required fields
if not result.get("example_name") or not result.get("summary"):
search_logger.warning(f"Incomplete response from LLM: {result}")
final_result = {
"example_name": result.get(
"example_name", f"Code Example{f' ({language})' if language else ''}"
),
"summary": result.get("summary", "Code example for demonstration purposes."),
}
search_logger.debug(
f"Calling OpenAI API with model: {model_choice}, language: {language}, code length: {len(code)}"
)
response = client.chat.completions.create(
model=model_choice,
messages=[
{
"role": "system",
"content": "You are a helpful assistant that analyzes code examples and provides JSON responses with example names and summaries.",
},
{"role": "user", "content": prompt},
],
response_format={"type": "json_object"},
)
response_content = response.choices[0].message.content.strip()
search_logger.debug(f"OpenAI API response: {repr(response_content[:200])}...")
result = json.loads(response_content)
# Validate the response has the required fields
if not result.get("example_name") or not result.get("summary"):
search_logger.warning(f"Incomplete response from OpenAI: {result}")
final_result = {
"example_name": result.get(
"example_name", f"Code Example{f' ({language})' if language else ''}"
),
"summary": result.get("summary", "Code example for demonstration purposes."),
}
search_logger.info(
f"Generated code example summary - Name: '{final_result['example_name']}', Summary length: {len(final_result['summary'])}"
)
return final_result
search_logger.info(
f"Generated code example summary - Name: '{final_result['example_name']}', Summary length: {len(final_result['summary'])}"
)
return final_result
except json.JSONDecodeError as e:
search_logger.error(
f"Failed to parse JSON response from OpenAI: {e}, Response: {repr(response_content) if 'response_content' in locals() else 'No response'}"
f"Failed to parse JSON response from LLM: {e}, Response: {repr(response_content) if 'response_content' in locals() else 'No response'}"
)
return {
"example_name": f"Code Example{f' ({language})' if language else ''}",
@@ -671,11 +639,8 @@ async def generate_code_summaries_batch(
# Add delay between requests to avoid rate limiting
await asyncio.sleep(0.5) # 500ms delay between requests
# Run the synchronous function in a thread
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
None,
generate_code_example_summary,
# Call the async function directly
result = await generate_code_example_summary(
block["code"],
block["context_before"],
block["context_after"],